Commit Graph
47 Commits
Author SHA1 Message Date
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
chichuan 41a3724e9e Merge branch 'main' into feat/drive-sync-family 2026-08-13 22:07:16 +08:00
chichuan 8c00068364 fix(drive): harden folder mirror safety 2026-08-13 15:49:59 +08:00
卷心菜 9c59206d2f feat(drive): add drive list --type/--start/--end client-side filtering 2026-08-13 14:12:13 +08:00
chichuan 1dabfa1dc6 fix(drive): keep pull partial results on stdout 2026-08-12 20:48:45 +08: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
之桐 86ec9733c0 Merge remote-tracking branch 'upstream/main' into feat/drive-file-comments 2026-08-12 14:35:22 +08:00
chichuan f6a699227e Merge branch 'main' into feat/drive-sync-family 2026-08-12 12:44:39 +08:00
chichuan 1d4c51a4d3 feat(drive): add local/Drive folder status, pull, push and sync 2026-08-12 11:37:47 +08:00
之桐 bdf3048773 feat(drive): add file comment commands 2026-08-12 11:29:21 +08:00
Dennis ae309b5846 feat(drive): harden and expand shortcut workflows 2026-08-12 11:11:46 +08:00
如椽 08ee5dc573 fix: emit JSON receipts for exports and downloads 2026-08-10 17:21:54 +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 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
玉澜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
玉澜andCursor 22a20355e7 fix(drive): declare multipart download CLI flags in Schema
Main brought --part-size/--parallel/--no-resume onto drive download leaves
without ParamDecl or mapping exclusions, so Catalog fell back to
flag_name_inference and failed the unpinned-adapter mapping audit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 14:24:54 +08:00
玉澜andCursor ee286abb05 Merge origin/main into agent/cmdcore-phase2
Bring in v1.0.56 release line (multipart Drive downloads, chat download-media JSON fix, event-bus socket fix) while keeping PR #830's runtime Schema assembly and retired schema pin/catalog/bindings paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 14:14:50 +08:00
半圭 837a96fe3d fix: show friendly message on Ctrl+C instead of internal error JSON
When user interrupts multipart download with Ctrl+C, display a helpful
message indicating checkpoint is saved and download can be resumed,
instead of returning an internal error with context.Canceled.
2026-08-04 08:58:17 +08:00
半圭 fdcd44f9e3 fix: handle Ctrl+C (SIGINT) gracefully during drive download
- Replace context.Background() with cmd.Context() in download and
  download-version commands so SIGINT propagates to download goroutines
- Enables graceful interruption of multipart downloads via Ctrl+C
2026-08-03 23:21:59 +08:00
半圭 34c0c86a59 feat: center protocol upload/download refactoring with multipart download
- Add multipart download engine (drive_transfer.go) with Range probe,
  resume support, and credential auto-refresh on 401/403
- Add --part-size, --parallel, --no-resume flags to drive download and
  download-version commands
- Replace httpGetFile with driveTransferDownload for chunked parallel
  downloads in download and download-version commands
- Replace uploadToDrive credential parsing with driveUploadPut
  (transparent header pass-through, retry on 401/403)
- Add typed httpStatusError for non-2xx HTTP responses in doc.go
- Add comprehensive unit tests (32 cases) for drive_transfer
- Update drive reference documentation with multipart download behavior
- Add E2E test for multipart download (auto-test/, gitignored)

CR: 28984991
2026-08-03 23:21:59 +08:00
玉澜andCursor b3adfa8d26 feat(schema): require Contract.Identity aligned with reviewed registry
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 12:10:54 +08:00
玉澜andCursor 2a1fe47c50 refactor(schema): retire JSON Catalog delivery for runtime assembly
Move schema authority to declare-time ParamDecl/ContractFinal and
ResolveSchemaBuild so CI/runtime assemble instead of shipping JSON
exclusions/meta-index as delivery sources.

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

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

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

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

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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 14:13:46 +08:00
玉澜 461455b4fa fix: harden destructive dry-run validation 2026-07-31 01:19:51 +08:00
玉澜 9567cfd3d8 fix(review): align wukong port with upstream behavior and PR #621 review findings
Must-fix: drive permission apply now gates on confirmDangerousAction and
declares confirmation=user_required, matching its help-text promise.

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

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

