diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 691687b5..4cdee739 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -298,9 +298,6 @@ jobs: - name: Policy run: make policy - - name: Generated Drift - run: ./scripts/policy/check-generated-drift.sh - interface-integrity: name: Interface Integrity runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index fc449980..528b6722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ### Added +- **Sheet imports and Aitable workflow writes** (#624) — adds `dws sheet import` / `sheet import create` for converting local xlsx/xls files into new online sheets, `sheet import get` for polling import tasks, and `dws aitable workflow create/update` for applying validated `workflow-dsl/v1` definitions, with matching reviewed Agent Schema and bundled Skill guidance. - **Official multi-platform Homebrew channel** — stable `Formula/dingtalk-workspace-cli.rb` and keg-only `Formula/dingtalk-workspace-cli-beta.rb` live in this repository and select signed macOS Intel/Apple Silicon or Linux amd64/arm64 artifacts at install time. Stable and beta releases open isolated Formula update PRs after final artifact signing, so beta never replaces the stable Formula. Agent Skills stay under `pkgshare` without mutating the user's home directory, and both tracks are covered by the six-channel post-release verifier. ## [1.0.52] - 2026-07-14 diff --git a/Makefile b/Makefile index 4658b361..17e2608c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ GO ?= go +DWS_POLICY_TMPDIR ?= $(CURDIR)/.worktrees/policy-tmp +POLICY_GOTMPDIR ?= $(DWS_POLICY_TMPDIR)/go +POLICY_ENV = DWS_POLICY_TMPDIR="$(DWS_POLICY_TMPDIR)" GOTMPDIR="$(POLICY_GOTMPDIR)" .PHONY: all help build rebuild test lint 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 cli-smoke mock-mcp-smoke test-schema-agent-examples generate-schema generate-schema-agent-metadata generate-schema-catalog package release publish-homebrew-formula setup-hooks @@ -10,7 +13,7 @@ help: @printf " make test - Run the Go test suite\n" @printf " make lint - Run formatting checks and golangci-lint when available\n" @printf " make fmt - Format Go source files\n" - @printf " make policy - Run open-source asset and Schema registry checks\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= - Check the Git-owned PR merge-base\n" @printf " make coverage-gate BASE_REF= - Enforce overall non-regression and changed-code coverage\n" @@ -45,13 +48,14 @@ fmt: @find cmd internal test scripts/policy -name '*.go' -print0 2>/dev/null | xargs -0r gofmt -w policy: - @./scripts/policy/check-open-source-assets.sh - @./scripts/policy/check-schema-command-registry.sh - @./scripts/policy/check-command-surface.sh --strict - @./scripts/policy/check-generated-drift.sh - @./scripts/policy/check-schema-catalog.sh - @./scripts/policy/check-schema-binary.sh - @$(MAKE) test-schema-agent-examples + @mkdir -p "$(POLICY_GOTMPDIR)" + @$(POLICY_ENV) ./scripts/policy/check-open-source-assets.sh + @$(POLICY_ENV) ./scripts/policy/check-schema-command-registry.sh + @$(POLICY_ENV) ./scripts/policy/check-command-surface.sh --strict + @$(POLICY_ENV) ./scripts/policy/check-generated-drift.sh + @$(POLICY_ENV) ./scripts/policy/check-schema-catalog.sh + @$(POLICY_ENV) ./scripts/policy/check-schema-binary.sh + @$(POLICY_ENV) $(MAKE) test-schema-agent-examples edition-test: $(GO) test -v -count=1 ./pkg/editiontest/... diff --git a/internal/cli/schema_agent_metadata/aitable.json b/internal/cli/schema_agent_metadata/aitable.json index c7d8899c..c765c9bc 100644 --- a/internal/cli/schema_agent_metadata/aitable.json +++ b/internal/cli/schema_agent_metadata/aitable.json @@ -29017,6 +29017,288 @@ "用户要移动字段/调整列顺序时必须走本命令" ] }, + "aitable workflow create": { + "agent_summary": "创建并发布 AI 表格自动化工作流。", + "agent_summary_source": "dws-agent-selection/aitable", + "availability": "available", + "avoid_when": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws aitable workflow create --base-id \u003cBASE_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "field_provenance": { + "agent_summary": { + "value": "创建并发布 AI 表格自动化工作流。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "candidates": [ + { + "value": "创建并发布 AI 表格自动化工作流。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + } + ] + }, + "avoid_when": { + "value": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "candidates": [ + { + "value": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + }, + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + }, + { + "value": "write", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "examples": { + "value": [ + "dws aitable workflow create --base-id \u003cBASE_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "candidates": [ + { + "value": [ + "dws aitable workflow create --base-id \u003cBASE_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + }, + { + "value": "unknown", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional." + }, + { + "value": "medium", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "use_when": { + "value": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "candidates": [ + { + "value": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "cobra-help:dws aitable workflow create --help", + "internal/cli/schema_command_registry.json#aitable.workflow_create", + "internal/cli/schema_hints/metadata/aitable.json", + "internal/cli/schema_hints/selection/aitable.json", + "skills/mono/references/products/aitable/aitable-workflow.md" + ], + "use_when": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ] + }, "aitable workflow disable": { "agent_summary": "禁用工作流(高危,需确认;status 变 STOP)。", "agent_summary_source": "dws-agent-selection/aitable", @@ -30072,6 +30354,288 @@ "use_when": [ "查看有哪些工作流及 status/flowId 时" ] + }, + "aitable workflow update": { + "agent_summary": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。", + "agent_summary_source": "dws-agent-selection/aitable", + "availability": "available", + "avoid_when": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws aitable workflow update --base-id \u003cBASE_ID\u003e --workflow-id \u003cFLOW_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "field_provenance": { + "agent_summary": { + "value": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "candidates": [ + { + "value": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + } + ] + }, + "avoid_when": { + "value": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "candidates": [ + { + "value": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + }, + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + }, + { + "value": "write", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "examples": { + "value": [ + "dws aitable workflow update --base-id \u003cBASE_ID\u003e --workflow-id \u003cFLOW_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "candidates": [ + { + "value": [ + "dws aitable workflow update --base-id \u003cBASE_ID\u003e --workflow-id \u003cFLOW_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。" + } + ] + }, + "idempotency": { + "value": "unknown", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "unknown", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + }, + { + "value": "unknown", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + } + ] + }, + "interface_reason": { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional." + }, + { + "value": "medium", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "use_when": { + "value": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "candidates": [ + { + "value": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ], + "source": "internal/cli/schema_hints/selection/aitable.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。" + } + ] + } + }, + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "cobra-help:dws aitable workflow update --help", + "internal/cli/schema_command_registry.json#aitable.workflow_update", + "internal/cli/schema_hints/metadata/aitable.json", + "internal/cli/schema_hints/selection/aitable.json", + "skills/mono/references/products/aitable/aitable-workflow.md" + ], + "use_when": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ] } } } diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index b442d3e2..ef643a9b 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,17 +1,17 @@ { "version": 1, - "source_hash": "sha256:84ae56ee689d80254b9f590733f7bcaa77f9c0f94ac9e0e5e6762956b1b102a2", - "surface_hash": "sha256:e858be68701d256818eccd085a6f7f97655835b9c7cbbce6275ed930c2436dc3", + "source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1", + "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", "coverage": { "surface_products": 22, "products_with_metadata": 22, - "surface_tools": 564, - "tools_with_metadata": 564, - "tools_with_agent_summary": 564, - "tools_with_use_when": 564, - "tools_with_avoid_when": 564, - "tools_with_examples": 564, - "tools_with_interface_mode": 564, + "surface_tools": 568, + "tools_with_metadata": 568, + "tools_with_agent_summary": 568, + "tools_with_use_when": 568, + "tools_with_avoid_when": 568, + "tools_with_examples": 568, + "tools_with_interface_mode": 568, "unmatched_skill_tools": 125, "unreviewed_skill_tools": 7 }, @@ -1371,20 +1371,21 @@ ] }, "sheet": { - "agent_summary": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", + "agent_summary": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", "agent_summary_source": "dws-agent-selection/sheet", "avoid_when": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ], "field_provenance": { "agent_summary": { - "value": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", + "value": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", "source": "internal/cli/schema_hints/selection/sheet.json", "precedence": "reviewed_explicit", "resolution": "highest_precedence", "candidates": [ { - "value": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", + "value": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", "source": "internal/cli/schema_hints/selection/sheet.json", "precedence": "reviewed_explicit", "selected": true @@ -1393,7 +1394,8 @@ }, "avoid_when": { "value": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ], "source": "internal/cli/schema_hints/selection/sheet.json", "precedence": "reviewed_explicit", @@ -1401,7 +1403,8 @@ "candidates": [ { "value": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ], "source": "internal/cli/schema_hints/selection/sheet.json", "precedence": "reviewed_explicit", @@ -1411,7 +1414,8 @@ }, "use_when": { "value": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ], "source": "internal/cli/schema_hints/selection/sheet.json", "precedence": "reviewed_explicit", @@ -1419,7 +1423,8 @@ "candidates": [ { "value": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ], "source": "internal/cli/schema_hints/selection/sheet.json", "precedence": "reviewed_explicit", @@ -1440,7 +1445,8 @@ "skills/mono/references/products/sheet.md" ], "use_when": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ] }, "todo": { diff --git a/internal/cli/schema_agent_metadata/sheet.json b/internal/cli/schema_agent_metadata/sheet.json index fc893e57..c9285f63 100644 --- a/internal/cli/schema_agent_metadata/sheet.json +++ b/internal/cli/schema_agent_metadata/sheet.json @@ -11251,6 +11251,581 @@ "需要关闭指定工作表的网格线显示时" ] }, + "sheet import create": { + "agent_summary": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ], + "confirmation": "not_required", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet import create --file ./report.xlsx --folder-token \u003cFOLDER_TOKEN\u003e --format json", + "dws sheet import create --file ./data.xls --workspace \u003cWORKSPACE_ID\u003e --name \"月度报表\" --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "candidates": [ + { + "value": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + } + ] + }, + "avoid_when": { + "value": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "candidates": [ + { + "value": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + }, + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "effect": { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "write", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + }, + { + "value": "write", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "examples": { + "value": [ + "dws sheet import create --file ./report.xlsx --folder-token \u003cFOLDER_TOKEN\u003e --format json", + "dws sheet import create --file ./data.xls --workspace \u003cWORKSPACE_ID\u003e --name \"月度报表\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "candidates": [ + { + "value": [ + "dws sheet import create --file ./report.xlsx --folder-token \u003cFOLDER_TOKEN\u003e --format json", + "dws sheet import create --file ./data.xls --workspace \u003cWORKSPACE_ID\u003e --name \"月度报表\" --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。" + } + ] + }, + "idempotency": { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "non_idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + }, + { + "value": "unknown", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + } + ] + }, + "interface_reason": { + "value": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。" + } + ] + }, + "risk": { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "candidates": [ + { + "value": "medium", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + }, + { + "value": "medium", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "use_when": { + "value": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "candidates": [ + { + "value": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。" + } + ] + } + }, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。", + "reviewed": true, + "risk": "medium", + "source_refs": [ + "cobra-help:dws sheet import create --help", + "internal/cli/schema_command_registry.json", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "live-mcp:none (Cobra+Skill; composite/unpinned)", + "skills/mono/references/products/sheet/sheet-import.md" + ], + "use_when": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ] + }, + "sheet import get": { + "agent_summary": "根据 taskId 查询表格导入任务结果。", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ], + "confirmation": "not_required", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet import get --task-id \u003cTASK_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "value": "根据 taskId 查询表格导入任务结果。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "candidates": [ + { + "value": "根据 taskId 查询表格导入任务结果。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。" + } + ] + }, + "availability": { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "available", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + } + ] + }, + "avoid_when": { + "value": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "candidates": [ + { + "value": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。" + } + ] + }, + "confirmation": { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "not_required", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + }, + { + "value": "not_required", + "source": "risk-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "effect": { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "read", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + }, + { + "value": "write", + "source": "command-verb", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "examples": { + "value": [ + "dws sheet import get --task-id \u003cTASK_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "candidates": [ + { + "value": [ + "dws sheet import get --task-id \u003cTASK_ID\u003e --format json" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。" + } + ] + }, + "idempotency": { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "idempotent", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + }, + { + "value": "unknown", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "interface_mode": { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "composite", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + } + ] + }, + "interface_reason": { + "value": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + } + ] + }, + "interface_ref": { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "candidates": [ + { + "value": null, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "final interface mode composite forbids a direct MCP interface_ref" + } + ] + }, + "reviewed": { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + }, + { + "value": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": false, + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。" + } + ] + }, + "risk": { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "candidates": [ + { + "value": "low", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + }, + { + "value": "medium", + "source": "effect-default", + "precedence": "inference_or_default", + "selected": false + } + ] + }, + "use_when": { + "value": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "candidates": [ + { + "value": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ], + "source": "internal/cli/schema_hints/selection/sheet.json", + "precedence": "reviewed_explicit", + "selected": true, + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。" + } + ] + } + }, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。", + "reviewed": true, + "risk": "low", + "source_refs": [ + "cobra-help:dws sheet import get --help", + "internal/cli/schema_command_registry.json", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "live-mcp:none (Cobra+Skill; composite/unpinned)", + "skills/mono/references/products/sheet/sheet-import.md" + ], + "use_when": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ] + }, "sheet info": { "agent_summary": "获取指定工作表详情(行列数、非空范围、合并区等)。", "agent_summary_source": "dws-agent-selection/sheet", diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 349da66f..00dbff43 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,11 +1,11 @@ { "version": 1, - "source_hash": "sha256:84ae56ee689d80254b9f590733f7bcaa77f9c0f94ac9e0e5e6762956b1b102a2", - "surface_hash": "sha256:e858be68701d256818eccd085a6f7f97655835b9c7cbbce6275ed930c2436dc3", - "source_files": 149, + "source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1", + "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", + "source_files": 150, "hint_files": 46, "hint_products": 39, - "hint_tools": 1280, + "hint_tools": 1288, "interface_metadata": { "source": "mcp-tools-list+cli-registry", "revision": "4574f7022c32cf4c033e9b7b4156e2fec815fed8", @@ -29,13 +29,13 @@ "coverage": { "surface_products": 22, "products_with_metadata": 22, - "surface_tools": 564, - "tools_with_metadata": 564, - "tools_with_agent_summary": 564, - "tools_with_use_when": 564, - "tools_with_avoid_when": 564, - "tools_with_examples": 564, - "tools_with_interface_mode": 564, + "surface_tools": 568, + "tools_with_metadata": 568, + "tools_with_agent_summary": 568, + "tools_with_use_when": 568, + "tools_with_avoid_when": 568, + "tools_with_examples": 568, + "tools_with_interface_mode": 568, "unmatched_skill_tools": 125, "unreviewed_skill_tools": 7 }, @@ -3877,7 +3877,7 @@ { "tool_path": "sheet range", "source": "skills/mono/references/products/sheet.md", - "line": 65, + "line": 66, "candidates": [ "sheet range batch-clear", "sheet range batch-set-style", @@ -3891,7 +3891,7 @@ { "tool_path": "sheet filter-view", "source": "skills/mono/references/products/sheet.md", - "line": 66, + "line": 67, "candidates": [ "sheet filter-view create", "sheet filter-view delete", diff --git a/internal/cli/schema_catalog.json b/internal/cli/schema_catalog.json index 65ddff6d..4eeff61a 100644 --- a/internal/cli/schema_catalog.json +++ b/internal/cli/schema_catalog.json @@ -1,17 +1,17 @@ { "version": 1, - "source_hash": "sha256:0ff538ed022fb3d54947a8b849600711106005086f119ff27dc93ad116ec319e", - "surface_hash": "sha256:e858be68701d256818eccd085a6f7f97655835b9c7cbbce6275ed930c2436dc3", + "source_hash": "sha256:9a907efbcea3339bcb3881aad578e77b15f4a3f4a8b53efe4c2791db894bb41a", + "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", "catalog": { "agent_metadata": { "products_with_metadata": 22, "source": "embedded-skill-metadata", - "source_hash": "sha256:84ae56ee689d80254b9f590733f7bcaa77f9c0f94ac9e0e5e6762956b1b102a2", - "surface_hash": "sha256:e858be68701d256818eccd085a6f7f97655835b9c7cbbce6275ed930c2436dc3", + "source_hash": "sha256:53567141622d90e02ce50fe3bc1733357a25b9860a0795eb396d9361f43c41d1", + "surface_hash": "sha256:69e1f9fcd3768d49a2a197c40fb26c0bc07385e868753210761c3ea7f676eadb", "surface_products": 22, - "surface_tools": 564, - "tools_with_agent_summary": 564, - "tools_with_metadata": 564, + "surface_tools": 568, + "tools_with_agent_summary": 568, + "tools_with_metadata": 568, "unmatched_skill_tools": 125, "version": 1 }, @@ -288,7 +288,7 @@ "id": "aitable", "name": "AI 表格操作", "runtime": true, - "tool_count": 114, + "tool_count": 116, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -3637,6 +3637,33 @@ "用户要移动字段/调整列顺序时必须走本命令" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "创建并发布 AI 表格自动化工作流。", + "agent_summary_source": "dws-agent-selection/aitable", + "availability": "available", + "avoid_when": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ], + "canonical_path": "aitable.workflow_create", + "cli_name": "create", + "cli_path": "aitable workflow create", + "confirmation": "not_required", + "description": "在指定 Base 中创建并发布自动化工作流。\n--dsl 必须是完整的 workflow-dsl/v1 JSON 对象;涉及数据表、字段或视图的节点应使用真实的 sheetId / fieldId / viewId。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。创建属于非幂等操作,CLI 不会自动重试。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。", + "effect": "write", + "group": "workflow", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "workflow_create", + "primary_cli_path": "aitable workflow create", + "reviewed": true, + "risk": "medium", + "title": "创建并发布自动化工作流", + "use_when": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "禁用工作流(高危,需确认;status 变 STOP)。", @@ -3680,7 +3707,7 @@ "cli_name": "enable", "cli_path": "aitable workflow enable", "confirmation": "not_required", - "description": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n当前不支持通过 CLI 新建工作流,请在 AI 表格 Web 端配置好后用 workflow list 拿到 workflowId 再启停。", + "description": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n可用 workflow create 创建工作流,或用 workflow list 获取已有 workflowId。", "effect": "write", "group": "workflow", "idempotency": "unknown", @@ -3760,6 +3787,33 @@ "use_when": [ "查看有哪些工作流及 status/flowId 时" ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。", + "agent_summary_source": "dws-agent-selection/aitable", + "availability": "available", + "avoid_when": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ], + "canonical_path": "aitable.workflow_update", + "cli_name": "update", + "cli_path": "aitable workflow update", + "confirmation": "not_required", + "description": "在指定 Base 中更新并发布已有自动化工作流。\n建议先用 workflow get 留底当前详情;--dsl 必须是完整的 workflow-dsl/v1 JSON 对象,而不是局部 patch。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。更新会发布传入的目标 DSL;请提供完整、可独立校验的 workflow-dsl/v1 对象。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。", + "effect": "write", + "group": "workflow", + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "name": "workflow_update", + "primary_cli_path": "aitable workflow update", + "reviewed": true, + "risk": "medium", + "title": "更新并发布已有自动化工作流", + "use_when": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ] } ], "use_when": [ @@ -15396,10 +15450,11 @@ "skills/mono/references/intent-guide.md", "skills/mono/references/products/sheet.md" ], - "agent_summary": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", + "agent_summary": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", "agent_summary_source": "dws-agent-selection/sheet", "avoid_when": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ], "description": "钉钉表格管理", "field_provenance": { @@ -15409,13 +15464,13 @@ "precedence": "reviewed_explicit", "selected": true, "source": "internal/cli/schema_hints/selection/sheet.json", - "value": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。" + "value": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。" } ], "precedence": "reviewed_explicit", "resolution": "highest_precedence", "source": "internal/cli/schema_hints/selection/sheet.json", - "value": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。" + "value": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。" }, "avoid_when": { "candidates": [ @@ -15424,7 +15479,8 @@ "selected": true, "source": "internal/cli/schema_hints/selection/sheet.json", "value": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ] } ], @@ -15432,7 +15488,8 @@ "resolution": "highest_precedence", "source": "internal/cli/schema_hints/selection/sheet.json", "value": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ] }, "use_when": { @@ -15442,7 +15499,8 @@ "selected": true, "source": "internal/cli/schema_hints/selection/sheet.json", "value": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ] } ], @@ -15450,14 +15508,15 @@ "resolution": "highest_precedence", "source": "internal/cli/schema_hints/selection/sheet.json", "value": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ] } }, "id": "sheet", "name": "钉钉表格管理", "runtime": true, - "tool_count": 77, + "tool_count": 79, "tools": [ { "agent_metadata_source": "embedded-skill-metadata", @@ -16681,6 +16740,62 @@ "需要关闭指定工作表的网格线显示时" ] }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ], + "canonical_path": "sheet.import", + "cli_name": "create", + "cli_path": "sheet import create", + "confirmation": "not_required", + "description": "将本地表格文件导入为一个新的钉钉在线电子表格,与 dws sheet export(导出)对称。\n\n支持的文件格式 (按扩展名):\n xlsx, xls Microsoft Excel 表格\n\n文件大小限制: 20MB\n固定导入为电子表格类型;只新建文档,不操作已有表格。\n\nCLI 内部自动完成全部流程:\n 1. 创建导入会话(获取 OSS 上传凭证)\n 2. 上传文件到 OSS\n 3. 确认导入(触发格式转换)\n 4. 渐进式退避轮询等待完成(最多约 5 分钟)\n\n如果轮询超时仍未完成,会输出 taskId 供后续手动查询:\n dws sheet import get --task-id \u003ctaskId\u003e", + "effect": "write", + "group": "import", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。", + "name": "import", + "primary_cli_path": "sheet import create", + "reviewed": true, + "risk": "medium", + "title": "导入本地表格文件为在线电子表格 (xlsx / xls)", + "use_when": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ] + }, + { + "agent_metadata_source": "embedded-skill-metadata", + "agent_summary": "根据 taskId 查询表格导入任务结果。", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ], + "canonical_path": "sheet.import_get", + "cli_name": "get", + "cli_path": "sheet import get", + "confirmation": "not_required", + "description": "根据 taskId 查询表格导入任务的执行结果。\n通常不需要手动调用,dws sheet import 会自动完成轮询。\n仅在导入命令超时或中断后,用于手动查询任务状态。\n\n任务状态:\n processing 转换中\n completed 导入成功,返回 documentUrl\n failed 导入失败", + "effect": "read", + "group": "import", + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。", + "name": "import_get", + "primary_cli_path": "sheet import get", + "reviewed": true, + "risk": "low", + "title": "查询表格导入任务结果(手动兜底)", + "use_when": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ] + }, { "agent_metadata_source": "embedded-skill-metadata", "agent_summary": "获取指定工作表详情(行列数、非空范围、合并区等)。", @@ -17745,7 +17860,8 @@ } ], "use_when": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ] }, { @@ -18926,7 +19042,7 @@ } ], "source": "embedded-command-catalog", - "tool_count": 564 + "tool_count": 568 }, "tools": { "aisearch.enterprise_person_search": { @@ -101652,6 +101768,685 @@ "用户要移动字段/调整列顺序时必须走本命令" ] }, + "aitable.workflow_create": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "cobra-help:dws aitable workflow create --help", + "internal/cli/schema_command_registry.json#aitable.workflow_create", + "internal/cli/schema_hints/metadata/aitable.json", + "internal/cli/schema_hints/selection/aitable.json", + "skills/mono/references/products/aitable/aitable-workflow.md" + ], + "agent_summary": "创建并发布 AI 表格自动化工作流。", + "agent_summary_source": "dws-agent-selection/aitable", + "availability": "available", + "avoid_when": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ], + "canonical_path": "aitable.workflow_create", + "cli_name": "create", + "cli_path": "aitable workflow create", + "confirmation": "not_required", + "description": "在指定 Base 中创建并发布自动化工作流。\n--dsl 必须是完整的 workflow-dsl/v1 JSON 对象;涉及数据表、字段或视图的节点应使用真实的 sheetId / fieldId / viewId。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。创建属于非幂等操作,CLI 不会自动重试。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。", + "display": "AI 表格操作", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws aitable workflow create --base-id \u003cBASE_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": "创建并发布 AI 表格自动化工作流。" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": "创建并发布 AI 表格自动化工作流。" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "aitable workflow create", + "value": "aitable.workflow_create" + }, + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": false, + "source": "reviewed_manual_hint", + "source_ref": "aitable workflow create", + "value": "aitable.workflow_create" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "aitable workflow create", + "value": "aitable.workflow_create" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "risk-default", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "在指定 Base 中创建并发布自动化工作流。\n--dsl 必须是完整的 workflow-dsl/v1 JSON 对象;涉及数据表、字段或视图的节点应使用真实的 sheetId / fieldId / viewId。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。创建属于非幂等操作,CLI 不会自动重试。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "在指定 Base 中创建并发布自动化工作流。\n--dsl 必须是完整的 workflow-dsl/v1 JSON 对象;涉及数据表、字段或视图的节点应使用真实的 sheetId / fieldId / viewId。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。创建属于非幂等操作,CLI 不会自动重试。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "write" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "command-verb", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "dws aitable workflow create --base-id \u003cBASE_ID\u003e --dsl @workflow.json --locale zh-CN" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "dws aitable workflow create --base-id \u003cBASE_ID\u003e --dsl @workflow.json --locale zh-CN" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "non_idempotent" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "selected": false, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "medium" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "创建并发布自动化工作流" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "创建并发布自动化工作流" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ] + } + }, + "group": "workflow", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "workflow_create", + "parameter_count": 3, + "parameters": { + "base-id": { + "description": "目标 Base ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标 Base ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标 Base ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "baseId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "baseId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "baseId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "baseId", + "required": true, + "type": "string" + }, + "dsl": { + "description": "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)" + }, + "interface_type": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "object" + }, + { + "precedence": "fallback", + "selected": false, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "object" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "dsl" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "dsl" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "dsl" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "interface_type": "object", + "property": "dsl", + "required": true, + "type": "string" + }, + "locale": { + "description": "请求语言,例如 zh-CN 或 zh_CN (可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "请求语言,例如 zh-CN 或 zh_CN (可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "请求语言,例如 zh-CN 或 zh_CN (可选)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "locale" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "locale" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "locale" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "locale", + "required": false, + "type": "string" + } + }, + "path": "aitable.workflow_create", + "primary_cli_path": "aitable workflow create", + "product_id": "aitable", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "创建并发布自动化工作流", + "use_when": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ] + }, "aitable.workflow_disable": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -102257,7 +103052,7 @@ "cli_name": "enable", "cli_path": "aitable workflow enable", "confirmation": "not_required", - "description": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n当前不支持通过 CLI 新建工作流,请在 AI 表格 Web 端配置好后用 workflow list 拿到 workflowId 再启停。", + "description": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n可用 workflow create 创建工作流,或用 workflow list 获取已有 workflowId。", "display": "AI 表格操作", "effect": "write", "effect_source": "command-verb", @@ -102359,7 +103154,7 @@ "precedence": "cobra_help", "selected": true, "source": "cobra_help", - "value": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n当前不支持通过 CLI 新建工作流,请在 AI 表格 Web 端配置好后用 workflow list 拿到 workflowId 再启停。" + "value": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n可用 workflow create 创建工作流,或用 workflow list 获取已有 workflowId。" }, { "precedence": "mcp_metadata", @@ -102371,7 +103166,7 @@ "precedence": "cobra_help", "resolution": "highest_precedence", "source": "cobra_help", - "value": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n当前不支持通过 CLI 新建工作流,请在 AI 表格 Web 端配置好后用 workflow list 拿到 workflowId 再启停。" + "value": "启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。\n返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。\n可用 workflow create 创建工作流,或用 workflow list 获取已有 workflowId。" }, "effect": { "candidates": [ @@ -104057,6 +104852,791 @@ "查看有哪些工作流及 status/flowId 时" ] }, + "aitable.workflow_update": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "cobra-help:dws aitable workflow update --help", + "internal/cli/schema_command_registry.json#aitable.workflow_update", + "internal/cli/schema_hints/metadata/aitable.json", + "internal/cli/schema_hints/selection/aitable.json", + "skills/mono/references/products/aitable/aitable-workflow.md" + ], + "agent_summary": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。", + "agent_summary_source": "dws-agent-selection/aitable", + "availability": "available", + "avoid_when": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ], + "canonical_path": "aitable.workflow_update", + "cli_name": "update", + "cli_path": "aitable workflow update", + "confirmation": "not_required", + "description": "在指定 Base 中更新并发布已有自动化工作流。\n建议先用 workflow get 留底当前详情;--dsl 必须是完整的 workflow-dsl/v1 JSON 对象,而不是局部 patch。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。更新会发布传入的目标 DSL;请提供完整、可独立校验的 workflow-dsl/v1 对象。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。", + "display": "AI 表格操作", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws aitable workflow update --base-id \u003cBASE_ID\u003e --workflow-id \u003cFLOW_ID\u003e --dsl @workflow.json --locale zh-CN" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "aitable workflow update", + "value": "aitable.workflow_update" + }, + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": false, + "source": "reviewed_manual_hint", + "source_ref": "aitable workflow update", + "value": "aitable.workflow_update" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "aitable workflow update", + "value": "aitable.workflow_update" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "risk-default", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "在指定 Base 中更新并发布已有自动化工作流。\n建议先用 workflow get 留底当前详情;--dsl 必须是完整的 workflow-dsl/v1 JSON 对象,而不是局部 patch。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。更新会发布传入的目标 DSL;请提供完整、可独立校验的 workflow-dsl/v1 对象。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "在指定 Base 中更新并发布已有自动化工作流。\n建议先用 workflow get 留底当前详情;--dsl 必须是完整的 workflow-dsl/v1 JSON 对象,而不是局部 patch。\n\n--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。更新会发布传入的目标 DSL;请提供完整、可独立校验的 workflow-dsl/v1 对象。\n返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success,\nvalid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "write" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "command-verb", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "dws aitable workflow update --base-id \u003cBASE_ID\u003e --workflow-id \u003cFLOW_ID\u003e --dsl @workflow.json --locale zh-CN" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "dws aitable workflow update --base-id \u003cBASE_ID\u003e --workflow-id \u003cFLOW_ID\u003e --dsl @workflow.json --locale zh-CN" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "unknown" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "unknown" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command." + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "selected": false, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "medium" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "internal/cli/schema_hints/metadata/aitable.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "更新并发布已有自动化工作流" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "更新并发布已有自动化工作流" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "selected": true, + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "source": "internal/cli/schema_hints/selection/aitable.json", + "value": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ] + } + }, + "group": "workflow", + "has_parameters": true, + "idempotency": "unknown", + "interface_mode": "composite", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "is_alias": false, + "name": "workflow_update", + "parameter_count": 4, + "parameters": { + "base-id": { + "description": "目标 Base ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标 Base ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标 Base ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "baseId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "baseId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "baseId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "baseId", + "required": true, + "type": "string" + }, + "dsl": { + "description": "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)" + }, + "interface_type": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "object" + }, + { + "precedence": "fallback", + "selected": false, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "object" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "dsl" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "dsl" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "dsl" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "interface_type": "object", + "property": "dsl", + "required": true, + "type": "string" + }, + "locale": { + "description": "请求语言,例如 zh-CN 或 zh_CN (可选)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "请求语言,例如 zh-CN 或 zh_CN (可选)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "请求语言,例如 zh-CN 或 zh_CN (可选)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "locale" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "locale" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "locale" + }, + "required": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": false + } + ], + "precedence": "default", + "resolution": "fallback", + "source": "default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "locale", + "required": false, + "type": "string" + }, + "workflow-id": { + "description": "目标工作流 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标工作流 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标工作流 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": "workflowId" + }, + { + "precedence": "inference", + "selected": false, + "source": "flag_name_inference", + "value": "workflowId" + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": "workflowId" + }, + "required": { + "candidates": [ + { + "precedence": "reviewed_manual", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "selected": true, + "source": "reviewed_manual_hint", + "value": true + }, + { + "precedence": "inference", + "selected": false, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "reviewed_manual", + "resolution": "highest_precedence", + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "source": "reviewed_manual_hint", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "workflowId", + "required": true, + "type": "string" + } + }, + "path": "aitable.workflow_update", + "primary_cli_path": "aitable workflow update", + "product_id": "aitable", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "更新并发布已有自动化工作流", + "use_when": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ] + }, "attendance.adjustment_get": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ @@ -381644,6 +383224,1163 @@ "需要关闭指定工作表的网格线显示时" ] }, + "sheet.import": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "cobra-help:dws sheet import create --help", + "internal/cli/schema_command_registry.json", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "live-mcp:none (Cobra+Skill; composite/unpinned)", + "skills/mono/references/products/sheet/sheet-import.md" + ], + "agent_summary": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ], + "canonical_path": "sheet.import", + "cli_name": "create", + "cli_path": "sheet import create", + "confirmation": "not_required", + "constraints": { + "require_one_of": [ + [ + "folder-token", + "workspace" + ] + ] + }, + "description": "将本地表格文件导入为一个新的钉钉在线电子表格,与 dws sheet export(导出)对称。\n\n支持的文件格式 (按扩展名):\n xlsx, xls Microsoft Excel 表格\n\n文件大小限制: 20MB\n固定导入为电子表格类型;只新建文档,不操作已有表格。\n\nCLI 内部自动完成全部流程:\n 1. 创建导入会话(获取 OSS 上传凭证)\n 2. 上传文件到 OSS\n 3. 确认导入(触发格式转换)\n 4. 渐进式退避轮询等待完成(最多约 5 分钟)\n\n如果轮询超时仍未完成,会输出 taskId 供后续手动查询:\n dws sheet import get --task-id \u003ctaskId\u003e", + "display": "钉钉表格管理", + "effect": "write", + "effect_source": "agent-hint", + "examples": [ + "dws sheet import create --file ./report.xlsx --folder-token \u003cFOLDER_TOKEN\u003e --format json", + "dws sheet import create --file ./data.xls --workspace \u003cWORKSPACE_ID\u003e --name \"月度报表\" --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet import create", + "value": "sheet.import" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet import create", + "value": "sheet.import" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "risk-default", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "将本地表格文件导入为一个新的钉钉在线电子表格,与 dws sheet export(导出)对称。\n\n支持的文件格式 (按扩展名):\n xlsx, xls Microsoft Excel 表格\n\n文件大小限制: 20MB\n固定导入为电子表格类型;只新建文档,不操作已有表格。\n\nCLI 内部自动完成全部流程:\n 1. 创建导入会话(获取 OSS 上传凭证)\n 2. 上传文件到 OSS\n 3. 确认导入(触发格式转换)\n 4. 渐进式退避轮询等待完成(最多约 5 分钟)\n\n如果轮询超时仍未完成,会输出 taskId 供后续手动查询:\n dws sheet import get --task-id \u003ctaskId\u003e" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "将本地表格文件导入为一个新的钉钉在线电子表格,与 dws sheet export(导出)对称。\n\n支持的文件格式 (按扩展名):\n xlsx, xls Microsoft Excel 表格\n\n文件大小限制: 20MB\n固定导入为电子表格类型;只新建文档,不操作已有表格。\n\nCLI 内部自动完成全部流程:\n 1. 创建导入会话(获取 OSS 上传凭证)\n 2. 上传文件到 OSS\n 3. 确认导入(触发格式转换)\n 4. 渐进式退避轮询等待完成(最多约 5 分钟)\n\n如果轮询超时仍未完成,会输出 taskId 供后续手动查询:\n dws sheet import get --task-id \u003ctaskId\u003e" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "command-verb", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "write" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet import create --file ./report.xlsx --folder-token \u003cFOLDER_TOKEN\u003e --format json", + "dws sheet import create --file ./data.xls --workspace \u003cWORKSPACE_ID\u003e --name \"月度报表\" --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet import create --file ./report.xlsx --folder-token \u003cFOLDER_TOKEN\u003e --format json", + "dws sheet import create --file ./data.xls --workspace \u003cWORKSPACE_ID\u003e --name \"月度报表\" --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "non_idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。" + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "medium" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "medium" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "导入本地表格文件为在线电子表格 (xlsx / xls)" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "导入本地表格文件为在线电子表格 (xlsx / xls)" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ] + } + }, + "group": "import", + "has_parameters": true, + "idempotency": "non_idempotent", + "interface_mode": "composite", + "interface_reason": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。", + "is_alias": false, + "name": "import", + "parameter_count": 4, + "parameters": { + "file": { + "description": "本地表格文件路径 (必填,支持 xlsx/xls)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "本地表格文件路径 (必填,支持 xlsx/xls)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "本地表格文件路径 (必填,支持 xlsx/xls)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "file" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "file" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "file", + "required": true, + "type": "string" + }, + "folder-token": { + "description": "目标文件夹 ID 或 URL (与 --workspace 至少传一个)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标文件夹 ID 或 URL (与 --workspace 至少传一个)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标文件夹 ID 或 URL (与 --workspace 至少传一个)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "folderToken" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "folderToken" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "folderToken", + "required": false, + "type": "string" + }, + "name": { + "description": "导入后表格名称 (可选,默认取文件名)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "导入后表格名称 (可选,默认取文件名)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "导入后表格名称 (可选,默认取文件名)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "name" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "name" + }, + "required": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_nonzero_default", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_nonzero_default", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "name", + "required": false, + "type": "string" + }, + "workspace": { + "description": "目标知识库 ID 或 URL (与 --folder-token 至少传一个)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "目标知识库 ID 或 URL (与 --folder-token 至少传一个)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "目标知识库 ID 或 URL (与 --folder-token 至少传一个)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "workspace" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "workspace" + }, + "required": { + "candidates": [ + { + "precedence": "command_constraint", + "selected": true, + "source": "require_one_of_constraint", + "value": false + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "command_constraint", + "resolution": "highest_precedence", + "source": "require_one_of_constraint", + "value": false + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "workspace", + "required": false, + "type": "string" + } + }, + "path": "sheet.import", + "primary_cli_path": "sheet import create", + "product_id": "sheet", + "reviewed": true, + "risk": "medium", + "source": "reviewed_command_registry", + "title": "导入本地表格文件为在线电子表格 (xlsx / xls)", + "use_when": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ] + }, + "sheet.import_get": { + "agent_metadata_source": "embedded-skill-metadata", + "agent_source_refs": [ + "cobra-help:dws sheet import get --help", + "internal/cli/schema_command_registry.json", + "internal/cli/schema_hints/metadata/sheet.json", + "internal/cli/schema_hints/selection/sheet.json", + "live-mcp:none (Cobra+Skill; composite/unpinned)", + "skills/mono/references/products/sheet/sheet-import.md" + ], + "agent_summary": "根据 taskId 查询表格导入任务结果。", + "agent_summary_source": "dws-agent-selection/sheet", + "availability": "available", + "avoid_when": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ], + "canonical_path": "sheet.import_get", + "cli_name": "get", + "cli_path": "sheet import get", + "confirmation": "not_required", + "description": "根据 taskId 查询表格导入任务的执行结果。\n通常不需要手动调用,dws sheet import 会自动完成轮询。\n仅在导入命令超时或中断后,用于手动查询任务状态。\n\n任务状态:\n processing 转换中\n completed 导入成功,返回 documentUrl\n failed 导入失败", + "display": "钉钉表格管理", + "effect": "read", + "effect_source": "agent-hint", + "examples": [ + "dws sheet import get --task-id \u003cTASK_ID\u003e --format json" + ], + "field_provenance": { + "agent_summary": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "根据 taskId 查询表格导入任务结果。" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": "根据 taskId 查询表格导入任务结果。" + }, + "availability": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "available" + }, + "avoid_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ] + }, + "canonical_path": { + "candidates": [ + { + "precedence": "command_registry", + "selected": true, + "source": "reviewed_command_registry", + "source_ref": "sheet import get", + "value": "sheet.import_get" + } + ], + "precedence": "command_registry", + "resolution": "registry_identity", + "source": "reviewed_command_registry", + "source_ref": "sheet import get", + "value": "sheet.import_get" + }, + "confirmation": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "risk-default", + "value": "not_required" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "not_required" + }, + "description": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "根据 taskId 查询表格导入任务的执行结果。\n通常不需要手动调用,dws sheet import 会自动完成轮询。\n仅在导入命令超时或中断后,用于手动查询任务状态。\n\n任务状态:\n processing 转换中\n completed 导入成功,返回 documentUrl\n failed 导入失败" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "根据 taskId 查询表格导入任务的执行结果。\n通常不需要手动调用,dws sheet import 会自动完成轮询。\n仅在导入命令超时或中断后,用于手动查询任务状态。\n\n任务状态:\n processing 转换中\n completed 导入成功,返回 documentUrl\n failed 导入失败" + }, + "effect": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "command-verb", + "value": "write" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "read" + }, + "examples": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet import get --task-id \u003cTASK_ID\u003e --format json" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "dws sheet import get --task-id \u003cTASK_ID\u003e --format json" + ] + }, + "idempotency": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "unknown" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "idempotent" + }, + "interface_mode": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "composite" + }, + "interface_reason": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。" + }, + "interface_ref": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + } + ], + "precedence": "reviewed_explicit", + "resolution": "interface_disposition_matrix", + "review_reason": "final interface mode composite forbids a direct MCP interface_ref", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": null + }, + "reviewed": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "selected": false, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": true + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": true + }, + "risk": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "selected": true, + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + { + "precedence": "inference_or_default", + "selected": false, + "source": "effect-default", + "value": "medium" + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。", + "source": "internal/cli/schema_hints/metadata/sheet.json", + "value": "low" + }, + "title": { + "candidates": [ + { + "precedence": "cobra_help", + "selected": true, + "source": "cobra_help", + "value": "查询表格导入任务结果(手动兜底)" + } + ], + "precedence": "cobra_help", + "resolution": "highest_precedence", + "source": "cobra_help", + "value": "查询表格导入任务结果(手动兜底)" + }, + "use_when": { + "candidates": [ + { + "precedence": "reviewed_explicit", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "selected": true, + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ] + } + ], + "precedence": "reviewed_explicit", + "resolution": "highest_precedence", + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "source": "internal/cli/schema_hints/selection/sheet.json", + "value": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ] + } + }, + "group": "import", + "has_parameters": true, + "idempotency": "idempotent", + "interface_mode": "composite", + "interface_reason": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。", + "is_alias": false, + "name": "import_get", + "parameter_count": 1, + "parameters": { + "task-id": { + "description": "导入任务 ID (必填)", + "field_provenance": { + "description": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_usage", + "value": "导入任务 ID (必填)" + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": "" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_usage", + "value": "导入任务 ID (必填)" + }, + "property": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "flag_name_inference", + "value": "taskId" + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "flag_name_inference", + "value": "taskId" + }, + "required": { + "candidates": [ + { + "precedence": "inference", + "selected": true, + "source": "usage_required_inference", + "value": true + }, + { + "precedence": "default", + "selected": false, + "source": "default", + "value": false + } + ], + "precedence": "inference", + "resolution": "highest_precedence", + "source": "usage_required_inference", + "value": true + }, + "required_when": { + "candidates": [ + { + "precedence": "default", + "selected": true, + "source": "default", + "value": "" + } + ], + "precedence": "default", + "resolution": "highest_precedence", + "source": "default", + "value": "" + }, + "type": { + "candidates": [ + { + "precedence": "cobra_contract", + "selected": true, + "source": "cobra_flag_type", + "value": "string" + } + ], + "precedence": "cobra_contract", + "resolution": "highest_precedence", + "source": "cobra_flag_type", + "value": "string" + } + }, + "property": "taskId", + "required": true, + "type": "string" + } + }, + "path": "sheet.import_get", + "primary_cli_path": "sheet import get", + "product_id": "sheet", + "reviewed": true, + "risk": "low", + "source": "reviewed_command_registry", + "title": "查询表格导入任务结果(手动兜底)", + "use_when": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ] + }, "sheet.info": { "agent_metadata_source": "embedded-skill-metadata", "agent_source_refs": [ diff --git a/internal/cli/schema_command_registry.json b/internal/cli/schema_command_registry.json index 76a246b2..9c8f042b 100644 --- a/internal/cli/schema_command_registry.json +++ b/internal/cli/schema_command_registry.json @@ -465,6 +465,10 @@ "canonical_path": "aitable.view_update_visible_fields", "cli_path": "aitable view update visible-fields" }, + { + "canonical_path": "aitable.workflow_create", + "cli_path": "aitable workflow create" + }, { "canonical_path": "aitable.workflow_disable", "cli_path": "aitable workflow disable" @@ -480,6 +484,10 @@ { "canonical_path": "aitable.workflow_list", "cli_path": "aitable workflow list" + }, + { + "canonical_path": "aitable.workflow_update", + "cli_path": "aitable workflow update" } ] }, @@ -2110,6 +2118,14 @@ "canonical_path": "sheet.info", "cli_path": "sheet info" }, + { + "canonical_path": "sheet.import", + "cli_path": "sheet import create" + }, + { + "canonical_path": "sheet.import_get", + "cli_path": "sheet import get" + }, { "canonical_path": "sheet.insert_dimension", "cli_path": "sheet insert-dimension" diff --git a/internal/cli/schema_constraints_runtime.go b/internal/cli/schema_constraints_runtime.go index 1292ecd0..2351f60f 100644 --- a/internal/cli/schema_constraints_runtime.go +++ b/internal/cli/schema_constraints_runtime.go @@ -89,6 +89,7 @@ func init() { registerRequireOneOf("sheet.update_filter_view", "name", "range", "criteria") registerRequireOneOf("sheet.update_float_image", "src", "range", "width", "height", "offset-x", "offset-y") registerRequireOneOf("sheet.update_sheet", "name", "index", "hidden", "frozen-row-count", "frozen-column-count", "tab-color") + registerRequireOneOf("sheet.import", "folder-token", "workspace") registerRequireOneOf("wiki.search_wikiSpaces", "query", "type") } diff --git a/internal/cli/schema_hints/metadata/aitable.json b/internal/cli/schema_hints/metadata/aitable.json index bdbf5d57..fc8c8364 100644 --- a/internal/cli/schema_hints/metadata/aitable.json +++ b/internal/cli/schema_hints/metadata/aitable.json @@ -959,6 +959,33 @@ "availability": "available", "reviewed": true }, + "aitable.workflow_create": { + "effect": "write", + "risk": "medium", + "confirmation": "not_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "parameters": { + "base-id": { + "property": "baseId", + "required": true + }, + "dsl": { + "property": "dsl", + "required": true, + "interface_type": "object" + }, + "locale": { + "property": "locale" + } + }, + "review_reason": "The command maps --base-id and a decoded workflow-dsl/v1 --dsl object to aitable/create_workflow. The call intentionally bypasses retries because creation is non-idempotent; locale remains optional.", + "cli_path": "aitable workflow create", + "runtime_gate": "none" + }, "aitable.workflow_disable": { "effect": "write", "risk": "high", @@ -998,6 +1025,37 @@ "interface_mode": "mcp", "availability": "available", "reviewed": true + }, + "aitable.workflow_update": { + "effect": "write", + "risk": "medium", + "confirmation": "not_required", + "idempotency": "unknown", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "Reviewed unpinned remote adapter: this executable CLI wrapper calls a remote helper that is absent from the pinned MCP metadata snapshot; no single pinned semantically equivalent interface_ref can represent the command.", + "reviewed": true, + "parameters": { + "base-id": { + "property": "baseId", + "required": true + }, + "workflow-id": { + "property": "workflowId", + "required": true + }, + "dsl": { + "property": "dsl", + "required": true, + "interface_type": "object" + }, + "locale": { + "property": "locale" + } + }, + "review_reason": "The command maps --base-id, --workflow-id and a decoded complete workflow-dsl/v1 --dsl object to aitable/update_workflow. This is full replacement rather than a partial patch; locale remains optional.", + "cli_path": "aitable workflow update", + "runtime_gate": "none" } } } diff --git a/internal/cli/schema_hints/metadata/sheet.json b/internal/cli/schema_hints/metadata/sheet.json index d127a79c..77277c75 100644 --- a/internal/cli/schema_hints/metadata/sheet.json +++ b/internal/cli/schema_hints/metadata/sheet.json @@ -392,6 +392,28 @@ "availability": "available", "reviewed": true }, + "sheet.import": { + "effect": "write", + "risk": "medium", + "confirmation": "not_required", + "idempotency": "non_idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "该 CLI 导入流程包含本地文件校验、创建导入会话、OSS 上传、确认转换和任务轮询,不能绑定为单一 interface_ref。", + "reviewed": true, + "review_reason": "人工审阅:导入会新建在线电子表格,不修改已有文档;重复执行会创建多个文档,因此标记为 non_idempotent。" + }, + "sheet.import_get": { + "effect": "read", + "risk": "low", + "confirmation": "not_required", + "idempotency": "idempotent", + "interface_mode": "composite", + "availability": "available", + "interface_reason": "该 CLI 续查命令显式复用 doc server 的 query_import_task;当前 Sheet 静态接口快照没有可直接绑定的单一 interface_ref。", + "reviewed": true, + "review_reason": "人工审阅:仅查询已有导入任务,不创建、覆盖或删除文档。" + }, "sheet.insert_dimension": { "interface_mode": "mcp", "availability": "available", diff --git a/internal/cli/schema_hints/runtime-surface-completeness.json b/internal/cli/schema_hints/runtime-surface-completeness.json index 14a85de3..cbd0372e 100644 --- a/internal/cli/schema_hints/runtime-surface-completeness.json +++ b/internal/cli/schema_hints/runtime-surface-completeness.json @@ -7,7 +7,7 @@ "channel": "open-source" }, "coverage": { - "source_tools": 564, + "source_tools": 568, "matched_tools": 71 }, "tools": { diff --git a/internal/cli/schema_hints/selection/aitable.json b/internal/cli/schema_hints/selection/aitable.json index 5c5c7108..400acf5b 100644 --- a/internal/cli/schema_hints/selection/aitable.json +++ b/internal/cli/schema_hints/selection/aitable.json @@ -2211,6 +2211,25 @@ "dws-schema-live:aitable.update_view" ] }, + "aitable.workflow_create": { + "agent_summary": "创建并发布 AI 表格自动化工作流。", + "use_when": [ + "用户明确要求在已知 Base 中创建自动化,且已准备完整 workflow-dsl/v1 定义时" + ], + "avoid_when": [ + "已有工作流要修改时用 workflow update;仅启停已有工作流用 enable/disable;请求结果不确定时先 list 核对,避免非幂等重复创建" + ], + "examples": [ + "dws aitable workflow create --base-id --dsl @workflow.json --locale zh-CN" + ], + "reviewed": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅创建语义,强调完整 DSL、结果 valid/issues 校验及非幂等边界。", + "source_refs": [ + "internal/cli/schema_command_registry.json#aitable.workflow_create", + "cobra-help:dws aitable workflow create --help", + "skills/mono/references/products/aitable/aitable-workflow.md" + ] + }, "aitable.workflow_disable": { "agent_summary": "禁用工作流(高危,需确认;status 变 STOP)。", "use_when": [ @@ -2290,6 +2309,25 @@ "skills/mono/references/products/aitable.md", "dws-schema-live:none (helper/composite; Skill+Cobra)" ] + }, + "aitable.workflow_update": { + "agent_summary": "用完整 DSL 更新并发布已有 AI 表格自动化工作流。", + "use_when": [ + "用户明确要求修改已知工作流,且已先留底当前详情并准备完整 workflow-dsl/v1 目标定义时" + ], + "avoid_when": [ + "新建工作流用 create;只改变运行状态用 enable/disable;不要把局部 patch 或 workflow get 返回的 flowSchema 直接当作 DSL" + ], + "examples": [ + "dws aitable workflow update --base-id --workflow-id --dsl @workflow.json --locale zh-CN" + ], + "reviewed": true, + "review_reason": "依据真实 Cobra leaf、Wukong 同步契约和 Aitable workflow Skill 审阅更新语义,强调全量替换、事前备份和 valid/issues 结果校验。", + "source_refs": [ + "internal/cli/schema_command_registry.json#aitable.workflow_update", + "cobra-help:dws aitable workflow update --help", + "skills/mono/references/products/aitable/aitable-workflow.md" + ] } }, "products": { diff --git a/internal/cli/schema_hints/selection/sheet.json b/internal/cli/schema_hints/selection/sheet.json index b6d4a5c3..2db91c19 100644 --- a/internal/cli/schema_hints/selection/sheet.json +++ b/internal/cli/schema_hints/selection/sheet.json @@ -849,6 +849,49 @@ "dws-schema-live:sheet.get_sheet" ] }, + "sheet.import": { + "agent_summary": "将本地 xlsx/xls 文件导入为新的钉钉在线电子表格。", + "use_when": [ + "用户要把本地 Excel 文件转换为可在线编辑的钉钉表格时" + ], + "avoid_when": [ + "读取或修改已有在线表格时应使用 sheet list/info/range 等命令", + "查询已提交导入任务的状态时使用 sheet import get,不要重复创建导入任务" + ], + "examples": [ + "dws sheet import create --file ./report.xlsx --folder-token --format json", + "dws sheet import create --file ./data.xls --workspace --name \"月度报表\" --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:公开 leaf 与兼容入口 sheet import 复用同一导入流程,使 Agent Schema 能发现并调用真正的导入动作。", + "source_refs": [ + "internal/cli/schema_command_registry.json", + "cobra-help:dws sheet import create --help", + "skills/mono/references/products/sheet/sheet-import.md", + "live-mcp:none (Cobra+Skill; composite/unpinned)" + ] + }, + "sheet.import_get": { + "agent_summary": "根据 taskId 查询表格导入任务结果。", + "use_when": [ + "已有 sheet import 返回的 taskId,需要在导入超时或中断后续查转换状态时" + ], + "avoid_when": [ + "发起新的 xlsx/xls 导入应使用 sheet import;不要用本命令代替导入", + "状态仍为 processing 时不要重新提交 import,稍后继续查询同一 taskId" + ], + "examples": [ + "dws sheet import get --task-id --format json" + ], + "reviewed": true, + "review_reason": "人工审阅:该命令与 Wukong sheet import get 契约一致,复用 doc 导入任务查询工具,仅用于已有任务续查。", + "source_refs": [ + "internal/cli/schema_command_registry.json", + "cobra-help:dws sheet import get --help", + "skills/mono/references/products/sheet/sheet-import.md", + "live-mcp:none (Cobra+Skill; composite/unpinned)" + ] + }, "sheet.insert_dimension": { "agent_summary": "在指定位置之前插入空行或空列。", "use_when": [ @@ -1552,12 +1595,14 @@ }, "products": { "sheet": { - "agent_summary": "创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", + "agent_summary": "导入本地 Excel,或创建、读取、编辑和导出钉钉在线电子表格(axls),并管理工作表、区域、筛选、图表、图片与格式。", "use_when": [ - "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时" + "用户要处理钉钉在线电子表格中的工作表、单元格、范围、筛选、图表、图片或格式时", + "用户要把本地 xlsx/xls 转换为新的钉钉在线电子表格时" ], "avoid_when": [ - "目标是 AI 表格 Base 的结构化记录、钉钉文档正文或本地 Excel 文件时不要使用 sheet" + "目标是 AI 表格 Base 的结构化记录或钉钉文档正文时不要使用 sheet", + "只读取已上传但未转换的 xlsx/xls 节点时先用 doc download;不要把文件节点直接传给单元格命令" ], "reviewed": true, "review_reason": "人工结合实时 MCP/Skill/Cobra 审阅产品级选择边界。", diff --git a/internal/cli/stdin_test.go b/internal/cli/stdin_test.go index c3299b08..96c3be2f 100644 --- a/internal/cli/stdin_test.go +++ b/internal/cli/stdin_test.go @@ -199,17 +199,13 @@ func TestReadFileBoundedEmptyFile(t *testing.T) { } } -func TestReadFileBoundedPermissionDenied(t *testing.T) { +func TestReadFileBoundedRejectsDirectory(t *testing.T) { t.Parallel() dir := t.TempDir() - path := filepath.Join(dir, "noperm.txt") - writeTestFile(t, path, []byte("secret")) - os.Chmod(path, 0o000) - t.Cleanup(func() { os.Chmod(path, 0o644) }) - _, _, err := ReadFileArg("@" + path) + _, _, err := ReadFileArg("@" + dir) if err == nil { - t.Fatal("expected error for permission denied") + t.Fatal("expected error when reading a directory as a file") } } diff --git a/internal/helpers/aitable.go b/internal/helpers/aitable.go index 0c6374e6..d4b1f8e4 100644 --- a/internal/helpers/aitable.go +++ b/internal/helpers/aitable.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "os" "strconv" "strings" @@ -81,6 +82,44 @@ func resolveRecordsFlag(cmd *cobra.Command) (string, error) { return "", fmt.Errorf("missing required flag(s): --records (example: --records '[{\"cells\":{\"fldTextId\":\"文本内容\"}}]')\n hint: for large payloads or Windows, use --records-file ./path/to/records.json") } +// resolveWorkflowDSL reads --dsl from inline JSON, @file, or stdin (-), then +// decodes the MCP-facing workflow-dsl/v1 object. Detailed DSL validation stays +// on the workflow service so callers receive its structured issues response. +func resolveWorkflowDSL(cmd *cobra.Command) (map[string]any, error) { + raw := mustGetFlag(cmd, "dsl") + switch { + case raw == "-": + data, err := io.ReadAll(cmd.InOrStdin()) + if err != nil { + return nil, fmt.Errorf("--dsl stdin read failed: %w", err) + } + raw = string(data) + case strings.HasPrefix(raw, "@"): + path := strings.TrimSpace(strings.TrimPrefix(raw, "@")) + if path == "" { + return nil, fmt.Errorf("--dsl file path must not be empty\n hint: use --dsl @workflow.json") + } + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("--dsl file read failed: %w\n hint: ensure the file exists and contains a workflow-dsl/v1 JSON object", err) + } + raw = string(data) + } + + if strings.TrimSpace(raw) == "" { + return nil, fmt.Errorf("--dsl must not be empty\n hint: pass inline JSON, @workflow.json, or - for stdin") + } + + var dsl map[string]any + if err := json.Unmarshal([]byte(raw), &dsl); err != nil { + return nil, fmt.Errorf("--dsl JSON parse failed: %w\n hint: --dsl must be a workflow-dsl/v1 JSON object", err) + } + if dsl == nil { + return nil, fmt.Errorf("--dsl must be a JSON object, got null") + } + return dsl, nil +} + // recordQueryFetchAll implements --all auto-pagination for record query. // - pageLimit controls max pages (default 50, 0 = unlimited) // - Mid-loop errors break gracefully, outputting already-fetched data @@ -3204,16 +3243,87 @@ locked 为 true 表示视图已锁定,false 表示未锁定。`, workflowCmd := &cobra.Command{ Use: "workflow", - Short: "自动化工作流管理(启停 / 查看 / 列表)", + Short: "自动化工作流管理(创建 / 更新 / 启停 / 查看 / 列表)", RunE: groupRunE, } + workflowCreateCmd := &cobra.Command{ + Use: "create", + Short: "创建并发布自动化工作流", + Long: `在指定 Base 中创建并发布自动化工作流。 +--dsl 必须是完整的 workflow-dsl/v1 JSON 对象;涉及数据表、字段或视图的节点应使用真实的 sheetId / fieldId / viewId。 + +--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。创建属于非幂等操作,CLI 不会自动重试。 +返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success, +valid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。`, + Example: ` dws aitable workflow create --base-id BASE_ID --dsl @workflow.json --locale zh-CN + cat workflow.json | dws aitable workflow create --base-id BASE_ID --dsl -`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlags(cmd, "dsl"); err != nil { + return err + } + baseID, err := mustFlagOrFallback(cmd, "base-id", "base") + if err != nil { + return err + } + dsl, err := resolveWorkflowDSL(cmd) + if err != nil { + return err + } + toolArgs := map[string]any{ + "baseId": baseID, + "dsl": dsl, + } + if locale, _ := cmd.Flags().GetString("locale"); strings.TrimSpace(locale) != "" { + toolArgs["locale"] = locale + } + // create_workflow is non-idempotent. Bypass the retry wrapper to + // prevent an uncertain first response from creating a duplicate. + return callMCPToolOnServer("aitable", "create_workflow", toolArgs) + }, + } + + workflowUpdateCmd := &cobra.Command{ + Use: "update", + Short: "更新并发布已有自动化工作流", + Long: `在指定 Base 中更新并发布已有自动化工作流。 +建议先用 workflow get 留底当前详情;--dsl 必须是完整的 workflow-dsl/v1 JSON 对象,而不是局部 patch。 + +--dsl 支持内联 JSON、@文件路径,或 - 从 stdin 读取。更新会发布传入的目标 DSL;请提供完整、可独立校验的 workflow-dsl/v1 对象。 +返回 data.valid、flowId、flowSchema、stepNodeIds、referenceMap、issues;即使 status=success, +valid=false 仍表示 DSL 校验或发布未通过,必须读取 issues 修正后再调用。`, + Example: ` dws aitable workflow update --base-id BASE_ID --workflow-id WORKFLOW_ID --dsl @workflow.json --locale zh-CN + cat workflow.json | dws aitable workflow update --base-id BASE_ID --workflow-id WORKFLOW_ID --dsl -`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateRequiredFlags(cmd, "workflow-id", "dsl"); err != nil { + return err + } + baseID, err := mustFlagOrFallback(cmd, "base-id", "base") + if err != nil { + return err + } + dsl, err := resolveWorkflowDSL(cmd) + if err != nil { + return err + } + toolArgs := map[string]any{ + "baseId": baseID, + "workflowId": mustGetFlag(cmd, "workflow-id"), + "dsl": dsl, + } + if locale, _ := cmd.Flags().GetString("locale"); strings.TrimSpace(locale) != "" { + toolArgs["locale"] = locale + } + return callAitableTool("update_workflow", toolArgs) + }, + } + workflowEnableCmd := &cobra.Command{ Use: "enable", Short: "启用指定工作流", Long: `启用指定 Base 中的自动化工作流。启用后工作流将按配置的触发条件自动执行。 返回 {workflowId, enabled} 用于确认操作结果(enabled 为动作确认而非状态查询)。 -当前不支持通过 CLI 新建工作流,请在 AI 表格 Web 端配置好后用 workflow list 拿到 workflowId 再启停。`, +可用 workflow create 创建工作流,或用 workflow list 获取已有 workflowId。`, Example: ` dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID # 查询 workflowId: dws aitable workflow list --base-id `, RunE: func(cmd *cobra.Command, args []string) error { @@ -4750,6 +4860,13 @@ parentSectionId 为空串表示该节点在 Base 根目录下。 formCmd.AddCommand(formListCmd, formGetCmd, formCreateCmd, formDeleteCmd, formUpdateCmd, formFieldCmd, formShareCmd, formQuestionsCmd) // workflow + workflowCreateCmd.Flags().String("base-id", "", "目标 Base ID (必填)") + workflowCreateCmd.Flags().String("dsl", "", "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)") + workflowCreateCmd.Flags().String("locale", "", "请求语言,例如 zh-CN 或 zh_CN (可选)") + workflowUpdateCmd.Flags().String("base-id", "", "目标 Base ID (必填)") + workflowUpdateCmd.Flags().String("workflow-id", "", "目标工作流 ID (必填)") + workflowUpdateCmd.Flags().String("dsl", "", "workflow-dsl/v1 JSON 对象;支持内联 JSON、@文件路径或 - 从 stdin 读取 (必填)") + workflowUpdateCmd.Flags().String("locale", "", "请求语言,例如 zh-CN 或 zh_CN (可选)") workflowEnableCmd.Flags().String("base-id", "", "目标 Base ID (必填)") workflowEnableCmd.Flags().String("workflow-id", "", "目标工作流 ID (必填)") workflowDisableCmd.Flags().String("base-id", "", "目标 Base ID (必填)") @@ -4759,7 +4876,11 @@ parentSectionId 为空串表示该节点在 Base 根目录下。 workflowListCmd.Flags().String("base-id", "", "目标 Base ID (必填)") workflowListCmd.Flags().Int("limit", 0, "分页大小 [1, 100],不传走服务端默认 20") workflowListCmd.Flags().Int("offset", 0, "分页偏移量,>= 0,不传走服务端默认 0") - workflowCmd.AddCommand(workflowEnableCmd, workflowDisableCmd, workflowGetCmd, workflowListCmd) + workflowCmd.AddCommand( + workflowCreateCmd, workflowUpdateCmd, + workflowEnableCmd, workflowDisableCmd, + workflowGetCmd, workflowListCmd, + ) // dashboard dashboardGetCmd.Flags().String("base-id", "", "所属 Base ID (必填)") diff --git a/internal/helpers/aitable_workflow_write_test.go b/internal/helpers/aitable_workflow_write_test.go new file mode 100644 index 00000000..efda7bef --- /dev/null +++ b/internal/helpers/aitable_workflow_write_test.go @@ -0,0 +1,168 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. + +package helpers + +import ( + "context" + "io" + "os" + "reflect" + "strings" + "testing" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" +) + +type aitableWorkflowCall struct { + productID string + toolName string + args map[string]any +} + +type aitableWorkflowCaller struct { + calls []aitableWorkflowCall +} + +func (c *aitableWorkflowCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) { + c.calls = append(c.calls, aitableWorkflowCall{productID: productID, toolName: toolName, args: args}) + return &edition.ToolResult{Content: []edition.ContentBlock{{ + Type: "text", + Text: `{"status":"success","data":{"valid":true,"flowId":"flow-test","issues":[]}}`, + }}}, nil +} + +func (*aitableWorkflowCaller) Format() string { return "json" } +func (*aitableWorkflowCaller) DryRun() bool { return false } +func (*aitableWorkflowCaller) Fields() string { return "" } +func (*aitableWorkflowCaller) JQ() string { return "" } + +func runAitableWorkflowCommand(t *testing.T, stdin io.Reader, args ...string) (*aitableWorkflowCaller, error) { + t.Helper() + previousDeps := deps + previousArgs := os.Args + t.Cleanup(func() { + deps = previousDeps + os.Args = previousArgs + }) + + caller := &aitableWorkflowCaller{} + InitDeps(caller) + deps.Out.w = io.Discard + os.Args = append([]string{"dws", "aitable", "workflow"}, args...) + + cmd := newAitableCommand() + cmd.PersistentFlags().String("format", "json", "output format") + cmd.SilenceErrors = true + cmd.SilenceUsage = true + cmd.SetArgs(append([]string{"workflow"}, args...)) + if stdin != nil { + cmd.SetIn(stdin) + } + return caller, cmd.Execute() +} + +func TestAitableWorkflowCreateMapsDSLWithoutRetry(t *testing.T) { + wantDSL := map[string]any{ + "version": "workflow-dsl/v1", + "name": "create test", + } + caller, err := runAitableWorkflowCommand(t, nil, + "create", + "--base-id", "base-create", + "--dsl", `{"version":"workflow-dsl/v1","name":"create test"}`, + "--locale", "zh-CN", + ) + if err != nil { + t.Fatalf("workflow create returned error: %v", err) + } + if len(caller.calls) != 1 { + t.Fatalf("tool call count = %d, want 1", len(caller.calls)) + } + call := caller.calls[0] + if call.productID != "aitable" || call.toolName != "create_workflow" { + t.Fatalf("tool call = %s/%s, want aitable/create_workflow", call.productID, call.toolName) + } + wantArgs := map[string]any{ + "baseId": "base-create", + "dsl": wantDSL, + "locale": "zh-CN", + } + if !reflect.DeepEqual(call.args, wantArgs) { + t.Fatalf("tool args = %#v, want %#v", call.args, wantArgs) + } +} + +func TestAitableWorkflowUpdateReadsDSLFile(t *testing.T) { + path := t.TempDir() + "/workflow.json" + if err := os.WriteFile(path, []byte(`{"version":"workflow-dsl/v1","name":"updated"}`), 0o600); err != nil { + t.Fatalf("write workflow fixture: %v", err) + } + caller, err := runAitableWorkflowCommand(t, nil, + "update", + "--base-id", "base-update", + "--workflow-id", "flow-existing", + "--dsl", "@"+path, + ) + if err != nil { + t.Fatalf("workflow update returned error: %v", err) + } + if len(caller.calls) != 1 { + t.Fatalf("tool call count = %d, want 1", len(caller.calls)) + } + call := caller.calls[0] + if call.productID != "aitable" || call.toolName != "update_workflow" { + t.Fatalf("tool call = %s/%s, want aitable/update_workflow", call.productID, call.toolName) + } + wantArgs := map[string]any{ + "baseId": "base-update", + "workflowId": "flow-existing", + "dsl": map[string]any{ + "version": "workflow-dsl/v1", + "name": "updated", + }, + } + if !reflect.DeepEqual(call.args, wantArgs) { + t.Fatalf("tool args = %#v, want %#v", call.args, wantArgs) + } +} + +func TestAitableWorkflowCreateReadsDSLFromStdin(t *testing.T) { + caller, err := runAitableWorkflowCommand(t, + strings.NewReader(`{"version":"workflow-dsl/v1","name":"stdin"}`), + "create", "--base-id", "base-stdin", "--dsl", "-", + ) + if err != nil { + t.Fatalf("workflow create from stdin returned error: %v", err) + } + if got := caller.calls[0].args["dsl"].(map[string]any)["name"]; got != "stdin" { + t.Fatalf("dsl name = %#v, want stdin", got) + } +} + +func TestAitableWorkflowWriteRejectsInvalidInput(t *testing.T) { + tests := []struct { + name string + args []string + want string + }{ + {name: "create missing dsl", args: []string{"create", "--base-id", "base"}, want: "dsl"}, + {name: "update missing workflow", args: []string{"update", "--base-id", "base", "--dsl", `{}`}, want: "workflow-id"}, + {name: "malformed json", args: []string{"create", "--base-id", "base", "--dsl", "{not-json"}, want: "JSON parse failed"}, + {name: "array", args: []string{"create", "--base-id", "base", "--dsl", `[]`}, want: "JSON parse failed"}, + {name: "null", args: []string{"create", "--base-id", "base", "--dsl", `null`}, want: "JSON object"}, + {name: "empty file path", args: []string{"create", "--base-id", "base", "--dsl", "@"}, want: "file path"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + caller, err := runAitableWorkflowCommand(t, nil, tc.args...) + if err == nil || !strings.Contains(err.Error(), tc.want) { + t.Fatalf("error = %v, want substring %q", err, tc.want) + } + if len(caller.calls) != 0 { + t.Fatalf("invalid input reached MCP: %#v", caller.calls) + } + }) + } +} diff --git a/internal/helpers/atomicwrite_test.go b/internal/helpers/atomicwrite_test.go index 65a690f8..7f3f1994 100644 --- a/internal/helpers/atomicwrite_test.go +++ b/internal/helpers/atomicwrite_test.go @@ -4,6 +4,7 @@ import ( "bytes" "os" "path/filepath" + "runtime" "strings" "testing" ) @@ -27,13 +28,15 @@ func TestAtomicWrite_Basic(t *testing.T) { t.Fatalf("got %q, want %q", got, data) } - // Check permissions - info, err := os.Stat(path) - if err != nil { - t.Fatalf("Stat() error = %v", err) - } - if perm := info.Mode().Perm(); perm != 0600 { - t.Fatalf("permissions = %o, want 0600", perm) + // Windows does not expose POSIX permission bits. + if runtime.GOOS != "windows" { + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat() error = %v", err) + } + if perm := info.Mode().Perm(); perm != 0600 { + t.Fatalf("permissions = %o, want 0600", perm) + } } } @@ -149,12 +152,14 @@ func TestAtomicWriteJSON_Basic(t *testing.T) { t.Fatalf("got %q, want %q", got, data) } - // Check permissions are 0600 - info, err := os.Stat(path) - if err != nil { - t.Fatalf("Stat() error = %v", err) - } - if perm := info.Mode().Perm(); perm != 0600 { - t.Fatalf("permissions = %o, want 0600", perm) + // Windows does not expose POSIX permission bits. + if runtime.GOOS != "windows" { + info, err := os.Stat(path) + if err != nil { + t.Fatalf("Stat() error = %v", err) + } + if perm := info.Mode().Perm(); perm != 0600 { + t.Fatalf("permissions = %o, want 0600", perm) + } } } diff --git a/internal/helpers/connect_agent_options_test.go b/internal/helpers/connect_agent_options_test.go index d7a2b3ff..e328d2af 100644 --- a/internal/helpers/connect_agent_options_test.go +++ b/internal/helpers/connect_agent_options_test.go @@ -17,6 +17,7 @@ import ( "bytes" "os" "path/filepath" + "runtime" "strings" "testing" ) @@ -24,7 +25,20 @@ import ( // writeExecStub drops an executable shell stub named name into dir so PATH // lookups resolve without the real CLI installed. func writeExecStub(dir, name string) error { - return os.WriteFile(filepath.Join(dir, name), []byte("#!/bin/sh\n"), 0o755) + path := filepath.Join(dir, name) + body := []byte("#!/bin/sh\n") + if runtime.GOOS == "windows" { + path += ".exe" + body = nil + } + return os.WriteFile(path, body, 0o755) +} + +func requirePOSIXShell(t *testing.T) { + t.Helper() + if runtime.GOOS == "windows" { + t.Skip("test fixture requires a POSIX shell executable") + } } // TestConvSessions covers the per-conversation session contract: first message diff --git a/internal/helpers/connect_codex_appserver_test.go b/internal/helpers/connect_codex_appserver_test.go index 6115e9da..1f2ef19f 100644 --- a/internal/helpers/connect_codex_appserver_test.go +++ b/internal/helpers/connect_codex_appserver_test.go @@ -26,6 +26,7 @@ import ( func writeShellExecutable(t *testing.T, dir, name, body string) string { t.Helper() + requirePOSIXShell(t) path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body), 0o755); err != nil { t.Fatalf("write stub %s: %v", name, err) @@ -37,7 +38,9 @@ func TestForwarderForChannelCodexPrefersAppServer(t *testing.T) { clearChannelEnv(t) t.Setenv("DWS_CONNECT_NO_INSTALL", "1") stub := t.TempDir() - writeShellExecutable(t, stub, "codex", "exit 0\n") + if err := writeExecStub(stub, "codex"); err != nil { + t.Fatalf("write codex stub: %v", err) + } t.Setenv("PATH", stub) fwd, err := forwarderForChannel("codex", "", connectAgentOptions{Memory: true}) diff --git a/internal/helpers/connect_daemon_test.go b/internal/helpers/connect_daemon_test.go index 556abc00..e5bb452e 100644 --- a/internal/helpers/connect_daemon_test.go +++ b/internal/helpers/connect_daemon_test.go @@ -17,6 +17,7 @@ import ( "bytes" "encoding/json" "os" + "runtime" "strings" "testing" "time" @@ -89,12 +90,14 @@ func TestStageDaemonExecutable(t *testing.T) { if string(got) != "test-binary" { t.Fatalf("content = %q, want test-binary", got) } - info, err := os.Stat(dst) - if err != nil { - t.Fatal(err) - } - if info.Mode().Perm() != daemonExecutablePerm { - t.Fatalf("mode = %o, want %o", info.Mode().Perm(), daemonExecutablePerm) + if runtime.GOOS != "windows" { + info, err := os.Stat(dst) + if err != nil { + t.Fatal(err) + } + if info.Mode().Perm() != daemonExecutablePerm { + t.Fatalf("mode = %o, want %o", info.Mode().Perm(), daemonExecutablePerm) + } } } diff --git a/internal/helpers/connect_exec_forwarder_test.go b/internal/helpers/connect_exec_forwarder_test.go index bd032b4f..311aca1e 100644 --- a/internal/helpers/connect_exec_forwarder_test.go +++ b/internal/helpers/connect_exec_forwarder_test.go @@ -23,6 +23,7 @@ import ( ) func TestExecForwarderRetriesMissingSessionOnce(t *testing.T) { + requirePOSIXShell(t) dir := t.TempDir() bin := filepath.Join(dir, "agent") logPath := filepath.Join(dir, "calls.log") @@ -68,6 +69,7 @@ echo fresh-ok } func TestExecForwarderStreamRetriesMissingSessionOnce(t *testing.T) { + requirePOSIXShell(t) dir := t.TempDir() bin := filepath.Join(dir, "agent-stream") logPath := filepath.Join(dir, "calls.log") @@ -113,6 +115,7 @@ printf '%s\n' '{"type":"result","result":"fresh-stream-ok"}' } func TestExecForwarderDoesNotRetryNonSessionErrors(t *testing.T) { + requirePOSIXShell(t) dir := t.TempDir() bin := filepath.Join(dir, "agent") logPath := filepath.Join(dir, "calls.log") diff --git a/internal/helpers/connect_lock.go b/internal/helpers/connect_lock.go index c443bc2c..de3dceee 100644 --- a/internal/helpers/connect_lock.go +++ b/internal/helpers/connect_lock.go @@ -14,13 +14,13 @@ package helpers import ( - "errors" "fmt" "os" "path/filepath" "strconv" "strings" - "syscall" + + eventprocess "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/process" ) // connectLockDir is a var so tests can isolate lock files. @@ -67,16 +67,8 @@ func sanitizeLockID(s string) string { }, s) } -// processAlive reports whether pid refers to a live process (unix signal-0 -// probe; a permission error still means "alive"). +// processAlive reports whether pid refers to a live process using the shared +// Unix signal-0 / Windows OpenProcess implementation. func processAlive(pid int) bool { - proc, err := os.FindProcess(pid) - if err != nil { - return false - } - err = proc.Signal(syscall.Signal(0)) - if err == nil { - return true - } - return errors.Is(err, syscall.EPERM) + return eventprocess.Alive(pid) } diff --git a/internal/helpers/connect_provider_model_test.go b/internal/helpers/connect_provider_model_test.go index a8da11fa..b8612e99 100644 --- a/internal/helpers/connect_provider_model_test.go +++ b/internal/helpers/connect_provider_model_test.go @@ -108,6 +108,7 @@ func TestForwarderProviderBaseURLDropsHaiku(t *testing.T) { // agent that prints "API Error: 422 ..." to stdout (and exits 0) must NOT have // that raw error forwarded as the answer; forward returns an actionable hint. func TestForwardReturnsHintOnAPIError(t *testing.T) { + requirePOSIXShell(t) stub := t.TempDir() bin := filepath.Join(stub, "fakeagent") // Print a provider 422 to stdout and exit 0, exactly like claude does when diff --git a/internal/helpers/connect_qoder_stream_test.go b/internal/helpers/connect_qoder_stream_test.go index 8ce8d507..4e52d196 100644 --- a/internal/helpers/connect_qoder_stream_test.go +++ b/internal/helpers/connect_qoder_stream_test.go @@ -26,6 +26,7 @@ import ( func writeQoderStreamStub(t *testing.T, dir string) string { t.Helper() + requirePOSIXShell(t) logPath := filepath.Join(dir, "qoder-starts.log") script := `#!/usr/bin/env python3 import json diff --git a/internal/helpers/connect_stream_test.go b/internal/helpers/connect_stream_test.go index c06527a8..855468dd 100644 --- a/internal/helpers/connect_stream_test.go +++ b/internal/helpers/connect_stream_test.go @@ -40,6 +40,7 @@ func TestForwardConnectTurnPreservesAttachmentsForCapableAgent(t *testing.T) { } func TestExecForwarderAllowsOnlyAttachmentDirectory(t *testing.T) { + requirePOSIXShell(t) dir := t.TempDir() logPath := filepath.Join(dir, "args.log") stub := filepath.Join(dir, "agent") diff --git a/internal/helpers/devapp_connect_test.go b/internal/helpers/devapp_connect_test.go index 691bab56..715480ed 100644 --- a/internal/helpers/devapp_connect_test.go +++ b/internal/helpers/devapp_connect_test.go @@ -223,7 +223,9 @@ func TestResolveAgentYoloMode(t *testing.T) { func TestForwarderForChannel(t *testing.T) { clearChannelEnv(t) stub := t.TempDir() - writeShellExecutable(t, stub, "codex", "exit 0\n") + if err := writeExecStub(stub, "codex"); err != nil { + t.Fatalf("write codex stub: %v", err) + } t.Setenv("PATH", stub) // DWS_AGENT_CMD covers ordinary stream-bridge channels so the test does not // depend on those binaries being installed. Codex ignores it and stays on diff --git a/internal/helpers/doc.go b/internal/helpers/doc.go index fdd49381..9e7db363 100644 --- a/internal/helpers/doc.go +++ b/internal/helpers/doc.go @@ -2543,133 +2543,7 @@ CLI 内部自动完成全部流程: # 自定义导入后的文档名称 dws doc import --file ./draft.md --name "项目周报"`, RunE: func(cmd *cobra.Command, args []string) error { - filePath := mustGetFlag(cmd, "file") - if filePath == "" && len(args) > 0 { - filePath = args[0] - } - if filePath == "" { - return fmt.Errorf("flag --file is required (or pass file path as argument)") - } - - fi, err := os.Stat(filePath) - if err != nil { - return fmt.Errorf("cannot read file %s: %w", filePath, err) - } - if fi.IsDir() { - return fmt.Errorf("%s is a directory, not a file", filePath) - } - - const maxFileSize = 20 * 1024 * 1024 - fileSize := fi.Size() - if fileSize > maxFileSize { - return fmt.Errorf("file size %d bytes exceeds 20MB limit", fileSize) - } - if fileSize == 0 { - return fmt.Errorf("file is empty: %s", filePath) - } - - ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(filePath)), ".") - supportedFormats := map[string]bool{ - "docx": true, "doc": true, "xlsx": true, "xls": true, - "md": true, "txt": true, "xmind": true, "mark": true, - } - if !supportedFormats[ext] { - return fmt.Errorf("unsupported file format %q, supported: docx, doc, xlsx, xls, md, txt, xmind, mark", ext) - } - - fileName := filepath.Base(filePath) - name, _ := cmd.Flags().GetString("name") - if name == "" { - name = strings.TrimSuffix(fileName, filepath.Ext(fileName)) - } - folder := flagOrFallback(cmd, "folder", "folder-id") - workspace := flagOrFallback(cmd, "workspace", "workspace-id") - - if deps.Caller.DryRun() { - deps.Out.PrintKeyValue("操作", "导入本地文件为在线文档") - deps.Out.PrintKeyValue("文件", filePath) - deps.Out.PrintKeyValue("名称", name) - deps.Out.PrintKeyValue("格式", ext) - deps.Out.PrintKeyValue("大小", fmt.Sprintf("%d bytes", fileSize)) - return nil - } - - ctx := context.Background() - - deps.Out.PrintInfo("[1/4] 创建导入会话...") - sessionArgs := map[string]any{ - "fileName": name, - "suffix": ext, - "fileSize": fileSize, - } - if folder != "" { - sessionArgs["targetFolderId"] = folder - } - if workspace != "" { - sessionArgs["workspaceId"] = workspace - } - - sessionText, err := callMCPToolReturnText(ctx, "create_import_session", sessionArgs) - if err != nil { - return fmt.Errorf("创建导入会话失败: %w", err) - } - - var sessionResult map[string]any - if err := json.Unmarshal([]byte(sessionText), &sessionResult); err != nil { - return fmt.Errorf("解析导入会话响应失败: %w", err) - } - sessionID, _ := sessionResult["sessionId"].(string) - uploadURL, _ := sessionResult["uploadUrl"].(string) - if sessionID == "" || uploadURL == "" { - deps.Out.PrintRaw(sessionText) - return fmt.Errorf("创建导入会话成功但缺少 sessionId 或 uploadUrl") - } - deps.Out.PrintInfo(fmt.Sprintf(" 会话已创建,sessionId: %s", sessionID)) - - deps.Out.PrintInfo("[2/4] 上传文件...") - if err := httpPutFile(ctx, uploadURL, nil, filePath, fileSize); err != nil { - return fmt.Errorf("文件上传失败 (sessionId=%s): %w", sessionID, err) - } - deps.Out.PrintInfo(" 文件上传完成") - - deps.Out.PrintInfo("[3/4] 确认导入,启动格式转换...") - confirmText, err := callMCPToolReturnText(ctx, "confirm_import", map[string]any{ - "sessionId": sessionID, - }) - if err != nil { - return fmt.Errorf("确认导入失败 (sessionId=%s): %w", sessionID, err) - } - - var confirmResult map[string]any - if err := json.Unmarshal([]byte(confirmText), &confirmResult); err != nil { - return fmt.Errorf("解析确认导入响应失败: %w", err) - } - taskID, _ := confirmResult["taskId"].(string) - if taskID == "" { - deps.Out.PrintRaw(confirmText) - return fmt.Errorf("确认导入成功但未返回 taskId") - } - deps.Out.PrintInfo(fmt.Sprintf(" 转换任务已提交,taskId: %s", taskID)) - - deps.Out.PrintInfo("[4/4] 等待格式转换完成...") - importResult, err := pollDocImportTask(ctx, taskID) - if err != nil { - return err - } - - documentURL, _ := importResult["documentUrl"].(string) - documentName, _ := importResult["documentName"].(string) - documentType, _ := importResult["documentType"].(string) - - deps.Out.PrintInfo(fmt.Sprintf("导入完成: %s", documentURL)) - deps.Out.PrintJSON(map[string]any{ - "success": true, - "taskId": taskID, - "documentUrl": documentURL, - "documentName": documentName, - "documentType": documentType, - }) - return nil + return runImportCommand(cmd, args, docImportFlowConfig()) }, } importCmd.Flags().String("file", "", "本地文件路径 (必填)") @@ -2694,43 +2568,7 @@ CLI 内部自动完成全部流程: failed 导入失败`, Example: ` dws doc import get --task-id `, RunE: func(cmd *cobra.Command, _ []string) error { - taskID := mustGetFlag(cmd, "task-id") - if taskID == "" { - return fmt.Errorf("flag --task-id is required") - } - - if deps.Caller.DryRun() { - deps.Out.PrintKeyValue("操作", "查询导入任务结果") - deps.Out.PrintKeyValue("任务ID", taskID) - return nil - } - - ctx := context.Background() - text, err := callMCPToolReturnText(ctx, "query_import_task", map[string]any{"taskId": taskID}) - if err != nil { - return err - } - - var result map[string]any - if err := json.Unmarshal([]byte(text), &result); err != nil { - deps.Out.PrintRaw(text) - return nil - } - - status, _ := result["status"].(string) - message, _ := result["message"].(string) - - switch strings.ToLower(status) { - case "completed", "processing": - deps.Out.PrintJSON(result) - return nil - default: - deps.Out.PrintJSON(result) - if message != "" { - return fmt.Errorf("导入任务失败 (status=%s): %s", status, message) - } - return fmt.Errorf("导入任务失败 (status=%s)", status) - } + return runImportGetCommand(cmd, docImportFlowConfig()) }, } importGetCmd.Flags().String("task-id", "", "导入任务 ID (必填)") @@ -3237,63 +3075,6 @@ func pollDocExportJob(ctx context.Context, jobID string) (downloadURL string, er return "", fmt.Errorf("导出任务超时:已轮询 %d 次仍在处理中 (jobId=%s),请稍后使用 dws doc export get --job-id %s 手动查询", maxPolls, jobID, jobID) } -// pollDocImportTask polls the import task status with progressive backoff. -func pollDocImportTask(ctx context.Context, taskID string) (map[string]any, error) { - const maxPolls = 30 - - pollInterval := func(attempt int) time.Duration { - switch { - case attempt <= 5: - return 2 * time.Second - case attempt <= 10: - return 5 * time.Second - case attempt <= 20: - return 10 * time.Second - default: - return 15 * time.Second - } - } - - for attempt := 1; attempt <= maxPolls; attempt++ { - interval := pollInterval(attempt) - deps.Out.PrintInfo(fmt.Sprintf(" 第 %d/%d 次查询,等待 %v ...", attempt, maxPolls, interval)) - - select { - case <-ctx.Done(): - return nil, fmt.Errorf("导入轮询被取消 (taskId=%s): %w", taskID, ctx.Err()) - case <-time.After(interval): - } - - text, queryErr := callMCPToolReturnText(ctx, "query_import_task", map[string]any{"taskId": taskID}) - if queryErr != nil { - return nil, fmt.Errorf("查询导入任务失败 (taskId=%s): %w", taskID, queryErr) - } - - var result map[string]any - if parseErr := json.Unmarshal([]byte(text), &result); parseErr != nil { - return nil, fmt.Errorf("解析查询结果失败 (taskId=%s): %w", taskID, parseErr) - } - - status, _ := result["status"].(string) - switch strings.ToLower(status) { - case "completed": - return result, nil - case "processing": - continue - case "failed": - message, _ := result["message"].(string) - if message != "" { - return nil, fmt.Errorf("导入任务失败 (taskId=%s): %s", taskID, message) - } - return nil, fmt.Errorf("导入任务失败 (taskId=%s)", taskID) - default: - continue - } - } - - return nil, fmt.Errorf("导入任务超时:已轮询 %d 次仍在处理中 (taskId=%s),请稍后使用 dws doc import get --task-id %s 手动查询", maxPolls, taskID, taskID) -} - // stripDuplicateTitle removes the leading H1 heading from markdown content // when it matches the document name (set via --name). This prevents the title // from appearing twice: once as document metadata and once in the body. diff --git a/internal/helpers/import_flow.go b/internal/helpers/import_flow.go new file mode 100644 index 00000000..cc32258b --- /dev/null +++ b/internal/helpers/import_flow.go @@ -0,0 +1,404 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" +) + +const importMaxFileSize int64 = 20 * 1024 * 1024 + +type importPollTimeoutError struct { + taskID string + maxPolls int +} + +func (e *importPollTimeoutError) Error() string { + return fmt.Sprintf("导入任务超时:已轮询 %d 次仍在处理中 (taskId=%s)", e.maxPolls, e.taskID) +} + +type importPollPolicy struct { + maxPolls int + interval func(attempt int) time.Duration + wait func(context.Context, time.Duration) error +} + +type importFlowConfig struct { + operation string + queryOperation string + supportedFormats map[string]bool + supportedFormatsText string + folderFlags []string + workspaceFlags []string + requireTarget bool + serverID string + includeNodeID bool + timeoutAsResult bool + nextCommand string + poll importPollPolicy +} + +type preparedImportFile struct { + path string + name string + extension string + size int64 + folder string + workspace string +} + +func defaultImportPollPolicy() importPollPolicy { + return importPollPolicy{ + maxPolls: 30, + interval: func(attempt int) time.Duration { + switch { + case attempt <= 5: + return 2 * time.Second + case attempt <= 10: + return 5 * time.Second + case attempt <= 20: + return 10 * time.Second + default: + return 15 * time.Second + } + }, + wait: waitForImportPoll, + } +} + +func docImportFlowConfig() importFlowConfig { + return importFlowConfig{ + operation: "导入本地文件为在线文档", + queryOperation: "查询导入任务结果", + supportedFormats: map[string]bool{"docx": true, "doc": true, "xlsx": true, "xls": true, "md": true, "txt": true, "xmind": true, "mark": true}, + supportedFormatsText: "docx, doc, xlsx, xls, md, txt, xmind, mark", + folderFlags: []string{"folder", "folder-id"}, + workspaceFlags: []string{"workspace", "workspace-id"}, + nextCommand: "dws doc import get --task-id %s", + poll: defaultImportPollPolicy(), + } +} + +func sheetImportFlowConfig() importFlowConfig { + return importFlowConfig{ + operation: "导入本地表格文件为在线电子表格", + queryOperation: "查询表格导入任务结果", + supportedFormats: map[string]bool{"xlsx": true, "xls": true}, + supportedFormatsText: "xlsx, xls", + folderFlags: []string{"folder-token", "folder"}, + workspaceFlags: []string{"workspace"}, + requireTarget: true, + serverID: "doc", + includeNodeID: true, + timeoutAsResult: true, + nextCommand: "dws sheet import get --task-id %s", + poll: defaultImportPollPolicy(), + } +} + +func waitForImportPoll(ctx context.Context, duration time.Duration) error { + timer := time.NewTimer(duration) + defer timer.Stop() + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + return nil + } +} + +func importFlagValue(cmd *cobra.Command, names ...string) string { + for _, name := range names { + if cmd.Flags().Lookup(name) == nil { + continue + } + if value, _ := cmd.Flags().GetString(name); value != "" { + return value + } + } + return "" +} + +func prepareImportFile(cmd *cobra.Command, args []string, cfg importFlowConfig) (preparedImportFile, error) { + filePath := mustGetFlag(cmd, "file") + if filePath == "" && len(args) > 0 { + filePath = args[0] + } + if filePath == "" { + return preparedImportFile{}, fmt.Errorf("flag --file is required (or pass file path as argument)") + } + + fileInfo, err := os.Stat(filePath) + if err != nil { + return preparedImportFile{}, fmt.Errorf("cannot read file %s: %w", filePath, err) + } + if fileInfo.IsDir() { + return preparedImportFile{}, fmt.Errorf("%s is a directory, not a file", filePath) + } + if fileInfo.Size() > importMaxFileSize { + return preparedImportFile{}, fmt.Errorf("file size %d bytes exceeds 20MB limit", fileInfo.Size()) + } + if fileInfo.Size() == 0 { + return preparedImportFile{}, fmt.Errorf("file is empty: %s", filePath) + } + + extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(filePath)), ".") + if !cfg.supportedFormats[extension] { + return preparedImportFile{}, fmt.Errorf("unsupported file format %q, supported: %s", extension, cfg.supportedFormatsText) + } + + name, _ := cmd.Flags().GetString("name") + if name == "" { + fileName := filepath.Base(filePath) + name = strings.TrimSuffix(fileName, filepath.Ext(fileName)) + } + folder := importFlagValue(cmd, cfg.folderFlags...) + workspace := importFlagValue(cmd, cfg.workspaceFlags...) + if cfg.requireTarget && folder == "" && workspace == "" { + return preparedImportFile{}, fmt.Errorf("--folder-token 与 --workspace 至少需要提供一个(导入目标位置)") + } + + return preparedImportFile{ + path: filePath, + name: name, + extension: extension, + size: fileInfo.Size(), + folder: folder, + workspace: workspace, + }, nil +} + +func (cfg importFlowConfig) callTool(ctx context.Context, toolName string, args map[string]any) (string, error) { + if cfg.serverID != "" { + return callMCPToolReturnTextOnServer(ctx, cfg.serverID, toolName, args) + } + return callMCPToolReturnText(ctx, toolName, args) +} + +func runImportCommand(cmd *cobra.Command, args []string, cfg importFlowConfig) error { + file, err := prepareImportFile(cmd, args, cfg) + if err != nil { + return err + } + + if deps.Caller.DryRun() { + deps.Out.PrintKeyValue("操作", cfg.operation) + deps.Out.PrintKeyValue("文件", file.path) + deps.Out.PrintKeyValue("名称", file.name) + deps.Out.PrintKeyValue("格式", file.extension) + deps.Out.PrintKeyValue("大小", fmt.Sprintf("%d bytes", file.size)) + return nil + } + + ctx := cmd.Context() + if ctx == nil { + ctx = context.Background() + } + + deps.Out.PrintInfo("[1/4] 创建导入会话...") + sessionArgs := map[string]any{ + "fileName": file.name, + "suffix": file.extension, + "fileSize": file.size, + } + if file.folder != "" { + sessionArgs["targetFolderId"] = file.folder + } + if file.workspace != "" { + sessionArgs["workspaceId"] = file.workspace + } + + sessionText, err := cfg.callTool(ctx, "create_import_session", sessionArgs) + if err != nil { + return fmt.Errorf("创建导入会话失败: %w", err) + } + var sessionResult map[string]any + if err := json.Unmarshal([]byte(sessionText), &sessionResult); err != nil { + return fmt.Errorf("解析导入会话响应失败: %w", err) + } + sessionID, _ := sessionResult["sessionId"].(string) + uploadURL, _ := sessionResult["uploadUrl"].(string) + if sessionID == "" || uploadURL == "" { + deps.Out.PrintRaw(sessionText) + return fmt.Errorf("创建导入会话成功但缺少 sessionId 或 uploadUrl") + } + deps.Out.PrintInfo(fmt.Sprintf(" 会话已创建,sessionId: %s", sessionID)) + + deps.Out.PrintInfo("[2/4] 上传文件...") + if err := httpPutFile(ctx, uploadURL, nil, file.path, file.size); err != nil { + return fmt.Errorf("文件上传失败 (sessionId=%s): %w", sessionID, err) + } + deps.Out.PrintInfo(" 文件上传完成") + + deps.Out.PrintInfo("[3/4] 确认导入,启动格式转换...") + confirmText, err := cfg.callTool(ctx, "confirm_import", map[string]any{"sessionId": sessionID}) + if err != nil { + return fmt.Errorf("确认导入失败 (sessionId=%s): %w", sessionID, err) + } + var confirmResult map[string]any + if err := json.Unmarshal([]byte(confirmText), &confirmResult); err != nil { + return fmt.Errorf("解析确认导入响应失败: %w", err) + } + taskID, _ := confirmResult["taskId"].(string) + if taskID == "" { + deps.Out.PrintRaw(confirmText) + return fmt.Errorf("确认导入成功但未返回 taskId") + } + deps.Out.PrintInfo(fmt.Sprintf(" 转换任务已提交,taskId: %s", taskID)) + + deps.Out.PrintInfo("[4/4] 等待格式转换完成...") + result, err := pollImportTask(ctx, taskID, cfg) + if err != nil { + var timeoutErr *importPollTimeoutError + if !errors.As(err, &timeoutErr) { + return err + } + if cfg.timeoutAsResult { + deps.Out.PrintInfo(timeoutErr.Error()) + _ = deps.Out.PrintJSON(map[string]any{ + "success": false, + "timed_out": true, + "taskId": taskID, + "status": "processing", + "next_command": fmt.Sprintf(cfg.nextCommand, taskID), + }) + return nil + } + return fmt.Errorf("%s,请稍后使用 %s 手动查询", timeoutErr.Error(), fmt.Sprintf(cfg.nextCommand, taskID)) + } + + documentURL, _ := result["documentUrl"].(string) + documentName, _ := result["documentName"].(string) + documentType, _ := result["documentType"].(string) + finalResult := map[string]any{ + "success": true, + "taskId": taskID, + "documentUrl": documentURL, + "documentName": documentName, + "documentType": documentType, + } + if cfg.includeNodeID { + finalResult["nodeId"] = extractNodeIDFromDocURL(documentURL) + } + deps.Out.PrintInfo(fmt.Sprintf("导入完成: %s", documentURL)) + _ = deps.Out.PrintJSON(finalResult) + return nil +} + +func runImportGetCommand(cmd *cobra.Command, cfg importFlowConfig) error { + taskID := mustGetFlag(cmd, "task-id") + if taskID == "" { + return fmt.Errorf("flag --task-id is required") + } + if deps.Caller.DryRun() { + deps.Out.PrintKeyValue("操作", cfg.queryOperation) + deps.Out.PrintKeyValue("任务ID", taskID) + return nil + } + + ctx := cmd.Context() + if ctx == nil { + ctx = context.Background() + } + text, err := cfg.callTool(ctx, "query_import_task", map[string]any{"taskId": taskID}) + if err != nil { + return err + } + + var result map[string]any + if err := json.Unmarshal([]byte(text), &result); err != nil { + deps.Out.PrintRaw(text) + return nil + } + status, _ := result["status"].(string) + message, _ := result["message"].(string) + if strings.EqualFold(status, "completed") { + if cfg.includeNodeID { + documentURL, _ := result["documentUrl"].(string) + result["nodeId"] = extractNodeIDFromDocURL(documentURL) + } + _ = deps.Out.PrintJSON(result) + return nil + } + if strings.EqualFold(status, "processing") { + _ = deps.Out.PrintJSON(result) + return nil + } + + _ = deps.Out.PrintJSON(result) + if message != "" { + return fmt.Errorf("导入任务失败 (status=%s): %s", status, message) + } + return fmt.Errorf("导入任务失败 (status=%s)", status) +} + +func pollImportTask(ctx context.Context, taskID string, cfg importFlowConfig) (map[string]any, error) { + poll := cfg.poll + if poll.maxPolls <= 0 || poll.interval == nil || poll.wait == nil { + poll = defaultImportPollPolicy() + } + for attempt := 1; attempt <= poll.maxPolls; attempt++ { + interval := poll.interval(attempt) + deps.Out.PrintInfo(fmt.Sprintf(" 第 %d/%d 次查询,等待 %v ...", attempt, poll.maxPolls, interval)) + if err := poll.wait(ctx, interval); err != nil { + return nil, fmt.Errorf("导入轮询被取消 (taskId=%s): %w", taskID, err) + } + + text, err := cfg.callTool(ctx, "query_import_task", map[string]any{"taskId": taskID}) + if err != nil { + return nil, fmt.Errorf("查询导入任务失败 (taskId=%s): %w", taskID, err) + } + var result map[string]any + if err := json.Unmarshal([]byte(text), &result); err != nil { + return nil, fmt.Errorf("解析查询结果失败 (taskId=%s): %w", taskID, err) + } + status, _ := result["status"].(string) + switch strings.ToLower(status) { + case "completed": + return result, nil + case "processing": + continue + case "failed": + message, _ := result["message"].(string) + if message != "" { + return nil, fmt.Errorf("导入任务失败 (taskId=%s): %s", taskID, message) + } + return nil, fmt.Errorf("导入任务失败 (taskId=%s)", taskID) + } + } + return nil, &importPollTimeoutError{taskID: taskID, maxPolls: poll.maxPolls} +} + +func extractNodeIDFromDocURL(rawURL string) string { + parsed, err := url.Parse(rawURL) + if err != nil || parsed.Path == "" { + return "" + } + nodeID := path.Base(strings.TrimRight(parsed.Path, "/")) + if nodeID == "." || nodeID == "/" { + return "" + } + return nodeID +} diff --git a/internal/helpers/sheet.go b/internal/helpers/sheet.go index 4039226c..a6aa188a 100644 --- a/internal/helpers/sheet.go +++ b/internal/helpers/sheet.go @@ -93,6 +93,7 @@ func newSheetCommand() *cobra.Command { dws sheet chart update 更新浮动图表 dws sheet chart delete 删除浮动图表 dws sheet export 导出表格为 xlsx(异步任务一站式:提交→轮询→可选下载) + dws sheet import 导入 xlsx/xls 为在线电子表格 dws sheet template list 获取表格模板列表 dws sheet template search 搜索表格模板 dws sheet template apply 应用表格模板创建新表格文档`, @@ -110,6 +111,7 @@ func newSheetCommand() *cobra.Command { floatImageCmds := newFloatImageCmds() chartCmd := newChartCmd() exportCmd := newExportCmd() + importCmd := newSheetImportCmd() templateCmd := newSheetTemplateCmd() tableCmds := newTableCmds() pivotTableCmd := newPivotTableCmd() @@ -134,7 +136,7 @@ func newSheetCommand() *cobra.Command { standaloneCmds = append(standaloneCmds, mediaCmds...) standaloneCmds = append(standaloneCmds, floatImageCmds...) standaloneCmds = append(standaloneCmds, tableCmds...) - standaloneCmds = append(standaloneCmds, exportCmd, batchUpdateCmd) + standaloneCmds = append(standaloneCmds, exportCmd, importCmd, batchUpdateCmd) // Register cross-product aliases for _, cmd := range standaloneCmds { diff --git a/internal/helpers/sheet_import.go b/internal/helpers/sheet_import.go new file mode 100644 index 00000000..fdd46998 --- /dev/null +++ b/internal/helpers/sheet_import.go @@ -0,0 +1,102 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import "github.com/spf13/cobra" + +const sheetImportLong = `将本地表格文件导入为一个新的钉钉在线电子表格,与 dws sheet export(导出)对称。 + +支持的文件格式 (按扩展名): + xlsx, xls Microsoft Excel 表格 + +文件大小限制: 20MB +固定导入为电子表格类型;只新建文档,不操作已有表格。 + +CLI 内部自动完成全部流程: + 1. 创建导入会话(获取 OSS 上传凭证) + 2. 上传文件到 OSS + 3. 确认导入(触发格式转换) + 4. 渐进式退避轮询等待完成(最多约 5 分钟) + +如果轮询超时仍未完成,会输出 taskId 供后续手动查询: + dws sheet import get --task-id ` + +func newSheetImportCmd() *cobra.Command { + return newSheetImportCmdWithConfig(sheetImportFlowConfig()) +} + +func newSheetImportCmdWithConfig(cfg importFlowConfig) *cobra.Command { + importCmd := &cobra.Command{ + Use: "import", + Short: "导入本地表格文件为在线电子表格 (xlsx / xls)", + Long: sheetImportLong, + Example: ` # 导入 xlsx 为在线电子表格(默认表格名取文件名) + dws sheet import --file ./quote.xlsx --folder-token + + # 指定目标文件夹与导入后表格名称 + dws sheet import --file ./report.xls --folder-token --name "月度报表" + + # 导入到指定知识库 + dws sheet import --file ./data.xls --workspace `, + RunE: func(cmd *cobra.Command, args []string) error { + return runImportCommand(cmd, args, cfg) + }, + } + addSheetImportFlags(importCmd) + + // Schema deliberately binds only runnable leaves. Keep the historical + // runnable parent for CLI compatibility, and expose the same action through + // a leaf so agents can discover and invoke the import operation. + importCreateCmd := &cobra.Command{ + Use: "create", + Short: "导入本地表格文件为在线电子表格 (xlsx / xls)", + Long: sheetImportLong, + Example: ` dws sheet import create --file ./quote.xlsx --folder-token + dws sheet import create --file ./data.xls --workspace --name "月度报表"`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return runImportCommand(cmd, args, cfg) + }, + } + addSheetImportFlags(importCreateCmd) + + importGetCmd := &cobra.Command{ + Use: "get", + Short: "查询表格导入任务结果(手动兜底)", + Long: `根据 taskId 查询表格导入任务的执行结果。 +通常不需要手动调用,dws sheet import 会自动完成轮询。 +仅在导入命令超时或中断后,用于手动查询任务状态。 + +任务状态: + processing 转换中 + completed 导入成功,返回 documentUrl + failed 导入失败`, + Example: ` dws sheet import get --task-id `, + RunE: func(cmd *cobra.Command, _ []string) error { + return runImportGetCommand(cmd, cfg) + }, + } + importGetCmd.Flags().String("task-id", "", "导入任务 ID (必填)") + importCmd.AddCommand(importCreateCmd, importGetCmd) + return importCmd +} + +func addSheetImportFlags(cmd *cobra.Command) { + cmd.Flags().String("file", "", "本地表格文件路径 (必填,支持 xlsx/xls)") + cmd.Flags().String("folder-token", "", "目标文件夹 ID 或 URL (与 --workspace 至少传一个)") + cmd.Flags().String("workspace", "", "目标知识库 ID 或 URL (与 --folder-token 至少传一个)") + cmd.Flags().StringP("name", "n", "", "导入后表格名称 (可选,默认取文件名)") + cmd.Flags().String("folder", "", "") + _ = cmd.Flags().MarkHidden("folder") +} diff --git a/internal/helpers/sheet_import_test.go b/internal/helpers/sheet_import_test.go new file mode 100644 index 00000000..fab80a83 --- /dev/null +++ b/internal/helpers/sheet_import_test.go @@ -0,0 +1,313 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helpers + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + "time" + + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" +) + +type sheetImportCall struct { + server string + tool string + args map[string]any +} + +type sheetImportCaller struct { + calls []sheetImportCall + responses map[string][]string + dryRun bool +} + +func (c *sheetImportCaller) CallTool(_ context.Context, server, tool string, args map[string]any) (*edition.ToolResult, error) { + c.calls = append(c.calls, sheetImportCall{server: server, tool: tool, args: args}) + responses := c.responses[tool] + if len(responses) == 0 { + return nil, fmt.Errorf("unexpected tool call %s/%s", server, tool) + } + c.responses[tool] = responses[1:] + return &edition.ToolResult{Content: []edition.ContentBlock{{Type: "text", Text: responses[0]}}}, nil +} + +func (*sheetImportCaller) Format() string { return "json" } +func (c *sheetImportCaller) DryRun() bool { return c.dryRun } +func (*sheetImportCaller) Fields() string { return "" } +func (*sheetImportCaller) JQ() string { return "" } + +func fastSheetImportConfig() importFlowConfig { + cfg := sheetImportFlowConfig() + cfg.poll.maxPolls = 3 + cfg.poll.interval = func(int) time.Duration { return 0 } + cfg.poll.wait = func(context.Context, time.Duration) error { return nil } + return cfg +} + +func executeSheetImportCommand(t *testing.T, caller *sheetImportCaller, cfg importFlowConfig, args ...string) (string, error) { + t.Helper() + previousDeps := deps + previousArgs := os.Args + t.Cleanup(func() { + deps = previousDeps + os.Args = previousArgs + SetHTTPPutFile(nil) + }) + + InitDeps(caller) + var output bytes.Buffer + deps.Out.w = &output + deps.Out.errW = &output + os.Args = append([]string{"dws", "sheet"}, args...) + root := newSheetImportCmdWithConfig(cfg) + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs(args) + err := root.Execute() + return output.String(), err +} + +func writeImportFixture(t *testing.T, ext string) string { + t.Helper() + filePath := filepath.Join(t.TempDir(), "sales."+ext) + if err := os.WriteFile(filePath, []byte("test workbook"), 0o600); err != nil { + t.Fatalf("write fixture: %v", err) + } + return filePath +} + +func TestSheetImportRejectsInvalidInputBeforeRemoteCall(t *testing.T) { + tests := []struct { + name string + fileExt string + args func(string) []string + wantErr string + }{ + { + name: "target is required", + fileExt: "xlsx", + args: func(path string) []string { return []string{"--file", path} }, + wantErr: "--folder-token 与 --workspace 至少需要提供一个", + }, + { + name: "only excel formats are accepted", + fileExt: "csv", + args: func(path string) []string { return []string{"--file", path, "--workspace", "ws1"} }, + wantErr: `unsupported file format "csv", supported: xlsx, xls`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caller := &sheetImportCaller{} + _, err := executeSheetImportCommand(t, caller, fastSheetImportConfig(), tt.args(writeImportFixture(t, tt.fileExt))...) + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("error = %v, want containing %q", err, tt.wantErr) + } + if len(caller.calls) != 0 { + t.Fatalf("remote calls = %d, want 0", len(caller.calls)) + } + }) + } +} + +func TestSheetImportRunsSharedDocImportFlow(t *testing.T) { + caller := &sheetImportCaller{responses: map[string][]string{ + "create_import_session": {`{"sessionId":"session-1","uploadUrl":"https://upload.example.test/object"}`}, + "confirm_import": {`{"taskId":"task-1"}`}, + "query_import_task": {`{"status":"completed","documentUrl":"https://alidocs.dingtalk.com/i/nodes/node-1?from=test","documentName":"Sales","documentType":"1"}`}, + }} + SetHTTPPutFile(func(_ context.Context, uploadURL string, _ map[string]string, _ string, size int64) error { + if uploadURL != "https://upload.example.test/object" || size <= 0 { + return fmt.Errorf("unexpected upload url=%s size=%d", uploadURL, size) + } + return nil + }) + + output, err := executeSheetImportCommand(t, caller, fastSheetImportConfig(), + "--file", writeImportFixture(t, "xlsx"), + "--workspace", "workspace-1", + "--name", "Sales", + ) + if err != nil { + t.Fatalf("sheet import returned error: %v", err) + } + + wantTools := []string{"create_import_session", "confirm_import", "query_import_task"} + if len(caller.calls) != len(wantTools) { + t.Fatalf("calls = %#v", caller.calls) + } + for i, call := range caller.calls { + if call.server != "doc" || call.tool != wantTools[i] { + t.Fatalf("call[%d] = %#v, want doc/%s", i, call, wantTools[i]) + } + } + wantSession := map[string]any{ + "fileName": "Sales", + "suffix": "xlsx", + "fileSize": int64(len("test workbook")), + "workspaceId": "workspace-1", + } + if !reflect.DeepEqual(caller.calls[0].args, wantSession) { + t.Fatalf("session args = %#v, want %#v", caller.calls[0].args, wantSession) + } + + if !strings.Contains(output, `"nodeId": "node-1"`) || !strings.Contains(output, `"success": true`) { + t.Fatalf("output missing success contract:\n%s", output) + } +} + +func TestSheetImportCreateLeafMatchesLegacyDryRun(t *testing.T) { + filePath := writeImportFixture(t, "xlsx") + legacy, err := executeSheetImportCommand(t, &sheetImportCaller{dryRun: true}, fastSheetImportConfig(), + "--file", filePath, "--workspace", "workspace-1", "--name", "Sales") + if err != nil { + t.Fatalf("legacy sheet import dry-run returned error: %v", err) + } + leaf, err := executeSheetImportCommand(t, &sheetImportCaller{dryRun: true}, fastSheetImportConfig(), + "create", "--file", filePath, "--workspace", "workspace-1", "--name", "Sales") + if err != nil { + t.Fatalf("sheet import create dry-run returned error: %v", err) + } + if leaf != legacy { + t.Fatalf("create leaf output differs from legacy entry:\nlegacy:\n%s\nleaf:\n%s", legacy, leaf) + } + + root := newSheetImportCmdWithConfig(fastSheetImportConfig()) + create, _, err := root.Find([]string{"create"}) + if err != nil || create == nil { + t.Fatalf("find create leaf: command=%v err=%v", create, err) + } + if !create.Runnable() || create.HasSubCommands() { + t.Fatalf("create command must be a runnable leaf: runnable=%v hasSubcommands=%v", create.Runnable(), create.HasSubCommands()) + } +} + +func TestSheetImportTimeoutIsStructuredSuccessExit(t *testing.T) { + cfg := fastSheetImportConfig() + cfg.poll.maxPolls = 2 + caller := &sheetImportCaller{responses: map[string][]string{ + "create_import_session": {`{"sessionId":"session-1","uploadUrl":"https://upload.example.test/object"}`}, + "confirm_import": {`{"taskId":"task-1"}`}, + "query_import_task": { + `{"status":"processing"}`, + `{"status":"processing"}`, + }, + }} + SetHTTPPutFile(func(context.Context, string, map[string]string, string, int64) error { return nil }) + + output, err := executeSheetImportCommand(t, caller, cfg, + "--file", writeImportFixture(t, "xls"), "--folder-token", "folder-1") + if err != nil { + t.Fatalf("timeout should exit successfully, got %v", err) + } + for _, fragment := range []string{ + `"success": false`, + `"timed_out": true`, + `"status": "processing"`, + `"next_command": "dws sheet import get --task-id task-1"`, + } { + if !strings.Contains(output, fragment) { + t.Fatalf("output missing %s:\n%s", fragment, output) + } + } +} + +func TestSheetImportGetAddsNodeIDAndUsesDocServer(t *testing.T) { + caller := &sheetImportCaller{responses: map[string][]string{ + "query_import_task": {`{"status":"completed","documentUrl":"https://alidocs.dingtalk.com/i/nodes/node-2/","documentType":"1"}`}, + }} + output, err := executeSheetImportCommand(t, caller, fastSheetImportConfig(), "get", "--task-id", "task-2") + if err != nil { + t.Fatalf("sheet import get returned error: %v", err) + } + if len(caller.calls) != 1 || caller.calls[0].server != "doc" || caller.calls[0].tool != "query_import_task" { + t.Fatalf("calls = %#v", caller.calls) + } + if !reflect.DeepEqual(caller.calls[0].args, map[string]any{"taskId": "task-2"}) { + t.Fatalf("args = %#v", caller.calls[0].args) + } + + var result map[string]any + jsonStart := strings.Index(output, "{") + if jsonStart < 0 || json.Unmarshal([]byte(output[jsonStart:]), &result) != nil { + t.Fatalf("invalid JSON output:\n%s", output) + } + if result["nodeId"] != "node-2" { + t.Fatalf("nodeId = %#v, want node-2", result["nodeId"]) + } +} + +func TestDocImportConfigPreservesExistingContract(t *testing.T) { + cfg := docImportFlowConfig() + for _, ext := range []string{"docx", "doc", "xlsx", "xls", "md", "txt", "xmind", "mark"} { + if !cfg.supportedFormats[ext] { + t.Errorf("doc import no longer supports %s", ext) + } + } + if cfg.requireTarget || cfg.includeNodeID || cfg.timeoutAsResult { + t.Fatalf("doc import compatibility changed: %#v", cfg) + } +} + +func TestDocImportDryRunStillAcceptsMarkdownWithoutTarget(t *testing.T) { + previousDeps := deps + previousArgs := os.Args + t.Cleanup(func() { + deps = previousDeps + os.Args = previousArgs + }) + + caller := &sheetImportCaller{dryRun: true} + InitDeps(caller) + var output bytes.Buffer + deps.Out.w = &output + filePath := writeImportFixture(t, "md") + os.Args = []string{"dws", "doc", "import", "--file", filePath} + root := newDocCommand() + root.SilenceErrors = true + root.SilenceUsage = true + root.SetArgs([]string{"import", "--file", filePath}) + if err := root.Execute(); err != nil { + t.Fatalf("doc import dry-run changed behavior: %v", err) + } + if len(caller.calls) != 0 { + t.Fatalf("dry-run made %d remote calls", len(caller.calls)) + } + if !strings.Contains(output.String(), "导入本地文件为在线文档") || !strings.Contains(output.String(), "md") { + t.Fatalf("unexpected dry-run output:\n%s", output.String()) + } +} + +func TestExtractNodeIDFromDocURL(t *testing.T) { + tests := map[string]string{ + "https://alidocs.dingtalk.com/i/nodes/node-1": "node-1", + "https://alidocs.dingtalk.com/i/nodes/node-2/?from=test": "node-2", + "https://alidocs.dingtalk.com/i/nodes/node-3#sheet=Sheet1": "node-3", + "": "", + } + for rawURL, want := range tests { + if got := extractNodeIDFromDocURL(rawURL); got != want { + t.Errorf("extractNodeIDFromDocURL(%q) = %q, want %q", rawURL, got, want) + } + } +} diff --git a/internal/helpers/wukong_sync_commands_test.go b/internal/helpers/wukong_sync_commands_test.go index f804f5ae..74fd5beb 100644 --- a/internal/helpers/wukong_sync_commands_test.go +++ b/internal/helpers/wukong_sync_commands_test.go @@ -98,6 +98,12 @@ func TestWukongSyncDocCommands(t *testing.T) { func TestWukongSyncSheetCommands(t *testing.T) { root := newSheetCommand() + importCmd := requireWukongSyncCommand(t, root, "import") + requireWukongSyncFlags(t, importCmd, "file", "folder-token", "workspace", "name", "folder") + + importGetCmd := requireWukongSyncCommand(t, root, "import", "get") + requireWukongSyncFlags(t, importGetCmd, "task-id") + tableGetCmd := requireWukongSyncCommand(t, root, "table-get") requireWukongSyncFlags(t, tableGetCmd, "node", "sheet-id", "range", "no-header") diff --git a/scripts/README.md b/scripts/README.md index d3f1d204..381a2dd5 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -6,6 +6,7 @@ These repo-local entrypoints are the supported shell entrypoints for building, t - `make test`: run `go test ./...` - `make lint`: run formatting checks and required `golangci-lint` - `make fmt`: format Go source files under `cmd/`, `internal/`, and `test/` +- `make policy`: reuse the current `dws` binary and run the complete policy suite (`make build` first) - `make package`: build all release artifacts locally via `goreleaser --snapshot` - `make release`: build and publish a release via `goreleaser` @@ -15,4 +16,5 @@ Script groups: - Product convenience installers: `./scripts/install-devapp.sh`, `./scripts/install-devapp.ps1`, `./scripts/install-event.sh` - Dev helpers: `./scripts/dev/build.sh`, `./scripts/dev/lint.sh`, `./scripts/dev/ci-local.sh`, `./scripts/dev/run-mock-e2e.sh`, `./scripts/dev/coverage.sh` - Policy checks: `./scripts/policy/check-schema-command-registry.sh`, `./scripts/policy/check-generated-drift.sh`, `./scripts/policy/check-command-surface.sh`, `./scripts/policy/check-schema-catalog.sh`, `./scripts/policy/check-schema-binary.sh`, `./scripts/policy/check-open-source-assets.sh` +- Policy runtime files: set `DWS_POLICY_TMPDIR` to override the default `.worktrees/policy-tmp` workspace - Release helpers: `./scripts/release/post-goreleaser.sh`, `./scripts/release/verify-package-managers.sh`, `./scripts/release/publish-homebrew-formula.sh` diff --git a/scripts/policy/check-authoritative-interface-baselines.sh b/scripts/policy/check-authoritative-interface-baselines.sh index 57bc563b..9db4f9cb 100755 --- a/scripts/policy/check-authoritative-interface-baselines.sh +++ b/scripts/policy/check-authoritative-interface-baselines.sh @@ -34,12 +34,15 @@ done [ -n "$BASE_REF" ] || { usage; exit 2; } cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || { printf 'error: interface authority is not available locally: %s\n' "$BASE_REF" >&2 exit 2 } -TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-interface-authority.XXXXXX")" +TMP_ROOT="$(policy_runtime_mktemp_dir dws-interface-authority)" BASE_WORKTREE="$TMP_ROOT/base-worktree" BASELINE="$TMP_ROOT/merge-base.txt" CANDIDATE_BIN="$TMP_ROOT/interface-baseline" diff --git a/scripts/policy/check-authoritative-schema-compatibility.sh b/scripts/policy/check-authoritative-schema-compatibility.sh index 32b63e01..aeb37aa6 100755 --- a/scripts/policy/check-authoritative-schema-compatibility.sh +++ b/scripts/policy/check-authoritative-schema-compatibility.sh @@ -35,6 +35,9 @@ done [ -n "$BASE_REF" ] || { usage; exit 2; } cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || { printf 'error: Schema authority is not available locally: %s\n' "$BASE_REF" >&2 exit 2 @@ -44,7 +47,7 @@ if [ ! -x "$BIN" ]; then exit 2 fi -TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-schema-authority.XXXXXX")" +TMP_ROOT="$(policy_runtime_mktemp_dir dws-schema-authority)" BASE_WORKTREE="$TMP_ROOT/base-worktree" BASE_BIN="$TMP_ROOT/base-dws" BASE_RAW="$TMP_ROOT/base-schema.json" diff --git a/scripts/policy/check-command-surface.sh b/scripts/policy/check-command-surface.sh index e5cac427..514c35d6 100755 --- a/scripts/policy/check-command-surface.sh +++ b/scripts/policy/check-command-surface.sh @@ -5,6 +5,7 @@ set -eu # Stub placeholder — extend with actual surface comparison logic as needed. ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" +BIN="${DWS_BIN:-$ROOT/dws}" STRICT=0 if [ "${1:-}" = "--strict" ]; then @@ -13,32 +14,27 @@ fi cd "$ROOT" -# Verify that all expected top-level commands exist in the built binary -if command -v go >/dev/null 2>&1; then - BIN_PATH="/tmp/dws-surface-check" - trap 'rm -f "$BIN_PATH"' EXIT HUP INT TERM - go build -ldflags="-s -w" -o "$BIN_PATH" ./cmd 2>/dev/null || { echo "build failed"; exit 1; } +if [ ! -x "$BIN" ]; then + printf 'error: dws binary not found at %s (run make build first)\n' "$BIN" >&2 + exit 2 +fi - # These utility commands are stable across the current open-source CLI shape. - EXPECTED_COMMANDS="auth cache completion version" - missing=0 - for cmd in $EXPECTED_COMMANDS; do - # Use `help ` so hidden commands are also validated. - if ! "$BIN_PATH" help "$cmd" >/dev/null 2>&1; then - printf 'missing command: %s\n' "$cmd" >&2 - missing=$((missing + 1)) - fi - done - - # Full Schema canonical/primary/alias delivery is exercised once by - # check-schema-binary.sh. Keep this check focused on the basic command tree. - rm -f "$BIN_PATH" - trap - EXIT HUP INT TERM - - if [ "$STRICT" -eq 1 ] && [ "$missing" -gt 0 ]; then - printf 'command surface check: %d missing commands\n' "$missing" >&2 - exit 1 +# These utility commands are stable across the current open-source CLI shape. +EXPECTED_COMMANDS="auth cache completion version" +missing=0 +for cmd in $EXPECTED_COMMANDS; do + # Use `help ` so hidden commands are also validated. + if ! "$BIN" help "$cmd" >/dev/null 2>&1; then + printf 'missing command: %s\n' "$cmd" >&2 + missing=$((missing + 1)) fi +done + +# Full Schema canonical/primary/alias delivery is exercised once by +# check-schema-binary.sh. Keep this check focused on the basic command tree. +if [ "$STRICT" -eq 1 ] && [ "$missing" -gt 0 ]; then + printf 'command surface check: %d missing commands\n' "$missing" >&2 + exit 1 fi printf 'command surface check: ok\n' diff --git a/scripts/policy/check-coverage-gate.sh b/scripts/policy/check-coverage-gate.sh index 32815ceb..4b1d7c8d 100755 --- a/scripts/policy/check-coverage-gate.sh +++ b/scripts/policy/check-coverage-gate.sh @@ -62,13 +62,21 @@ done [ -n "$BASE_REF" ] || { usage; exit 2; } cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || { printf 'error: coverage base ref is not available locally: %s\n' "$BASE_REF" >&2 exit 2 } +TMP_ROOT="$(policy_runtime_mktemp_dir dws-coverage-gate)" +CHECKER="$TMP_ROOT/coverage-gate" +trap 'rm -rf "$TMP_ROOT"' EXIT HUP INT TERM +go build -o "$CHECKER" ./scripts/policy/coverage-gate + module="$(go list -m -f '{{.Path}}')" -set -- go run ./scripts/policy/coverage-gate \ +set -- "$CHECKER" \ --diff-profile "$DIFF_PROFILE" \ --base-ref "$BASE_REF" \ --module "$module" \ diff --git a/scripts/policy/check-generated-drift.sh b/scripts/policy/check-generated-drift.sh index e9eda827..229c543a 100755 --- a/scripts/policy/check-generated-drift.sh +++ b/scripts/policy/check-generated-drift.sh @@ -7,9 +7,17 @@ set -eu ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" tmp="$(mktemp -d)" -trap 'rm -rf "$tmp"' EXIT HUP INT TERM +exec_tmp="$(policy_runtime_mktemp_dir dws-generated-drift)" +metadata_generator="$exec_tmp/schema-agent-metadata" +catalog_generator="$exec_tmp/schema-catalog" +trap 'rm -rf "$tmp" "$exec_tmp"' EXIT HUP INT TERM + +go build -o "$metadata_generator" ./internal/generator/cmd_schema_agent_metadata +go build -a -o "$catalog_generator" ./internal/generator/cmd_schema_catalog # CommandRegistry is a reviewed input, never a generated artifact. Keep an # independent byte-for-byte guard around the ordinary downstream generators. @@ -26,7 +34,7 @@ audit_tmp="$tmp/audit.json" catalog_tmp="$tmp/catalog.json" catalog_tmp_second="$tmp/catalog-second.json" -go run ./internal/generator/cmd_schema_agent_metadata \ +"$metadata_generator" \ -root . \ -registry internal/cli/schema_command_registry.json \ -output-dir "$metadata_tmp" \ @@ -46,11 +54,11 @@ if ! cmp -s internal/cli/schema_agent_metadata_audit.json "$audit_tmp"; then exit 1 fi -go run -a ./internal/generator/cmd_schema_catalog \ +"$catalog_generator" \ -root . \ -output "$catalog_tmp" -go run -a ./internal/generator/cmd_schema_catalog \ +"$catalog_generator" \ -root . \ -output "$catalog_tmp_second" diff --git a/scripts/policy/check-interface-baseline.sh b/scripts/policy/check-interface-baseline.sh index a0f6dd39..08eb5732 100755 --- a/scripts/policy/check-interface-baseline.sh +++ b/scripts/policy/check-interface-baseline.sh @@ -7,12 +7,17 @@ set -eu ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" BASELINE="${INTERFACE_BASELINE:-$ROOT/test/fixtures/cli-interface-baseline.txt}" -SNAPSHOT_HOME="$(mktemp -d)" -SNAPSHOT_BIN="$(mktemp)" -CURRENT="$(mktemp)" -trap 'rm -rf "$CURRENT" "$SNAPSHOT_HOME" "$SNAPSHOT_BIN"' EXIT - cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + +TMP_ROOT="$(policy_runtime_mktemp_dir dws-interface-baseline)" +SNAPSHOT_HOME="$TMP_ROOT/home" +SNAPSHOT_BIN="$TMP_ROOT/interface-baseline" +CURRENT="$TMP_ROOT/current.txt" +mkdir -p "$SNAPSHOT_HOME" +trap 'rm -rf "$TMP_ROOT"' EXIT HUP INT TERM + # Compile with the caller's normal Go cache, then isolate only the execution # HOME so user-installed DWS plugins cannot alter the public command tree. go build -o "$SNAPSHOT_BIN" ./scripts/policy/interface-baseline diff --git a/scripts/policy/check-schema-binary.sh b/scripts/policy/check-schema-binary.sh index 840c0d8f..71598867 100755 --- a/scripts/policy/check-schema-binary.sh +++ b/scripts/policy/check-schema-binary.sh @@ -2,16 +2,26 @@ set -eu ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" +BIN="${DWS_BIN:-$ROOT/dws}" cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + +if [ ! -x "$BIN" ]; then + printf 'error: dws binary not found at %s (run make build first)\n' "$BIN" >&2 + exit 2 +fi tmp="$(mktemp -d)" -trap 'rm -rf "$tmp"' EXIT HUP INT TERM +exec_tmp="$(policy_runtime_mktemp_dir dws-schema-binary)" +smoke_generator="$exec_tmp/schema-registry-smoke" +trap 'rm -rf "$tmp" "$exec_tmp"' EXIT HUP INT TERM -go build -o "$tmp/dws" ./cmd -go run ./internal/generator/cmd_schema_registry_smoke >"$tmp/smoke-vector.json" +go build -o "$smoke_generator" ./internal/generator/cmd_schema_registry_smoke +"$smoke_generator" >"$tmp/smoke-vector.json" -"$tmp/dws" schema list --format json >"$tmp/list.json" -"$tmp/dws" schema --all --format json >"$tmp/all.json" +"$BIN" schema list --format json >"$tmp/list.json" +"$BIN" schema --all --format json >"$tmp/all.json" if ! jq -e ' .version == 1 and @@ -111,8 +121,8 @@ while IFS= read -r command; do canonical="$(printf '%s\n' "$command" | jq -r '.canonical_path')" primary="$(printf '%s\n' "$command" | jq -r '.primary_cli_path')" - "$tmp/dws" schema "$canonical" --format json >"$tmp/canonical.json" - "$tmp/dws" schema --cli-path "$primary" --format json >"$tmp/primary.json" + "$BIN" schema "$canonical" --format json >"$tmp/canonical.json" + "$BIN" schema --cli-path "$primary" --format json >"$tmp/primary.json" jq -S --arg canonical "$canonical" ' [.products[].tools[] | select(.canonical_path == $canonical)] | if length == 1 then .[0] else null end @@ -132,8 +142,8 @@ alias_command="$(jq -c 'first(.commands[] | select((.alias_cli_paths | length) > if [ -n "$alias_command" ]; then canonical="$(printf '%s\n' "$alias_command" | jq -r '.canonical_path')" alias_path="$(printf '%s\n' "$alias_command" | jq -r '.alias_cli_paths[0]')" - "$tmp/dws" schema "$canonical" --format json >"$tmp/canonical.json" - "$tmp/dws" schema --cli-path "$alias_path" --format json >"$tmp/alias.json" + "$BIN" schema "$canonical" --format json >"$tmp/canonical.json" + "$BIN" schema --cli-path "$alias_path" --format json >"$tmp/alias.json" jq -S 'del(.cli_path, .is_alias)' "$tmp/canonical.json" >"$tmp/canonical-content.json" jq -S 'del(.cli_path, .is_alias)' "$tmp/alias.json" >"$tmp/alias-content.json" if ! cmp -s "$tmp/canonical-content.json" "$tmp/alias-content.json"; then diff --git a/scripts/policy/check-schema-catalog.sh b/scripts/policy/check-schema-catalog.sh index 1cac1a39..6c1dd185 100755 --- a/scripts/policy/check-schema-catalog.sh +++ b/scripts/policy/check-schema-catalog.sh @@ -4,6 +4,8 @@ set -eu ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" cd "$ROOT" . "$ROOT/scripts/policy/search.sh" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" if [ -e internal/cli/schema_native_contracts.go ] || [ -e internal/cli/schema_native_contracts_generated.go ] || diff --git a/scripts/policy/check-schema-command-registry.sh b/scripts/policy/check-schema-command-registry.sh index 644ad2dc..1a1bfe61 100755 --- a/scripts/policy/check-schema-command-registry.sh +++ b/scripts/policy/check-schema-command-registry.sh @@ -8,6 +8,8 @@ set -eu ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" cd "$ROOT" . "$ROOT/scripts/policy/search.sh" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" if [ -e internal/cli/schema_native_contracts.go ] || [ -e internal/cli/schema_native_contracts_generated.go ] || diff --git a/scripts/policy/check-skill-commands.sh b/scripts/policy/check-skill-commands.sh index d5ef3cfa..56ceee55 100755 --- a/scripts/policy/check-skill-commands.sh +++ b/scripts/policy/check-skill-commands.sh @@ -2,10 +2,15 @@ set -eu ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)" -CHECKER="$(mktemp)" -CHECK_HOME="$(mktemp -d)" -trap 'rm -rf "$CHECKER" "$CHECK_HOME"' EXIT - cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + +TMP_ROOT="$(policy_runtime_mktemp_dir dws-skill-command-check)" +CHECKER="$TMP_ROOT/skill-command-check" +CHECK_HOME="$TMP_ROOT/home" +mkdir -p "$CHECK_HOME" +trap 'rm -rf "$TMP_ROOT"' EXIT HUP INT TERM + go build -o "$CHECKER" ./scripts/policy/skill-command-check HOME="$CHECK_HOME" DWS_LANG=zh "$CHECKER" diff --git a/scripts/policy/policy-runtime.sh b/scripts/policy/policy-runtime.sh new file mode 100644 index 00000000..96299f5e --- /dev/null +++ b/scripts/policy/policy-runtime.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# Shared runtime workspace for policy helpers that compile and execute Go +# programs. Callers may override DWS_POLICY_TMPDIR and GOTMPDIR. +policy_prepare_runtime() { + policy_root="$1" + DWS_POLICY_TMPDIR="${DWS_POLICY_TMPDIR:-$policy_root/.worktrees/policy-tmp}" + mkdir -p "$DWS_POLICY_TMPDIR/go" + DWS_POLICY_TMPDIR="$(CDPATH= cd -- "$DWS_POLICY_TMPDIR" && pwd)" + export DWS_POLICY_TMPDIR + + if [ -z "${GOTMPDIR:-}" ]; then + GOTMPDIR="$DWS_POLICY_TMPDIR/go" + fi + mkdir -p "$GOTMPDIR" + GOTMPDIR="$(CDPATH= cd -- "$GOTMPDIR" && pwd)" + export GOTMPDIR +} + +policy_runtime_mktemp_dir() { + prefix="$1" + mktemp -d "$DWS_POLICY_TMPDIR/${prefix}.XXXXXX" +} diff --git a/scripts/policy/run-platform-coverage-gate.sh b/scripts/policy/run-platform-coverage-gate.sh index 6f2eed1d..dde2bbd8 100755 --- a/scripts/policy/run-platform-coverage-gate.sh +++ b/scripts/policy/run-platform-coverage-gate.sh @@ -39,19 +39,24 @@ done [ -n "$BASE_REF" ] && [ -n "$PROFILE" ] || { usage; exit 2; } cd "$ROOT" +. "$ROOT/scripts/policy/policy-runtime.sh" +policy_prepare_runtime "$ROOT" + git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || { printf 'error: coverage base ref is not available locally: %s\n' "$BASE_REF" >&2 exit 2 } -TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-platform-coverage.XXXXXX")" +TMP_ROOT="$(policy_runtime_mktemp_dir dws-platform-coverage)" PACKAGES="$TMP_ROOT/packages" SORTED_PACKAGES="$TMP_ROOT/packages.sorted" +TEST_PACKAGES="$TMP_ROOT/test-packages" cleanup() { rm -rf "$TMP_ROOT" } trap cleanup EXIT HUP INT TERM : >"$PACKAGES" +: >"$TEST_PACKAGES" git diff --name-only --diff-filter=ACMR "$BASE_REF" -- '*.go' | while IFS= read -r file; do @@ -70,9 +75,11 @@ git diff --name-only --diff-filter=ACMR "$BASE_REF" -- '*.go' | sort -u "$PACKAGES" >"$SORTED_PACKAGES" set -- COVERPKG="" +FIRST_PACKAGE="" while IFS= read -r package; do if [ -n "$package" ]; then set -- "$@" "$package" + [ -n "$FIRST_PACKAGE" ] || FIRST_PACKAGE="$package" if [ -z "$COVERPKG" ]; then COVERPKG="$package" else @@ -87,15 +94,33 @@ else printf 'native changed coverage packages:' printf ' %s' "$@" printf '\n' + for package in "$@"; do + tests="$(go test -list '^(TestAllShortcuts|TestCrossPlatformCoverage)' "$package")" + if printf '%s\n' "$tests" | grep -Eq '^(TestAllShortcuts|TestCrossPlatformCoverage)'; then + printf '%s\n' "$package" >>"$TEST_PACKAGES" + fi + done + set -- + while IFS= read -r package; do + [ -n "$package" ] && set -- "$@" "$package" + done <"$TEST_PACKAGES" + if [ "$#" -eq 0 ]; then + # Keep a valid zero-coverage profile so the fail-closed gate can report + # the missing coverage when no explicit platform test exists. + set -- "$FIRST_PACKAGE" + fi + printf 'native coverage test packages:' + printf ' %s' "$@" + printf '\n' # The shortcut integration harness lives in internal/shortcut/builtin but # executes code from every product package. Cross-package instrumentation is # required for those real executions to count toward the owning source file. # - # Platform runners intentionally execute only the exhaustive shortcut - # harness and explicitly cross-platform tests. All changed production - # packages remain instrumented, so unexercised statements still count as - # uncovered. This keeps the native Windows gate independent of unrelated - # tests that require Unix permissions, shell scripts, or process semantics. + # Platform runners intentionally execute only packages that actually declare + # an exhaustive shortcut harness or explicit cross-platform tests. All + # changed production packages remain instrumented via coverpkg, so unexercised + # statements still count as uncovered. Avoiding zero-test driver binaries also + # keeps profiles small enough for the fail-closed gate to process reliably. go test -count=1 -timeout="$TIMEOUT" -run '^(TestAllShortcuts|TestCrossPlatformCoverage)' \ -coverpkg="$COVERPKG" -coverprofile="$PROFILE" -covermode=atomic "$@" fi diff --git a/skills/mono/references/products/aitable.md b/skills/mono/references/products/aitable.md index c22a23f0..144f8567 100644 --- a/skills/mono/references/products/aitable.md +++ b/skills/mono/references/products/aitable.md @@ -105,12 +105,14 @@ | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| +| `workflow create` | 创建并发布工作流 | `--base-id` `--dsl` | `--dsl` 为完整 workflow-dsl/v1;非幂等,不自动重试 | +| `workflow update` | 更新并发布工作流 | `--base-id` `--workflow-id` `--dsl` | 全量替换,先 get 留底;检查 `data.valid/issues` | | `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` | | `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) | | `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` | | `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` `--yes` | 影响业务自动化,建议二次确认;status 变 STOP | -> **当前不支持通过 CLI 新建/修改/删除工作流**,请去 AI 表格 Web 端(数据表页面 → 自动化)配置。 +> 当前支持创建、更新、查询和启停;删除、运行历史及手动触发仍未开放。 ### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) diff --git a/skills/mono/references/products/aitable/aitable-workflow.md b/skills/mono/references/products/aitable/aitable-workflow.md index a223155a..1db8249b 100644 --- a/skills/mono/references/products/aitable/aitable-workflow.md +++ b/skills/mono/references/products/aitable/aitable-workflow.md @@ -1,22 +1,100 @@ # workflow — 自动化工作流管理 -启停 / 查看 / 列出 Base 下的自动化工作流(在 AI 表格 Web 端配置的 "当 X 时自动 Y" 流程)。 -适用场景:用户问 "停掉这个流程"、"看下都有哪些自动化流程"、"流程 X 的配置是什么"。 +创建 / 更新 / 启停 / 查看 / 列出 Base 下的自动化工作流("当 X 时自动 Y" 流程)。 +适用场景:用户要求创建自动化、修改流程、停掉流程、查询已有流程或恢复运行。 ## 命令一览 | 命令 | 用途 | |------|------| +| `workflow create` | 创建并发布自动化工作流 | +| `workflow update` | 更新并发布已有自动化工作流 | | `workflow list` | 列出 Base 下所有工作流(含状态/创建人/最后修改时间),支持分页 | | `workflow get` | 获取单个工作流详情(含 flowSchema 完整节点定义) | | `workflow enable` | 启用指定工作流(按配置的触发条件自动执行) | | `workflow disable` | 禁用指定工作流(高危,建议 `--yes` 二次确认) | > 所有子命令的 `--base-id` 必填(可用隐藏别名 `--base`)。 -> 当前**不支持通过 CLI 新建工作流**,请在 AI 表格 Web 端配置好后用 `workflow list` 拿到 ID 再启停。 + +## DSL 入参格式与最小 Demo + +`workflow create/update` 的 `--dsl` 接收完整的 `workflow-dsl/v1` JSON object,不是局部 patch。支持内联 JSON、`@文件路径` 或 `-` 从 stdin 读取。 + +复杂工作流应先用 `table get` / `field get` / `view list` 确认真实 `sheetId`、`fieldId`、`viewId`,并检查所有 `next`、`loopEntry`、branch `to` 和 ref。下面是一个不依赖数据表字段的最小定时消息工作流: + +```json +{ + "version": "workflow-dsl/v1", + "name": "每日提醒", + "description": "可选说明", + "trigger": "start", + "steps": { + "start": { + "type": "Scheduled", + "next": "send", + "data": { + "mode": "daily", + "time": "09:00", + "timezone": "GMT+08:00" + } + }, + "send": { + "type": "SendMessage", + "data": { + "title": "定时任务已触发", + "to": {"users": [{"ref": "$.system_node.ownerUserId"}]} + } + } + } +} +``` + +create 和 update 都必须同时满足 `status=success`、`data.valid=true`、`data.issues=[]` 才表示发布成功。 ## 命令详情 +### workflow create — 创建并发布工作流 + +```bash +dws aitable workflow create \ + --base-id BASE_ID \ + --dsl @workflow.json \ + --locale zh-CN \ + --format json + +cat workflow.json | dws aitable workflow create --base-id BASE_ID --dsl - --format json +``` + +| flag | 必填 | 说明 | +|------|------|------| +| `--base-id` | 是 | 所属 Base ID | +| `--dsl` | 是 | workflow-dsl/v1 JSON object;支持内联、`@filepath`、`-` stdin | +| `--locale` | 否 | 请求语言,如 `zh-CN` / `zh_CN` | + +`create` 非幂等,CLI 不自动重试。若网络中断导致结果不确定,先 `workflow list` 按名称确认是否已经创建,再决定是否重试。`status=success` 只说明服务正常返回;`data.valid=false` 仍是发布失败,必须读取 `issues`。 + +### workflow update — 更新并发布工作流 + +```bash +# 先留底,再提交完整目标 DSL +dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/workflow-backup.json +dws aitable workflow update \ + --base-id BASE_ID \ + --workflow-id WORKFLOW_ID \ + --dsl @workflow.json \ + --locale zh_CN \ + --format json +``` + +| flag | 必填 | 说明 | +|------|------|------| +| `--base-id` | 是 | 所属 Base ID | +| `--workflow-id` | 是 | 目标工作流 ID,对应 list 的 `flowId` | +| `--dsl` | 是 | 完整目标 workflow-dsl/v1 JSON object;支持内联、`@filepath`、`-` stdin | +| `--locale` | 否 | 请求语言,如 `zh-CN` / `zh_CN` | + +update 返回结构与 create 相同,并会对瞬态错误重试。成功时 `data.flowId` 应与传入的工作流 ID 一致;随后用 `workflow get/list` 验证发布结果和运行状态。 + ### workflow list — 列出工作流 ```bash @@ -119,12 +197,12 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - | 能力 | 状态 | |------|------| +| 新建工作流 | ✅ 创建并发布 | +| 修改工作流配置 | ✅ 更新并发布 | | 列出工作流 | ✅ | | 看工作流详情(含 flowSchema) | ✅ | | 启用/禁用 | ✅ | -| 新建工作流 | ❌ 当前不支持,请去 AI 表格 Web 端 → 数据表 → 自动化 创建 | -| 修改工作流配置 | ❌ 同上,需 Web UI 编辑 | -| 删除工作流 | ❌ 同上 | +| 删除工作流 | ❌ 暂未开放 | | 查看运行历史/执行日志 | ❌ 暂未开放 | | 手动触发/单次运行 | ❌ 暂未开放 | @@ -132,6 +210,9 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - | 场景 | code | type | 备注 | |------|------|------|------| +| create/update 返回 `valid=false` | — | success envelope | 读取 `data.issues` 修正 DSL,不能当作发布成功 | +| create 下游失败 | `CREATE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | create 不自动重试;先 list 排查是否已创建 | +| update 下游失败 | `UPDATE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | update 会重试瞬态错误,最终失败时保留 DSL 和 workflowId 排查 | | `workflow-id` 不存在调 get | `GET_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 可能为 null,先 `workflow list` 核对 ID | | `workflow-id` 不存在调 enable | `ENABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 含 "场域中不存在该 namespace" | | `workflow-id` 不存在调 disable | `DISABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | 同上 | @@ -142,6 +223,18 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - ## 典型工作流 +### 创建并确认一个工作流 + +```bash +dws aitable workflow create --base-id BASE_ID --dsl @/tmp/workflow.json --locale zh-CN --format json \ + | tee /tmp/workflow-result.json + +jq '{valid: .data.valid, flowId: .data.flowId, issues: .data.issues}' /tmp/workflow-result.json +FLOW_ID=$(jq -r '.data.flowId' /tmp/workflow-result.json) +dws aitable workflow list --base-id BASE_ID --format json \ + | jq --arg id "$FLOW_ID" '.data.list[] | select(.flowId == $id) | {flowId, name, status}' +``` + ### 看看 Base 里有哪些自动化在跑 ```bash @@ -175,6 +268,9 @@ done ## 注意事项 - `--workflow-id` 接受的就是 `list` 返回里的 `flowId`(同值,CLI 屏蔽了服务端字段名差异)。 +- create / update 的 `--dsl` 必须是 JSON object,不能传数组、二次字符串化 JSON 或 `flowSchema`。 +- `status=success` 且 `data.valid=false` 仍是 DSL 校验失败;`issues` 才是下一步修复依据。 +- create 不自动重试;update 仅对网络、5xx、`retryable:true` 等瞬态错误自动重试。 - enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。 - `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。 -- 新建/修改/删除工作流目前必须在 AI 表格 Web 端(数据表页面 → 自动化)完成。 +- 删除、运行历史和手动触发当前仍未开放。 diff --git a/skills/mono/references/products/sheet.md b/skills/mono/references/products/sheet.md index a7310028..c2f6e2a0 100644 --- a/skills/mono/references/products/sheet.md +++ b/skills/mono/references/products/sheet.md @@ -2,12 +2,13 @@ ## 适用范围(重要) -`sheet` 产品线仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`),不支持上传的 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件。 +`sheet` 的单元格与工作表操作仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`)。`sheet import create` 是 Agent 可发现的文件入口:它接收本地 `xlsx` / `xls`,转换并新建在线电子表格;不能把已上传的 xlsx 节点直接当作 axls 操作。 | 文件类型 | 处理方式 | |---------|---------| | 在线电子表格(`axls`) | 走 `sheet` 全部命令(读/写/筛选/合并/导出等服务端原子操作) | -| `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 | 必须用 `dws doc download --node --output <路径>` 先下载到本地再解析处理,禁止调用任何 `sheet` 子命令(sheet 底层 MCP 工具仅认 `axls`,传入 xlsx 节点会直接报错) | +| 本地路径上的 `xlsx` / `xls` | 用 `dws sheet import create --file <路径> --folder-token ` 或 `--workspace ` 转换为新的在线电子表格 | +| 钉盘/文档中已有的 `xlsx` / `xls` / `xlsm` / `csv` 节点 | 不能直接调用工作表/单元格命令;先用 `dws doc download --node --output <路径>` 下载。若用户要转为在线表格,再对下载文件执行 `sheet import` | | 想把在线表格导出为 xlsx | 用 `dws sheet export` ——输入是 `axls`,输出是 xlsx(axls → xlsx 的格式转换) | > 用户直接粘贴原始 `alidocs` URL 时必须先 probe:先执行 `dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验 `contentType` 和 `extension`: @@ -125,6 +126,7 @@ dws sheet filter-view --help | `sheet list-float-images` | 列出工作表所有浮动图片 | | `sheet update-float-image` | 更新浮动图片属性 | | `sheet delete-float-image` | 删除浮动图片 | +| `sheet import create` | 导入本地 xlsx/xls 为新的在线电子表格(`sheet import` 保留兼容) | | `sheet export` | 导出表格为 xlsx | | `sheet filter get` | 获取全局筛选信息 | | `sheet filter create` | 创建全局筛选 | @@ -176,6 +178,7 @@ dws sheet filter-view --help | 查找替换 | [sheet/sheet-search-replace.md](./sheet/sheet-search-replace.md) | find / replace | | 全局筛选 | [sheet/sheet-filter.md](./sheet/sheet-filter.md) | filter get / create / delete / update / clear-criteria / sort | | 筛选视图 | [sheet/sheet-filter-view.md](./sheet/sheet-filter-view.md) | filter-view 全系列 | +| 导入 | [sheet/sheet-import.md](./sheet/sheet-import.md) | import / import get | | 导出 | [sheet/sheet-export.md](./sheet/sheet-export.md) | export | > `template list/search/apply` 无独立子文档,直接执行 `dws sheet template <子命令> --help` 查看用法。 @@ -369,6 +372,15 @@ dws sheet filter-view --help - 清除筛选视图列条件 → `filter-view delete-criteria` - 注意与 `filter-view delete`(删除整个筛选视图)区分:`delete-criteria` 仅清除指定列的条件,不删除筛选视图本身 +### 导入 + +用户说"导入Excel/把xlsx转为在线表格/上传本地表格并在线编辑": +- 使用 `sheet import`,本地文件仅支持 `xlsx` / `xls` +- `--folder-token` 与 `--workspace` 至少提供一个;不指定 `--name` 时使用文件名 +- CLI 内部完成上传、转换和轮询,Agent 不要自行拆分流程或重复提交 +- 若返回 `timed_out:true`,执行返回的 `next_command` 续查,不要重新执行 import +- 详见 [sheet/sheet-import.md](./sheet/sheet-import.md) + ### 导出 用户说"导出/下载xlsx/存为Excel/存成表格文件/把表格变成xlsx/导出表格/下载表格/导出为 excel": @@ -2139,7 +2151,7 @@ dws sheet export --node --output ./ - `copy` 当指定名称与已有工作表重复时,系统会自动重命名为合法值 - `copy` 的 `--index` 可选,不传时副本将放置在源工作表之后的默认位置 - ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) -- sheet 产品线仅支持 `axls`(在线电子表格,`contentType=ALIDOC`),不支持 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 +- 工作表与单元格命令仅支持 `axls`;本地 `xlsx` / `xls` 可通过 `sheet import` 转换为新的在线电子表格 - 遇到未知 `alidocs` URL 时,必须先 probe(`dws doc info --node --format json`)确认 `contentType` 和 `extension`,才能决定是否走 sheet - 当节点 `extension=xlsx` / `xls` / `xlsm` / `csv`(`contentType≠ALIDOC`)时,必须用 `dws doc download --node --output <路径>` 先下载到本地再处理,禁止调用任何 sheet 子命令(sheet 底层 MCP 工具只识别 axls,调用 xlsx 节点必失败) -- 要把在线表格导出为 xlsx 文件——走 `dws sheet export`(axls → xlsx 的格式转换);要读已有的 xlsx 文件——走 `dws doc download` 后在本地解析,两者方向相反 +- 要把在线表格导出为 xlsx 文件,走 `dws sheet export`;要把本地 xlsx/xls 转为在线表格,走 `dws sheet import`;要读取已上传但未转换的 xlsx 节点,先走 `dws doc download` diff --git a/skills/mono/references/products/sheet/sheet-import.md b/skills/mono/references/products/sheet/sheet-import.md new file mode 100644 index 00000000..2aa0a946 --- /dev/null +++ b/skills/mono/references/products/sheet/sheet-import.md @@ -0,0 +1,76 @@ +# 导入本地表格(import) + +## 使用场景 + +用户说“导入 Excel”“把 xlsx 变成在线表格”“上传本地表格并在线编辑”时,使用 Agent 可发现入口 `dws sheet import create`。它只新建在线电子表格,不覆盖已有表格。旧入口 `dws sheet import` 继续兼容人工调用。 + +- 支持本地 `xlsx` / `xls`,文件上限 20MB +- `--folder-token` 与 `--workspace` 至少提供一个 +- CLI 内部完成创建会话、OSS 上传、确认转换和轮询,Agent 不要自行拆分或重试 +- `drive upload` 只上传二进制文件,不会转换为可编辑的在线表格,不能替代本命令 + +## 命令 + +```bash +# 导入到指定文件夹 +dws sheet import create \ + --file ./quote.xlsx \ + --folder-token \ + --format json + +# 导入到指定知识库,并自定义名称 +dws sheet import create \ + --file ./report.xls \ + --workspace \ + --name "月度报表" \ + --format json + +# 导入超时或中断后续查 +dws sheet import get --task-id --format json +``` + +公开参数: + +| 命令 | 参数 | 说明 | +|------|------|------| +| `sheet import create` | `--file` | 本地 xlsx/xls 文件(必填) | +| `sheet import create` | `--folder-token` | 目标文件夹 ID 或 URL,与 `--workspace` 至少传一个 | +| `sheet import create` | `--workspace` | 目标知识库 ID 或 URL,与 `--folder-token` 至少传一个 | +| `sheet import create` | `--name`, `-n` | 导入后的名称;默认取文件名并去掉扩展名 | +| `sheet import get` | `--task-id` | 导入任务 ID | + +## 返回与续查 + +成功时返回: + +```json +{ + "success": true, + "taskId": "", + "documentUrl": "", + "documentName": "月度报表", + "documentType": "1", + "nodeId": "" +} +``` + +轮询达到上限时命令以退出码 0 返回业务态,避免 Agent 重复创建文档: + +```json +{ + "success": false, + "timed_out": true, + "taskId": "", + "status": "processing", + "next_command": "dws sheet import get --task-id " +} +``` + +检测到 `timed_out:true` 后,只执行 `next_command` 续查。不要重新执行 `sheet import create`。 + +## 边界 + +- 本命令接受的是本地文件路径;它把 xlsx/xls 转换成新的 axls 在线表格 +- 已经存在于钉盘或文档中的 xlsx 节点不能直接传给工作表/单元格命令;先用 `doc download` 下载,需要在线编辑时再执行 `sheet import create` +- 向已有在线表格写数据应使用 `range update`、`append`、`csv-put` 或 `table-put` +- md/doc/docx 等文字文档导入使用 `dws doc import` diff --git a/skills/multi/dingtalk-aitable/references/aitable.md b/skills/multi/dingtalk-aitable/references/aitable.md index 904c3837..c8929d70 100644 --- a/skills/multi/dingtalk-aitable/references/aitable.md +++ b/skills/multi/dingtalk-aitable/references/aitable.md @@ -105,12 +105,14 @@ | 命令 | 用途 | 必填参数 | 路由提醒 | |------|------|----------|----------| +| `workflow create` | 创建并发布工作流 | `--base-id` `--dsl` | `--dsl` 为完整 workflow-dsl/v1;非幂等,不自动重试 | +| `workflow update` | 更新并发布工作流 | `--base-id` `--workflow-id` `--dsl` | 全量替换,先 get 留底;检查 `data.valid/issues` | | `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` | | `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) | | `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` | | `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` `--yes` | 影响业务自动化,建议二次确认;status 变 STOP | -> **当前不支持通过 CLI 新建/修改/删除工作流**,请去 AI 表格 Web 端(数据表页面 → 自动化)配置。 +> 当前支持创建、更新、查询和启停;删除、运行历史及手动触发仍未开放。 ### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md) diff --git a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md index a223155a..1db8249b 100644 --- a/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md +++ b/skills/multi/dingtalk-aitable/references/aitable/aitable-workflow.md @@ -1,22 +1,100 @@ # workflow — 自动化工作流管理 -启停 / 查看 / 列出 Base 下的自动化工作流(在 AI 表格 Web 端配置的 "当 X 时自动 Y" 流程)。 -适用场景:用户问 "停掉这个流程"、"看下都有哪些自动化流程"、"流程 X 的配置是什么"。 +创建 / 更新 / 启停 / 查看 / 列出 Base 下的自动化工作流("当 X 时自动 Y" 流程)。 +适用场景:用户要求创建自动化、修改流程、停掉流程、查询已有流程或恢复运行。 ## 命令一览 | 命令 | 用途 | |------|------| +| `workflow create` | 创建并发布自动化工作流 | +| `workflow update` | 更新并发布已有自动化工作流 | | `workflow list` | 列出 Base 下所有工作流(含状态/创建人/最后修改时间),支持分页 | | `workflow get` | 获取单个工作流详情(含 flowSchema 完整节点定义) | | `workflow enable` | 启用指定工作流(按配置的触发条件自动执行) | | `workflow disable` | 禁用指定工作流(高危,建议 `--yes` 二次确认) | > 所有子命令的 `--base-id` 必填(可用隐藏别名 `--base`)。 -> 当前**不支持通过 CLI 新建工作流**,请在 AI 表格 Web 端配置好后用 `workflow list` 拿到 ID 再启停。 + +## DSL 入参格式与最小 Demo + +`workflow create/update` 的 `--dsl` 接收完整的 `workflow-dsl/v1` JSON object,不是局部 patch。支持内联 JSON、`@文件路径` 或 `-` 从 stdin 读取。 + +复杂工作流应先用 `table get` / `field get` / `view list` 确认真实 `sheetId`、`fieldId`、`viewId`,并检查所有 `next`、`loopEntry`、branch `to` 和 ref。下面是一个不依赖数据表字段的最小定时消息工作流: + +```json +{ + "version": "workflow-dsl/v1", + "name": "每日提醒", + "description": "可选说明", + "trigger": "start", + "steps": { + "start": { + "type": "Scheduled", + "next": "send", + "data": { + "mode": "daily", + "time": "09:00", + "timezone": "GMT+08:00" + } + }, + "send": { + "type": "SendMessage", + "data": { + "title": "定时任务已触发", + "to": {"users": [{"ref": "$.system_node.ownerUserId"}]} + } + } + } +} +``` + +create 和 update 都必须同时满足 `status=success`、`data.valid=true`、`data.issues=[]` 才表示发布成功。 ## 命令详情 +### workflow create — 创建并发布工作流 + +```bash +dws aitable workflow create \ + --base-id BASE_ID \ + --dsl @workflow.json \ + --locale zh-CN \ + --format json + +cat workflow.json | dws aitable workflow create --base-id BASE_ID --dsl - --format json +``` + +| flag | 必填 | 说明 | +|------|------|------| +| `--base-id` | 是 | 所属 Base ID | +| `--dsl` | 是 | workflow-dsl/v1 JSON object;支持内联、`@filepath`、`-` stdin | +| `--locale` | 否 | 请求语言,如 `zh-CN` / `zh_CN` | + +`create` 非幂等,CLI 不自动重试。若网络中断导致结果不确定,先 `workflow list` 按名称确认是否已经创建,再决定是否重试。`status=success` 只说明服务正常返回;`data.valid=false` 仍是发布失败,必须读取 `issues`。 + +### workflow update — 更新并发布工作流 + +```bash +# 先留底,再提交完整目标 DSL +dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/workflow-backup.json +dws aitable workflow update \ + --base-id BASE_ID \ + --workflow-id WORKFLOW_ID \ + --dsl @workflow.json \ + --locale zh_CN \ + --format json +``` + +| flag | 必填 | 说明 | +|------|------|------| +| `--base-id` | 是 | 所属 Base ID | +| `--workflow-id` | 是 | 目标工作流 ID,对应 list 的 `flowId` | +| `--dsl` | 是 | 完整目标 workflow-dsl/v1 JSON object;支持内联、`@filepath`、`-` stdin | +| `--locale` | 否 | 请求语言,如 `zh-CN` / `zh_CN` | + +update 返回结构与 create 相同,并会对瞬态错误重试。成功时 `data.flowId` 应与传入的工作流 ID 一致;随后用 `workflow get/list` 验证发布结果和运行状态。 + ### workflow list — 列出工作流 ```bash @@ -119,12 +197,12 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - | 能力 | 状态 | |------|------| +| 新建工作流 | ✅ 创建并发布 | +| 修改工作流配置 | ✅ 更新并发布 | | 列出工作流 | ✅ | | 看工作流详情(含 flowSchema) | ✅ | | 启用/禁用 | ✅ | -| 新建工作流 | ❌ 当前不支持,请去 AI 表格 Web 端 → 数据表 → 自动化 创建 | -| 修改工作流配置 | ❌ 同上,需 Web UI 编辑 | -| 删除工作流 | ❌ 同上 | +| 删除工作流 | ❌ 暂未开放 | | 查看运行历史/执行日志 | ❌ 暂未开放 | | 手动触发/单次运行 | ❌ 暂未开放 | @@ -132,6 +210,9 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - | 场景 | code | type | 备注 | |------|------|------|------| +| create/update 返回 `valid=false` | — | success envelope | 读取 `data.issues` 修正 DSL,不能当作发布成功 | +| create 下游失败 | `CREATE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | create 不自动重试;先 list 排查是否已创建 | +| update 下游失败 | `UPDATE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | update 会重试瞬态错误,最终失败时保留 DSL 和 workflowId 排查 | | `workflow-id` 不存在调 get | `GET_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 可能为 null,先 `workflow list` 核对 ID | | `workflow-id` 不存在调 enable | `ENABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 含 "场域中不存在该 namespace" | | `workflow-id` 不存在调 disable | `DISABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | 同上 | @@ -142,6 +223,18 @@ dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes - ## 典型工作流 +### 创建并确认一个工作流 + +```bash +dws aitable workflow create --base-id BASE_ID --dsl @/tmp/workflow.json --locale zh-CN --format json \ + | tee /tmp/workflow-result.json + +jq '{valid: .data.valid, flowId: .data.flowId, issues: .data.issues}' /tmp/workflow-result.json +FLOW_ID=$(jq -r '.data.flowId' /tmp/workflow-result.json) +dws aitable workflow list --base-id BASE_ID --format json \ + | jq --arg id "$FLOW_ID" '.data.list[] | select(.flowId == $id) | {flowId, name, status}' +``` + ### 看看 Base 里有哪些自动化在跑 ```bash @@ -175,6 +268,9 @@ done ## 注意事项 - `--workflow-id` 接受的就是 `list` 返回里的 `flowId`(同值,CLI 屏蔽了服务端字段名差异)。 +- create / update 的 `--dsl` 必须是 JSON object,不能传数组、二次字符串化 JSON 或 `flowSchema`。 +- `status=success` 且 `data.valid=false` 仍是 DSL 校验失败;`issues` 才是下一步修复依据。 +- create 不自动重试;update 仅对网络、5xx、`retryable:true` 等瞬态错误自动重试。 - enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。 - `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。 -- 新建/修改/删除工作流目前必须在 AI 表格 Web 端(数据表页面 → 自动化)完成。 +- 删除、运行历史和手动触发当前仍未开放。 diff --git a/skills/multi/dingtalk-sheet/SKILL.md b/skills/multi/dingtalk-sheet/SKILL.md index 07d7b2cb..b36804b4 100644 --- a/skills/multi/dingtalk-sheet/SKILL.md +++ b/skills/multi/dingtalk-sheet/SKILL.md @@ -1,6 +1,6 @@ --- name: dingtalk-sheet -description: 钉钉电子表格。Use when 用户说 电子表格/工作表/单元格读写/单元格追加/查找/公式/超链接/插入图片/浮动图片/sheet。Distinct from dingtalk-aitable(AI表格/多维表/字段类型)、dingtalk-doc(普通文档)。命令前缀:dws sheet。 +description: 钉钉电子表格。Use when 用户说 电子表格/导入Excel为在线表格/工作表/单元格读写/单元格追加/查找/公式/超链接/插入图片/浮动图片/sheet。Distinct from dingtalk-aitable(AI表格/多维表/字段类型)、dingtalk-doc(普通文档)。命令前缀:dws sheet。 cli_version: ">=0.2.14" metadata: category: product @@ -43,6 +43,7 @@ metadata: | 用户说 | 命令 | |--------|------| | "创建电子表格" | `dws sheet create --name "<标题>"` | +| "导入本地 Excel 为在线表格" | `dws sheet import create --file --folder-token ` 或 `--workspace ` | | "新建工作表" | `dws sheet new --node --name ""` | | "读取单元格" | `dws sheet range read --node --sheet-id --range A1:B2` | | "写入单元格" | `dws sheet range update --node --sheet-id --range A1:B2 --values '[[..]]'` | diff --git a/skills/multi/dingtalk-sheet/references/sheet.md b/skills/multi/dingtalk-sheet/references/sheet.md index a7310028..c2f6e2a0 100644 --- a/skills/multi/dingtalk-sheet/references/sheet.md +++ b/skills/multi/dingtalk-sheet/references/sheet.md @@ -2,12 +2,13 @@ ## 适用范围(重要) -`sheet` 产品线仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`),不支持上传的 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件。 +`sheet` 的单元格与工作表操作仅支持钉钉在线电子表格(`contentType=ALIDOC`、`extension=axls`)。`sheet import create` 是 Agent 可发现的文件入口:它接收本地 `xlsx` / `xls`,转换并新建在线电子表格;不能把已上传的 xlsx 节点直接当作 axls 操作。 | 文件类型 | 处理方式 | |---------|---------| | 在线电子表格(`axls`) | 走 `sheet` 全部命令(读/写/筛选/合并/导出等服务端原子操作) | -| `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 | 必须用 `dws doc download --node --output <路径>` 先下载到本地再解析处理,禁止调用任何 `sheet` 子命令(sheet 底层 MCP 工具仅认 `axls`,传入 xlsx 节点会直接报错) | +| 本地路径上的 `xlsx` / `xls` | 用 `dws sheet import create --file <路径> --folder-token ` 或 `--workspace ` 转换为新的在线电子表格 | +| 钉盘/文档中已有的 `xlsx` / `xls` / `xlsm` / `csv` 节点 | 不能直接调用工作表/单元格命令;先用 `dws doc download --node --output <路径>` 下载。若用户要转为在线表格,再对下载文件执行 `sheet import` | | 想把在线表格导出为 xlsx | 用 `dws sheet export` ——输入是 `axls`,输出是 xlsx(axls → xlsx 的格式转换) | > 用户直接粘贴原始 `alidocs` URL 时必须先 probe:先执行 `dws doc info --node --format json`,按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) 校验 `contentType` 和 `extension`: @@ -125,6 +126,7 @@ dws sheet filter-view --help | `sheet list-float-images` | 列出工作表所有浮动图片 | | `sheet update-float-image` | 更新浮动图片属性 | | `sheet delete-float-image` | 删除浮动图片 | +| `sheet import create` | 导入本地 xlsx/xls 为新的在线电子表格(`sheet import` 保留兼容) | | `sheet export` | 导出表格为 xlsx | | `sheet filter get` | 获取全局筛选信息 | | `sheet filter create` | 创建全局筛选 | @@ -176,6 +178,7 @@ dws sheet filter-view --help | 查找替换 | [sheet/sheet-search-replace.md](./sheet/sheet-search-replace.md) | find / replace | | 全局筛选 | [sheet/sheet-filter.md](./sheet/sheet-filter.md) | filter get / create / delete / update / clear-criteria / sort | | 筛选视图 | [sheet/sheet-filter-view.md](./sheet/sheet-filter-view.md) | filter-view 全系列 | +| 导入 | [sheet/sheet-import.md](./sheet/sheet-import.md) | import / import get | | 导出 | [sheet/sheet-export.md](./sheet/sheet-export.md) | export | > `template list/search/apply` 无独立子文档,直接执行 `dws sheet template <子命令> --help` 查看用法。 @@ -369,6 +372,15 @@ dws sheet filter-view --help - 清除筛选视图列条件 → `filter-view delete-criteria` - 注意与 `filter-view delete`(删除整个筛选视图)区分:`delete-criteria` 仅清除指定列的条件,不删除筛选视图本身 +### 导入 + +用户说"导入Excel/把xlsx转为在线表格/上传本地表格并在线编辑": +- 使用 `sheet import`,本地文件仅支持 `xlsx` / `xls` +- `--folder-token` 与 `--workspace` 至少提供一个;不指定 `--name` 时使用文件名 +- CLI 内部完成上传、转换和轮询,Agent 不要自行拆分流程或重复提交 +- 若返回 `timed_out:true`,执行返回的 `next_command` 续查,不要重新执行 import +- 详见 [sheet/sheet-import.md](./sheet/sheet-import.md) + ### 导出 用户说"导出/下载xlsx/存为Excel/存成表格文件/把表格变成xlsx/导出表格/下载表格/导出为 excel": @@ -2139,7 +2151,7 @@ dws sheet export --node --output ./ - `copy` 当指定名称与已有工作表重复时,系统会自动重命名为合法值 - `copy` 的 `--index` 可选,不传时副本将放置在源工作表之后的默认位置 - ★ 关键区分: sheet(电子表格/单元格读写) vs aitable(AI多维表/结构化记录/字段定义) vs doc(文档编辑/阅读) -- sheet 产品线仅支持 `axls`(在线电子表格,`contentType=ALIDOC`),不支持 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 +- 工作表与单元格命令仅支持 `axls`;本地 `xlsx` / `xls` 可通过 `sheet import` 转换为新的在线电子表格 - 遇到未知 `alidocs` URL 时,必须先 probe(`dws doc info --node --format json`)确认 `contentType` 和 `extension`,才能决定是否走 sheet - 当节点 `extension=xlsx` / `xls` / `xlsm` / `csv`(`contentType≠ALIDOC`)时,必须用 `dws doc download --node --output <路径>` 先下载到本地再处理,禁止调用任何 sheet 子命令(sheet 底层 MCP 工具只识别 axls,调用 xlsx 节点必失败) -- 要把在线表格导出为 xlsx 文件——走 `dws sheet export`(axls → xlsx 的格式转换);要读已有的 xlsx 文件——走 `dws doc download` 后在本地解析,两者方向相反 +- 要把在线表格导出为 xlsx 文件,走 `dws sheet export`;要把本地 xlsx/xls 转为在线表格,走 `dws sheet import`;要读取已上传但未转换的 xlsx 节点,先走 `dws doc download` diff --git a/skills/multi/dingtalk-sheet/references/sheet/sheet-import.md b/skills/multi/dingtalk-sheet/references/sheet/sheet-import.md new file mode 100644 index 00000000..2aa0a946 --- /dev/null +++ b/skills/multi/dingtalk-sheet/references/sheet/sheet-import.md @@ -0,0 +1,76 @@ +# 导入本地表格(import) + +## 使用场景 + +用户说“导入 Excel”“把 xlsx 变成在线表格”“上传本地表格并在线编辑”时,使用 Agent 可发现入口 `dws sheet import create`。它只新建在线电子表格,不覆盖已有表格。旧入口 `dws sheet import` 继续兼容人工调用。 + +- 支持本地 `xlsx` / `xls`,文件上限 20MB +- `--folder-token` 与 `--workspace` 至少提供一个 +- CLI 内部完成创建会话、OSS 上传、确认转换和轮询,Agent 不要自行拆分或重试 +- `drive upload` 只上传二进制文件,不会转换为可编辑的在线表格,不能替代本命令 + +## 命令 + +```bash +# 导入到指定文件夹 +dws sheet import create \ + --file ./quote.xlsx \ + --folder-token \ + --format json + +# 导入到指定知识库,并自定义名称 +dws sheet import create \ + --file ./report.xls \ + --workspace \ + --name "月度报表" \ + --format json + +# 导入超时或中断后续查 +dws sheet import get --task-id --format json +``` + +公开参数: + +| 命令 | 参数 | 说明 | +|------|------|------| +| `sheet import create` | `--file` | 本地 xlsx/xls 文件(必填) | +| `sheet import create` | `--folder-token` | 目标文件夹 ID 或 URL,与 `--workspace` 至少传一个 | +| `sheet import create` | `--workspace` | 目标知识库 ID 或 URL,与 `--folder-token` 至少传一个 | +| `sheet import create` | `--name`, `-n` | 导入后的名称;默认取文件名并去掉扩展名 | +| `sheet import get` | `--task-id` | 导入任务 ID | + +## 返回与续查 + +成功时返回: + +```json +{ + "success": true, + "taskId": "", + "documentUrl": "", + "documentName": "月度报表", + "documentType": "1", + "nodeId": "" +} +``` + +轮询达到上限时命令以退出码 0 返回业务态,避免 Agent 重复创建文档: + +```json +{ + "success": false, + "timed_out": true, + "taskId": "", + "status": "processing", + "next_command": "dws sheet import get --task-id " +} +``` + +检测到 `timed_out:true` 后,只执行 `next_command` 续查。不要重新执行 `sheet import create`。 + +## 边界 + +- 本命令接受的是本地文件路径;它把 xlsx/xls 转换成新的 axls 在线表格 +- 已经存在于钉盘或文档中的 xlsx 节点不能直接传给工作表/单元格命令;先用 `doc download` 下载,需要在线编辑时再执行 `sheet import create` +- 向已有在线表格写数据应使用 `range update`、`append`、`csv-put` 或 `table-put` +- md/doc/docx 等文字文档导入使用 `dws doc import` diff --git a/test/scripts/install_script_test.go b/test/scripts/install_script_test.go index 77448d2b..ad0c61a3 100644 --- a/test/scripts/install_script_test.go +++ b/test/scripts/install_script_test.go @@ -577,8 +577,8 @@ func TestBuildEntrypointsUseStripLdflags(t *testing.T) { want: `go build -ldflags="-s -w" -o $tmpBin "$Root/cmd"`, }, { - relPath: filepath.Join("..", "..", "scripts", "policy", "check-command-surface.sh"), - want: `go build -ldflags="-s -w" -o "$BIN_PATH" ./cmd`, + relPath: filepath.Join("..", "..", "scripts", "dev", "build.sh"), + want: `-ldflags="-s -w"`, }, } @@ -599,6 +599,32 @@ func TestBuildEntrypointsUseStripLdflags(t *testing.T) { } } +func TestPolicyBinaryChecksReuseBuiltDWS(t *testing.T) { + t.Parallel() + + for _, relPath := range []string{ + filepath.Join("..", "..", "scripts", "policy", "check-command-surface.sh"), + filepath.Join("..", "..", "scripts", "policy", "check-schema-binary.sh"), + } { + scriptPath, err := filepath.Abs(relPath) + if err != nil { + t.Fatalf("Abs(%s) error = %v", relPath, err) + } + data, err := os.ReadFile(scriptPath) + if err != nil { + t.Fatalf("ReadFile(%s) error = %v", scriptPath, err) + } + text := string(data) + if !strings.Contains(text, `BIN="${DWS_BIN:-$ROOT/dws}"`) { + t.Fatalf("%s does not reuse DWS_BIN", scriptPath) + } + if strings.Contains(text, `go build -o "$tmp/dws" ./cmd`) || + strings.Contains(text, `go build -ldflags="-s -w"`) { + t.Fatalf("%s unexpectedly rebuilds dws", scriptPath) + } + } +} + // TestInstallScriptsCacheMultiSkills verifies install.sh / install.ps1 / // install-skills.sh / build/npm/install.js all carry the wiring that caches // the multi/ tree to ~/.dws/skills/multi/ during install. This is what lets