Hardened the skill static audit to scan fenced code blocks and reject
unknown subcommands on group commands, fixing the stale aitable/drive
doc examples it exposed. Added CHANGELOG entry and coverage tests for
all changed statements plus previously untested ported commands.
2026-07-29 15:34:59 +08:00
玉澜 68e1a78810 feat(cli): port drive list --depth and doc style, register all new commands in Schema
- Port drive list --depth N BFS recursive listing (pan + workspace routes,
  rate-limit requeue, SIGINT partial emit, --pattern/--quiet)
- Port doc style cover set/clear, background set/clear, get with local
  image validation and attachment-upload subflow
- Register all 26 newly ported commands in schema_command_registry with
  reviewed metadata/selection hints instead of exclusions (813->839 tools)
- Review fixes: drive list --node usage text no longer implies required
  in agent schema; remove broken formula-verify --exit-on-error; error on
  --range without --sheet-id; portable stdin read; drop local --yes
  shadowing root -y on drive revert/transfer-owner; use
  confirmDangerousAction for non-delete confirms; explicit
  recursiveChange=false now transmitted; sheet version revert and
  comment delete moved into sheet confirmationGuards registry
2026-07-29 11:57:57 +08:00
玉澜 d28a50c0f4 fix(cli): resolve schema parameter mapping for drive download
Remove --version flag from drive download (polymorphic tool dispatch
incompatible with schema validation). Regenerate schema catalog and
add new commands to schema_command_exclusions.json.
2026-07-29 10:12:15 +08:00
玉澜 a705c9de0b feat(cli): implement wukong-internal commands in open-source CLI
Port 19 command leaves from wukong internal CLI:
- drive star add/remove/list (文档收藏)
- drive cover (节点封面)
- drive revert (文件版本回滚)
- drive list --versions / download --version (文件历史版本)
- drive permission transfer-owner/apply-info/apply
- sheet version save/list/revert
- sheet formula-verify
- sheet comment list/create/reply/update/delete
- chat group user-settings query/set

Restore corresponding skill docs and register commands in schema
exclusions pending Schema review.
2026-07-29 01:06:36 +08:00
Dennis 69d813afef fix: address contract review feedback 2026-07-28 12:09:57 +08:00
Dennis ec7fdb0f0d fix: harden dws contract edge cases 2026-07-28 11:36:44 +08:00
修雨 f9443af460 fix: preserve schema compatibility for synced capabilities 2026-07-23 17:43:13 +08:00
修雨 876afcddfb feat: sync Wukong capabilities through 3306c3307 2026-07-23 17:43:12 +08:00
玉澜 125f0c8fe0 Merge remote-tracking branch 'upstream/main' into feat/schema-on-main
# Conflicts:
#	test/scripts/package_script_test.go
2026-07-13 21:09:27 +08:00
玉澜 55afc656ac fix(schema): validate agent example delivery 2026-07-13 20:33:56 +08:00
Aemeathand张卓澎 657d2c25e3 feat: sync open product command capabilities (#608)
Co-authored-by: 张卓澎 <zhuopeng.zzp@alibaba-inc.com>
2026-07-13 17:21:47 +08:00
修雨 a32d7985e6 refactor: switch to static endpoint delivery runtime 2026-07-07 19:07:03 +08:00
修雨 4c5f1faeb1 feat: align open CLI with dws-wukong via cedar discovery version code (#509)
Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
2026-06-26 21:49:38 +08:00
539d10f80f feat: 合并 pre-mcp-discovery 分支能力到 main(服务发现保持生产) (#391)
将 test/pre-mcp-discovery 分支上多位同学的能力改动整体合入 main,
服务发现端点保持生产 https://mcp.dingtalk.com,未携带分支上的预发改动。

主要内容:
- 修雨域(chat / report / todo / contact / mail)对齐 wukong 基线 (#355)
- DWS 内容生产能力增强 (#387)、文档导出与 OA 对齐 (#362 #388 #390)
- envelope 注册 cli.Aliases 为 cobra aliases、命令结构与 JSON 解析优化
- skills 多包同步 wukong 对齐内容(attendance/calendar/minutes/oa/sheet 等)
- product/oa 进一步对齐 wukong

排除项:未携带 566b4e1(test: point service discovery to pre-mcp.dingtalk.com);
以下 4 个常量保持生产:skill_command.go / auth/endpoints.go / cli/loader.go / market/registry.go。

Co-authored-by: john <32427341+wqyenjoy@users.noreply.github.com>
Co-authored-by: wxianfeng <wang.fl1429@gmail.com>
2026-06-02 21:04:12 +08:00
Ari 45cb237f74 fix(drive): unblock OSS upload + improve AI-agent discoverability (#347)
* fix(drive): drop client-side Content-Type fallback on presigned OSS PUT

The drive helper used to set req.Header["Content-Type"] = fallbackMIME
whenever the prepare_upload response returned an empty headers map.
DingTalk drive uses OSS presigned URLs whose StringToSign is computed
against an empty Content-Type at signing time, so any client-side
Content-Type makes the signature OSS recomputes at PUT time differ
from the server's presignature → HTTP 403 SignatureDoesNotMatch.

This broke every `dws drive upload` for any file whose mime detects
to a non-empty value (image/png, application/pdf, etc.), which is
basically everything in practice.

Reproduction (against DingTalk drive):
  dws drive upload --file any.png
  → [1/3] 获取上传凭证 any.png (X 字节, image/png)...
  → [2/3] 上传文件到 OSS...
  → OSS 上传失败 HTTP 403: SignatureDoesNotMatch

Fix: trust the server's headers map as authoritative. Empty map means
"no client-side headers needed" — do not infer and add Content-Type.
Removes the hasContentType / fallbackMIME path entirely; httpPutDriveFile
signature loses the fallbackMIME parameter.

Manual verification:
  curl -X PUT -H "Content-Type:" --data-binary @file <same-presigned-url>
  → HTTP 200 (proves the only difference was the client-side Content-Type)

Note: aitable.go's upload-file helper deliberately keeps its
Set("Content-Type", mimeType) call because its OSS endpoint uses a
different signing mode (server includes the client-declared mime in
its signature computation, verified by running aitable upload-file
across 12 file types — all succeed). The two helpers must not be
unified without re-validating both endpoints.

Tests:
  - TestHttpPutDriveFile_NoContentTypeWhenServerHeadersEmpty: guards
    that an empty server headers map results in no Content-Type on PUT.
  - TestHttpPutDriveFile_PassthroughServerHeaders: guards that server
    headers (Content-Type, x-oss-*) are forwarded verbatim.

* feat(aitable): unhide attachment upload-file + clarify prepare-only command

AI agents that discover commands only via --help (e.g. Lobster, generic
LLM agents) currently hit a dead end when trying to upload an attachment
to an AITable:

  - `dws aitable attachment` exposes only `upload` (prepare-only),
    which returns uploadUrl + fileToken but does not actually upload.
  - The real one-shot command `attachment upload-file` (which performs
    prepare + HTTP PUT + return fileToken automatically) is marked
    Hidden:true, so it is invisible in --help output.

Agents that don't read skills/references/products/aitable.md therefore
get stuck after step 1 — they call `upload`, receive an uploadUrl they
have no idea how to consume, attempt various wrong things (e.g. write
the uploadUrl into the record's attachment field as if it were a token),
fail, and fall back to "please use the UI to upload" messages, which
makes dws look broken even though the capability is fully implemented.

This is the same UX gap that lark-cli avoids with its highly visible
`base +record-upload-attachment` command — discoverability via --help
is the difference between "works for any agent" and "only works if the
agent reads our skill docs".

Changes:

  - newAITableUploadFileCommand: remove Hidden:true so the command
    appears in `dws aitable attachment --help`. Tighten Short to
    explicitly mention the 3 steps it bundles. Long now also calls
    out the prepare-only sibling and recommends this command as the
    default for AI agents.

  - newAITableAttachmentUploadCommand (prepare-only): add a Long
    description that explicitly states this command is only step 1
    of a 3-step flow, lists what an agent must do after (HTTP PUT,
    then write fileToken into record attachment field with the exact
    [{"fileToken":"ft_xxx"}] shape), and points to upload-file as
    the recommended one-shot alternative. Updated Short to flag that
    no file is uploaded by this command.

  - aitable_upload_file_test: add TestAITableUploadFileCommandIsDiscoverable
    to guard against re-introducing Hidden:true. The test includes a
    rationale comment explaining the agent-discoverability gap.

The drive Content-Type fix in the preceding commit and this aitable
discoverability fix together restore end-to-end attachment upload
functionality for both human operators and AI agents that only read
--help.

* feat(root): show 'dws upgrade' guidance in dws --help when commands are missing

AI agents (and users) reading `dws --help` see only the discovered MCP
service list and utility command list, with no hint about what to do if
none of the listed commands fit their task. The natural failure mode is
to give up or hack around — but in many cases the right action is just
`dws upgrade`, because new capabilities and bugfixes ship continuously
and a missing command is usually a stale binary issue.

Two changes restore visibility of the root command's Long description so
this guidance can be surfaced:

  - internal/app/root.go: set root.Long to a one-paragraph hint —
    "if you hit a missing command, an error, or cannot complete the
    task, try `dws upgrade` first; both the DingTalk OpenAPI surface
    and dws CLI evolve continuously."

  - internal/app/root_help.go: the custom SetHelpFunc that renders the
    root help (renderRootHelp) replaces cobra's default template, which
    had been silently dropping root.Long from --help output. Restore
    rendering by appending root.Long (when non-empty) after the command
    list, separated by a blank line. This matches cobra's default
    behavior for the Long field while preserving the custom services /
    utilities sections renderRootHelp introduced.

  - internal/app/visibility_test.go: new TestRenderRootHelpIncludesLong
    guards against re-introducing the regression. Uses a sentinel Long
    string and asserts renderRootHelp output contains it verbatim. If a
    future change rewrites the help renderer without preserving Long
    rendering, this test fails immediately and reminds the author the
    upgrade hint must stay visible.

Verified locally:
  $ ./dws --help | tail -3
  Use "dws <service> --help" for more information about a discovered MCP
  service or "dws <command> --help" for utility commands.

  提示: 如果遇到能力缺失、命令报错、新功能未注册、或无法完成任务,
  请先用 'dws upgrade' 升级到最新版本后再试. 钉钉 OpenAPI 和 dws CLI
  持续迭代, 新能力和 bugfix 会先在新版本上线.
2026-05-24 15:55:08 +08:00
修雨andClaude Opus 4.7 c7d8ddf98d feat: align drive with wukong — helper (upload) + skill docs (doc/sheet dingpan URL) (#335)
* feat(helpers): drive upload 三步合成胶水(list-spaces / delete 由 envelope 接管)

业务背景:开源版 dws drive 仅有来自服务发现 envelope 的 6 个工具,悟空版多
3 个能力 — list-spaces / upload / delete。这 3 个里只有 upload 是真正的客户端
胶水(PUT 文件二进制到 OSS 的中间 HTTP 步),其它两个完全可以通过 envelope
toolOverrides 表达,无需 Go 代码。

按 envelope 优先原则拆分:

1. envelope/pre-discovery.pre.json (单独提供 / 不在本 commit)
   - drive.toolOverrides.list_spaces  →  cliName: list-spaces
   - drive.toolOverrides.delete_document → cliName: delete, serverOverride: doc,
                                             isSensitive: true
2. internal/helpers/drive.go (本 commit)
   - 仅注册 upload 一个 leaf
   - 原因:envelope.PipelineStep 只支持 type:"call"/"download",没有
     type:"upload"。客户端流式 PUT 本地文件到 OSS 签名 URL(含 per-URL
     headers)当前 envelope schema 表达不了,故保留为 helper。

internal/helpers/drive.go (新增 ~290 行)

  - upload — 三步合成:
    1) drive get_upload_info → resourceUrl + uploadId + headers
    2) HTTP PUT 文件二进制 → OSS(沿用 aitable upload-file 模式,
       10min timeout,跟随 per-URL headers)
    3) drive commit_upload → 提交入库
    --dry-run 输出三步 invocation 预览不实际请求。
    parseDriveUploadInfo 兼容 content/result 包裹层与 resourceUrls 数组
    及 flat resourceUrl/uploadUrl fallback,与悟空侧 parseDriveUploadInfo
    等价。
    validateDriveParentID 拒纯数字 dentryId(防混淆 chat 链路 dentryId 与
    drive 链路 dentryUuid)。

注入路径:helpers.RegisterPublic → pickCommands.MergeHardcodedLeaves(dynamic,
helper)(dynamic 赢冲突、helper 填空白)。本 helper root.Use="drive" 不覆盖
dynamic 的 6 个 leaf,仅追加 upload。

dry-run 实测:

  $ dws drive upload --file /etc/hosts --dry-run --format json
  → step_1_get_upload_info + step_2_http_put_oss + step_3_commit_upload 三步预览

测试结果:
  ok  internal/helpers (含全部既有 TestSendByBot* / TestAtomicWrite* /
                       TestValidate* 等 23 个测试)
  ok  test/integration/extensions
  test/cli_compat pre-existing failures 与本改动无关(在干净 origin/main HEAD
  同样 fail,fixture 缺失等)。

未来工作 (follow-up):
  - 等 envelope 文件 commit 到主线后,list-spaces / delete 自动从 envelope
    生成 cobra command,无需此 helper 操心
  - 如果 envelope schema 加入 type:"upload" pipeline 步类型,本 helper 整个
    可以删除,由 envelope pipeline 表达完整三步

关联:
  - 悟空源头实现:dws-wukong/wukong/products/drive.go
    (runDriveUpload / parseDriveUploadInfo / httpPutFile + delete 路由)
  - 配套 skill 文档 PR:#333 (doc 端钉盘 URL 识别)
  - envelope 改动: drive.toolOverrides 增加 list_spaces + delete_document,
    见对应的 envelope PR / commit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(skill): doc --node 接受钉盘 document/edit|preview?dentryKey URL + 新增 url-patterns 分流

业务背景:hho 业务侧用户在悟空版上已用钉盘 URL(alidocs.dingtalk.com/document/edit?dentryKey=...)
直接喂给 Agent,但开源版 skill 文档未覆盖这种格式,Agent 不知道整段 URL 应原样传给 --node,
经常误把 dentryKey 当成裸 nodeId 调用导致失败。

本 PR 把悟空 dws-wukong PR #82157526 的 skill 文档增量移植到开源主线,保持 doc/sheet
两端入口的 URL 识别策略对齐悟空:

skills/SKILL.md
- 核心流程新增 Step 0「URL 预检」:含 alidocs URL 必须先读 url-patterns.md 分流,再选择产品
- 详细参考目录加入 url-patterns.md 引用

skills/references/products/doc.md
- dws doc info/read 的 Example 各 +2 行钉盘 URL 示例
- 「URL 识别与 DOC_ID 提取」支持的 URL 格式表新增 document/edit|preview?dentryKey={key} 一行
- 提取规则拆成 3 条,禁止 Agent 自行提取 dentryKey 当裸 nodeId
- 「nodeId 双格式说明」升级为「nodeId 多格式说明」,给出 4 种 --node 输入等价示例

skills/references/url-patterns.md (新建)
- 沉淀 alidocs URL 5 类分流决策:/i/p/ 短链 / /i/nodes/ 节点 / /spreadsheetv2/ 直链 /
  /document/edit|preview?dentryKey 链接 / 其他
- 含 i/nodes/ 类型探测流程(doc info 探 contentType/extension/nodeType 后再选产品)
- 含分享短链 read_url 兜底处理 + 动态渲染失败时的标准答复

skill 命名形态兼容:
- 当前 main 上 skills/ 是扁平结构,改动直接落在 skills/* 路径
- 单点(mono)/ 多点(multi)拆分形态在 feature/skill-setup 分支,待该分支合主线时
  按相同语义同步到 skills/mono/* 和 skills/multi/dingtalk-{doc,sheet}/* 两套

不涉及 Go 代码变更,纯 skill 文档;钉盘 URL 解析逻辑由 MCP 服务端承担。

关联悟空 PR:dws-wukong commit 565c63f8 / 712da968(#82157526)
对应 wukong skill 包:dingtalk-workspace/{SKILL.md,references/products/doc.md,references/url-patterns.md}

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:38:00 +08:00