Sync third-party and MCP marketplace plugins

Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources.
Confidence: high
Scope-risk: narrow
Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history.
Tested: Marketplace validation passed.
This commit is contained in:
KeyInfo Bot committed 2026-11-04 17:28:46 +08:00
1 parent 04673a8de8
commit d8b11a58ac
80 files changed
+13146 -663

No files matched your search

+14 -14
View File
@@ -69,8 +69,8 @@
"repo": "https://github.com/vercel-labs/skills.git", "repo": "https://github.com/vercel-labs/skills.git",
"ref": "main", "ref": "main",
"adapter": "claude-skill", "adapter": "claude-skill",
"commit": "7a5e920bead4b1039a6d6df5d7e1163b7b09423a", "commit": "9197d919034d19010ae303cf96bf19ad087dd4be",
"syncedAt": "2026-11-03T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
}, },
{ {
"id": "generate-agents-md", "id": "generate-agents-md",
@@ -121,10 +121,10 @@
"id": "dingtalk-workspace", "id": "dingtalk-workspace",
"pluginName": "dingtalk-workspace", "pluginName": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git", "repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
"ref": "v1.0.66", "ref": "v1.0.67",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "f215043e676d6889e1ddce3464828d8b1ec40c31", "commit": "63a7c4906c7dee10750011ea309b9eb78abb7eb0",
"syncedAt": "2026-10-28T15:59:59Z" "syncedAt": "2026-11-04T09:24:25Z"
}, },
{ {
"id": "ui-ux-pro-max", "id": "ui-ux-pro-max",
@@ -132,17 +132,17 @@
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git", "repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
"ref": "main", "ref": "main",
"adapter": "claude-skill", "adapter": "claude-skill",
"commit": "713eef51b04efc98d6fa565323b495f7c9c89fd4", "commit": "dd9a1b4cf9f67ca2e326f646d93e101b2778db30",
"syncedAt": "2026-11-03T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
}, },
{ {
"id": "superpowers-zh", "id": "superpowers-zh",
"pluginName": "superpowers-zh", "pluginName": "superpowers-zh",
"repo": "https://github.com/AreChen/superpowers-zh.git", "repo": "https://github.com/AreChen/superpowers-zh.git",
"ref": "v7.0.1-zh.1", "ref": "v7.0.2-zh.1",
"adapter": "codex-plugin", "adapter": "codex-plugin",
"commit": "a41f784bb852f0c86d4cc891ecdb14d1344aa063", "commit": "79c797bd8c9d5273a5fba5eae5ac9ba27613d0d8",
"syncedAt": "2026-10-27T07:55:04Z" "syncedAt": "2026-11-04T09:24:25Z"
}, },
{ {
"id": "caveman", "id": "caveman",
@@ -168,8 +168,8 @@
"repo": "https://github.com/shadcn-ui/ui.git", "repo": "https://github.com/shadcn-ui/ui.git",
"ref": "main", "ref": "main",
"adapter": "claude-skill", "adapter": "claude-skill",
"commit": "4aaea19d9cff3e0908e12a8c1d504b8c5ec34610", "commit": "8a3536678a528cc012b9e62484290e79d0be410f",
"syncedAt": "2026-11-02T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
}, },
{ {
"id": "frontend-slides", "id": "frontend-slides",
@@ -285,8 +285,8 @@
"repo": "https://github.com/vercel/next.js.git", "repo": "https://github.com/vercel/next.js.git",
"ref": "canary", "ref": "canary",
"adapter": "skill-collection", "adapter": "skill-collection",
"commit": "4c3f5618ffdcd08e33693a1b25d47a4ef1254d90", "commit": "cc65c414d283e22e587828c94770d9b12522bbb4",
"syncedAt": "2026-11-03T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
}, },
{ {
"id": "shuorenhua", "id": "shuorenhua",
@@ -1,6 +1,6 @@
{ {
"name": "dingtalk-workspace", "name": "dingtalk-workspace",
"version": "1.0.66", "version": "1.0.67",
"description": "通过 dws CLI 操作钉钉消息、文档、表格、日历、待办和知识库的官方 Skill 集合。需要另行安装匹配版本的 dws、登录并取得企业授权;辅助脚本需要 Python 3。", "description": "通过 dws CLI 操作钉钉消息、文档、表格、日历、待办和知识库的官方 Skill 集合。需要另行安装匹配版本的 dws、登录并取得企业授权;辅助脚本需要 Python 3。",
"author": { "author": {
"name": "DingTalk", "name": "DingTalk",
@@ -1,9 +1,9 @@
{ {
"sourceId": "dingtalk-workspace", "sourceId": "dingtalk-workspace",
"repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git", "repo": "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git",
"ref": "v1.0.66", "ref": "v1.0.67",
"commit": "f215043e676d6889e1ddce3464828d8b1ec40c31", "commit": "63a7c4906c7dee10750011ea309b9eb78abb7eb0",
"adapter": "skill-collection", "adapter": "skill-collection",
"sourcePath": "skills/multi", "sourcePath": "skills/multi",
"syncedAt": "2026-10-28T15:59:59Z" "syncedAt": "2026-11-04T09:24:25Z"
} }
@@ -1,9 +1,10 @@
--- ---
name: dingtalk-aicard name: dingtalk-aicard
description: > description: >
Create, edit, and validate DingTalk AI Card (A2UI) JSON files offline. Create, edit, and validate DingTalk AI Card (A2UI) JSON files with native
Use for cards built from requirements or images, structural protocol errors, DWS commands. Use for cards built from requirements or images, structural
and named component or function contract lookup through native DWS commands. protocol errors, named component or function contract lookup, and cloud
templates created, retrieved, or revised by template ID and version.
Send a card to the current user only when a preview is requested. Send a card to the current user only when a preview is requested.
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
@@ -19,30 +20,32 @@ metadata:
# DingTalk AI Cards V0.8 # DingTalk AI Cards V0.8
Create or edit an A2UI file from the user's content or image, look up only the contracts needed, and distinguish local validation from delivery and client rendering. This Skill uses the DingTalk V0.8 package based on A2UI 1.0: message `version` is `"v1.0"` and send API `protocolVersion` is `"1.0"`. Create or edit A2UI from user content or images; query needed contracts. Distinguish local validation, delivery, and rendering. DingTalk V0.8 uses A2UI 1.0: message `version` is `"v1.0"`; send API `protocolVersion` is `"1.0"`.
Follow the user's requested lookup, file, send or update directly; the references are not a mandatory sequence. Obey command and protocol requirements. Ask only when missing facts, interaction meaning or the target affect the outcome. Follow the requested lookup, file, send, or update; references are not a mandatory sequence. Obey command and protocol requirements. Ask only if missing facts, interaction meaning, or target affect the outcome.
## Route by intent ## Route by intent
Read only the relevant reference. Do not preload the complete protocol, every pattern, or the whole guide. Design supplies visual principles; patterns give reusable content and interaction strategies; templates are complete scene examples. Read only the relevant reference. Do not preload the complete protocol, every pattern, or the whole guide.
| Intent | First reference | Optional follow-up | | Intent | Start with | Optional follow-up |
|---|---|---| |---|---|---|
| Set up lookup and validation | [Execution environment](#execution-environment) | Installed command help | | Set up lookup and validation | [Execution environment](#execution-environment) | Installed command help |
| Create or redesign from content or images | [Visual focus and layout](references/design.md) | One relevant [pattern](#composition-patterns) or example | | Reuse a bundled template | Its [reuse boundary](references/templates/README.md) and selected JSON | Components outside the data model; pattern for changed behavior |
| Create or redesign from requirements | [Visual focus and layout](references/design.md) | One [pattern](#composition-patterns); a template only if compatible |
| Reconstruct an image | [Image reconstruction](references/design.md#image-reconstruction) | Selected component contracts |
| Query a known name | [Query a named contract](#query-a-named-contract) | The result's source pointer | | Query a known name | [Query a named contract](#query-a-named-contract) | The result's source pointer |
| Find an unknown name | [Component](references/index/components.md), [function](references/index/functions.md), or [Token](references/index/tokens.md) index | Query only the selected names | | Find an unknown name | [Component](references/index/components.md), [function](references/index/functions.md), or [Token](references/index/tokens.md) index | Query only the selected names |
| Construct or repair a card | [Construct and validate](#construct-and-validate) | Diagnostic pointer and named contract | | Construct or repair a card, or prepare an update | Current messages and [Construct and validate](#construct-and-validate) | Affected guidance, diagnostic pointer, and named contract |
| Send, preview, or update | [Delivery boundary](#delivery-boundary) | Installed command help | | Send, preview, or deliver an update | [Delivery boundary](#delivery-boundary) | Installed command help |
Use patterns for composition guidance and linked JSON for message shapes. Read a named example directly; use the [example index](references/protocol/examples/README.md) only to choose one. Use patterns for composition guidance and linked JSON for message shapes. Read a named capability example directly; use the [example index](references/protocol/examples/README.md) only to choose one. Template reuse must match structure, collection sizes, and interactions.
## Composition patterns ## Composition patterns
| Content | Reference | | Content | Reference |
|---|---| |---|---|
| Notifications and reminders | [Notification](references/patterns/notification.md) | | Notifications and reminders | [Short notifications](references/patterns/detail.md#short-notifications) |
| Object facts and decisions | [Detail](references/patterns/detail.md) | | Object facts and decisions | [Detail](references/patterns/detail.md) |
| Illustrated content and attachments | [Content](references/patterns/content.md) | | Illustrated content and attachments | [Content](references/patterns/content.md) |
| Metrics and reports | [Report](references/patterns/report.md) | | Metrics and reports | [Report](references/patterns/report.md) |
@@ -55,7 +58,19 @@ When replaying an execution example, read [Examples and replay](references/patte
## Execution environment ## Execution environment
This edition uses native DWS commands, not Python. Confirm `dws aicard --help` exposes `explain`, `lint`, and `preview`; copying the Skill does not install commands. Lookup and lint are offline and need no Profile. Check each command's current help and structured envelope (`ok`, `outcome`, `data`, or `error.details`). If unavailable, references can guide a draft, but do not claim DWS validation. Use native DWS commands. Check `dws aicard --help` for `explain`, `lint`, `preview`, and `template`; copying this Skill does not install them. Lookup and lint are offline and need no Profile. Check leaf help and JSON envelopes (`ok`, `outcome`, `data`, `error.details`). Missing commands permit a draft, never a DWS validation claim.
### Cloud templates
| Intent | Command |
|---|---|
| Save a validated card | `dws aicard template create --file <file>` |
| Read a known ID and version | `dws aicard template get --template-id <id> --version <version> --out <new-file>` |
| Revise a template | `dws aicard template update --template-id <id> --file <file>` |
Cloud calls need the intended Profile, organization, and environment, plus an explicit `--mcp-id` or `DWS_AICARD_TEMPLATE_MCP_ID`; there is no default. `get` needs a known ID and version, not keywords. To derive a template, get it, edit the file, and create a new one; `update` appends a version under the same ID. Report the returned ID and version; never retry an uncertain write. A read of an absent version may return the latest content, so strict use must match the approved digest.
Use approved business bindings and data paths; do not substitute local samples. Business configuration owns activation and rollback. Send complete new cards or authorized instance deltas; delivery never modifies template assets.
## Query a named contract ## Query a named contract
@@ -96,36 +111,40 @@ Removing `createSurface` from a complete file does not make it a safe delta.
Do not replay form defaults or unrelated initial data: that can overwrite user Do not replay form defaults or unrelated initial data: that can overwrite user
input. Resource checks alone cannot prove an update preserves state or references. input. Resource checks alone cannot prove an update preserves state or references.
Preserve unrelated styling when editing. For ordinary new cards, prefer a root `Column` without background fields. If a root `Card` is needed, use `backgroundColor: "#00FFFFFF"` and no background token. Honor explicit surface designs; see [design guidance](references/design.md#root-surface). Preserve unrelated styling when editing. When reusing a template, keep its frame and insets; the new-card defaults below do not override template styling or explicit surface designs. For ordinary new cards, prefer a root `Column` without `backgroundColor` or `backgroundColorToken`. If a root `Card` is needed, use `backgroundColor: "#00FFFFFF"` and omit `backgroundColorToken`: omitting both fields gives Card an opaque default, and a configured background Token takes precedence over the color. See [design guidance](references/design.md#root-surface).
Prefer `Markdown` for rich text and static tables; `Text` for short titles and labels. Omit root `padding` for ordinary cards. Do not default to `CardHeader`; preserve it when requested and check [insets](references/design.md#headers-and-card-insets). Prefer `Markdown` for rich text and static tables; use `Text` for short titles and labels. See [report composition](references/patterns/report.md) for table selection and pagination. Omit root `padding` for ordinary new cards; omission is not equivalent to explicit `0`. Do not default to `CardHeader`; preserve it when requested and check [insets](references/design.md#headers-and-card-insets). These are authoring defaults, not Schema restrictions.
Preserve supplied facts and requested interactions; label demonstration data. Replace `REPLACE_*` placeholders before delivery. Do not present unverified resources, unconnected actions, or simulated states as real and working. Preserve supplied facts and requested interactions; label demonstration data. Replace `REPLACE_*` placeholders before delivery. Do not present unverified resources, unconnected actions, or simulated states as real and working.
Choose literals or bindings as the field contract permits. Initialize bound data with the required types and update it through `updateDataModel`; change component literals through `updateComponents`. `sendDataModel` controls whether A2A messages include the full data model, not whether bindings work. Choose literals or bindings as the field contract permits. Initialize bound data with the required types and update it through `updateDataModel`; change component literals and layout through `updateComponents`. `sendDataModel` controls whether A2A messages include the full data model, not whether bindings work.
```bash ```bash
dws aicard lint --file card.a2ui.json --format json dws aicard lint --file a2ui.json --format json
``` ```
Choose `--preflight new-card` or `--preflight resources` for the selected scenario, and use `--emit` only when serialized message strings are needed; it cannot be combined with `--fragment`. Check exit status and `data.valid` plus `data.preflight.valid` when selected. `dws aicard lint --self-check --format json` verifies the full package. Choose `--preflight new-card` or `--preflight resources` for the selected scenario. Use `--emit` only when serialized message strings are needed; it cannot be combined with `--fragment`. Check exit status, `data.valid`, and `data.preflight.valid` when selected. Fix errors at their diagnostic pointers and rerun the selected check. `dws aicard lint --self-check --format json` verifies the full package.
For group cards that @ members, Markdown content uses `<a atId="openDingTalkId">nickname</a>` for a member and `<a atId="all">everyone</a>` for everyone. Pass the declared ids with `--at-open-dingtalk-ids` alongside `--preflight`: lint then fails on ids declared but unused (`mention.declared_unused`) or used but undeclared (`mention.undeclared`). IM-style `<@id>` placeholders never notify in cards; explicit lint preflight reports `mention.im_placeholder`, excluding Markdown code examples. The reserved `all` stays in the Markdown body only; it is never listed. Lint has no `--at-all` option and does not enforce the send-time 100-declaration limit or everyone-tag pairing; run send dry-run for those checks. Static matching accepts literal Markdown and resolvable absolute data paths from the same batch; runtime expressions remain unverified. Markdown code spans, code blocks and escaped tag examples are ignored. Matching uses a double-quoted `atId` as the sole attribute.
## Validation scope ## Validation scope
Lint checks syntax and Schema fields. New-card preflight additionally checks initialization, root, duplicate IDs within a message, static references and cycles in the final component snapshot, not every intermediate streaming frame. Unreachable components are warnings. Resource preflight checks inline encoding; neither mode evaluates bindings or proves image loading. `--fragment` checks component/message fragments; it is not a way to bypass new-card initialization. Do not silently rewrite user data, remove requested components, or downgrade interactions to pass a check. Lint checks syntax and Schema. New-card preflight checks initialization, root, IDs, final-snapshot static references, cycles, and ownership. Multiple parents, repeated child slots, shared template sources, and static/template overlap fail even with empty arrays; unreachable nodes warn. Use distinct IDs per placement. Bindings, dynamic expansion, intermediate frames, and rendering remain unverified. Resource preflight rejects literal `data:` resource fields (`resource.inline_data_unsupported`); Markdown/`fallbackMarkdown` markers, even code/comments, only warn (`resource.markdown_data_unverified`) for review. This does not parse Markdown/HTML, evaluate bindings, or fetch resources; a clean report proves neither absence of inline media nor loading. Prefer HTTPS [assets](references/design.md#media-resources). `--fragment` never bypasses initialization. Do not silently remove requested data, components, or interactions.
A successful named lookup is not a full package check. Use the self-check supported by the selected execution environment; local checks do not establish client behavior. Use self-check for package integrity, not named lookup. Review facts, actions, visual completeness, and first-frame values. Verify loading, functions, rendering, interaction, and writeback on the client; report delivery evidence separately.
## Delivery boundary ## Delivery boundary
With native `--emit`, read `data.a2uiMessages`. Follow the installed dingtalk-chat A2UI guidance for creation, updates, summaries, annotations, and completion; check leaf help for `--summary`, `--support-forward`, and update flags. A missing capability blocks delivery.
Send only when requested. Resolve exactly one target under the sending profile: `--conversation-id` for a conversation or `--open-dingtalk-id` for a person. Names are not target IDs. Inspect installed command help, JSON-encode the emitted message-string array as one `--content` argument, and call `dws chat message send-a2ui-card`. Preserve the request, target, profile and returned `bizId`. An uncertain result must be checked before another create; do not resend automatically. Send only when requested. Resolve exactly one target under the sending Profile: `--conversation-id` for a conversation or `--open-dingtalk-id` for a person. Names are not target IDs. JSON-encode the emitted `data.a2uiMessages` as one `--content` argument for `dws chat message send-a2ui-card`, with a readable fallback `--summary` and a policy-based `--support-forward=true|false` (default false). For a card instantiated from a business template, update only the data paths its business configuration authorizes.
A group card that @ members must keep the Markdown `<a atId>` tags and the send flags consistent: list each member id with `--at-open-dingtalk-ids` (sent as `atOpenDingTalkIds`, at most 100 declarations including duplicates) and add `--at-all` only when the body uses `<a atId="all">` (sent as `atAll`; the CLI rewrites the tag to the server highlight alias `<a atId="10">` on the wire). With explicit mention flags, a statically known mismatch fails with exit code 3. Valid Markdown bindings may receive their body after placeholder creation; the CLI cannot certify future values. Without mention flags, legacy bodies are preserved, including direct-chat markup. Single-chat cards reject explicit @ flags. The server fails closed: an invalid or unresolvable declared id, over-limit declarations, @ in a single chat, or an alias conflict rejects card creation with INVALID_PARAM and a nonzero exit; body tags that were never declared get no highlight or notification.
Creation starts in PROCESSING. Update with `dws chat message update-a2ui-card --biz-id <bizId> --content <message-strings> --flow-status <state>` under the original profile. Keep the same `surfaceId` and component IDs; send only intended `updateDataModel` or `updateComponents` changes, not another `createSurface` or unrelated defaults. Static cards require `--flow-status FINISH` with a nonempty valid delta; prepare it before sending and preserve business data. Stream complete messages, not JSON tokens. Resource preflight checks only the delta. If current state is known, validate it with the delta; Schema validity does not prove business safety. Creation starts in PROCESSING. Update with `dws chat message update-a2ui-card --biz-id <bizId> --content <message-strings> --flow-status <state>` under the original profile. Keep the same `surfaceId` and component IDs; send only intended `updateDataModel` or `updateComponents` changes, not another `createSurface` or unrelated defaults. Static cards require `--flow-status FINISH` with a nonempty valid delta; prepare it before sending and preserve business data. Stream complete messages, not JSON tokens. Resource preflight checks only the delta. If current state is known, validate it with the delta; Schema validity does not prove business safety.
`dws aicard preview --file card.a2ui.json` sends a self-preview in PROCESSING; `--dry-run` does not send. For a completed static preview, finish it under the same profile with `dws chat message update-a2ui-card --biz-id <data.bizId> --content <message-strings> --flow-status FINISH`, using a nonempty state-preserving delta for the same Surface. Do not replay form defaults. Use server-issued `data.bizId`, never request-side `bizCardId`. If absent, inspect `updateWarning` and the receipt; do not recreate automatically. `openTaskId` cannot be used with `query-send-status`. `dws aicard preview --file a2ui.json` sends a self-preview in PROCESSING; `--dry-run` does not send. Finish a static preview with `dws chat message update-a2ui-card --flow-status FINISH`, the server-issued `data.bizId` (never request-side `bizCardId`), and `--a2ui-annotations` marking its main content component as `artifact`; otherwise the conversation summary becomes the server default. If it is absent, inspect `updateWarning` and the receipt; do not recreate automatically. `openTaskId` cannot be used with `query-send-status`.
After sending, use the available readback capability under the same profile and target to identify this exact card from returned instance/message identifiers. Generic card text and a nearby timestamp are not sufficient. Report request acceptance, same-target readback, client rendering and interaction separately. If readback is unavailable or inconclusive, mark it unverified; do not claim the user received or correctly rendered the card from API acceptance alone. After sending, identify this exact card from returned instance or message identifiers under the same Profile and target; generic card text and a nearby timestamp are not sufficient. Report request acceptance, readback, asynchronous summary, client rendering, and interaction separately; missing evidence stays unverified.
Report the file, checks actually run, and delivery result when requested. Local validation and request acceptance do not prove client rendering. Report the file, checks actually run, and delivery result when requested. Local validation and request acceptance do not prove client rendering.
@@ -4,7 +4,7 @@
## Global design guidance ## Global design guidance
1. **Prefer a transparent root for ordinary new cards.** Let the host provide the outer background instead of approximating its color. Preserve explicit surface designs and unrelated existing styling; see [root surface](#root-surface). 1. **Prefer a transparent root for ordinary new cards.** Let the host provide the outer background instead of approximating its color. Preserve explicit surface designs, a reused template's frame and insets, and unrelated existing styling; see [root surface](#root-surface) and [headers and insets](#headers-and-card-insets).
2. **Show generation state when relevant.** For an Agent that is generating or executing, a `Markdown` component may prefix its `content` with `[AIGenerating]`. DingTalk supports this convention across its six platforms; remove it on completion, failure, or cancellation. For panel titles and execution updates, see the [progress pattern](patterns/progress.md). 2. **Show generation state when relevant.** For an Agent that is generating or executing, a `Markdown` component may prefix its `content` with `[AIGenerating]`. DingTalk supports this convention across its six platforms; remove it on completion, failure, or cancellation. For panel titles and execution updates, see the [progress pattern](patterns/progress.md).
3. **Group execution details.** Use `CollapsiblePanel` with `variant: "reasoning"` for an Agent generation or execution process. Choose its expanded state and internal layout from the content. 3. **Group execution details.** Use `CollapsiblePanel` with `variant: "reasoning"` for an Agent generation or execution process. Choose its expanded state and internal layout from the content.
@@ -12,7 +12,7 @@
| Layer | Checks | Evidence | | Layer | Checks | Evidence |
|---|---|---| |---|---|---|
| Tools | Lint checks protocol structure; new-card preflight also checks initialization, reachable references in the final snapshot, static cycles, and resource encoding | Run checks for the delivery scenario. Fix errors at their locations; decide whether an unreferenced-component warning matters for this card | | Tools | Lint checks protocol structure; new-card preflight also checks initialization, final-snapshot references and component ownership, static cycles, and the inline-resource policy. See the Skill's [validation scope](../SKILL.md#validation-scope) for exact coverage | Run checks for the delivery scenario. Fix errors at their locations; decide whether an unreferenced-component warning matters for this card |
| Author review | Preserve user information, operations, and visual requirements; provide appropriate first-frame binding values; make priority, grouping, and density clear | Compare each request with its component or action and the host's existing state. Visual and content completeness are not lint diagnostics | | Author review | Preserve user information, operations, and visual requirements; provide appropriate first-frame binding values; make priority, grouping, and density clear | Compare each request with its component or action and the host's existing state. Visual and content completeness are not lint diagnostics |
| Runtime acceptance | Resource loading, function evaluation, delivery, actual layout, interactions, and state writeback | Verify in the target host. Report request acceptance, conversation readback, and client rendering separately | | Runtime acceptance | Resource loading, function evaluation, delivery, actual layout, interactions, and state writeback | Verify in the target host. Report request acceptance, conversation readback, and client rendering separately |
@@ -29,15 +29,17 @@ Passing structural checks does not establish the other two layers. Explain unsup
| Brand marks and specific document or product icons | Use a real asset or a crop of the reference. Do not impersonate them with emoji, ordinary characters, or vaguely similar icons. If the asset is unavailable, report the gap and, when appropriate, use an explicit text description | | Brand marks and specific document or product icons | Use a real asset or a crop of the reference. Do not impersonate them with emoji, ordinary characters, or vaguely similar icons. If the asset is unavailable, report the gap and, when appropriate, use an explicit text description |
| Generic action or status icons | Prefer a registered `Icon` with matching meaning. Otherwise use a supplied asset or text; do not invent an Icon name | | Generic action or status icons | Prefer a registered `Icon` with matching meaning. Otherwise use a supplied asset or text; do not invent an Icon name |
Crop media to the media region itself. Do not bake surrounding interface copy or interactive controls into the image; text and marks that belong to a poster may remain inside it. Do not substitute a screenshot of the entire interface for components, or destroy poster artwork just to make it structural. Images need a working URL or correctly encoded inline data, not a placeholder path. Crop media to the media region itself. Do not bake surrounding interface copy or interactive controls into the image; text and marks that belong to a poster may remain inside it. Do not substitute a screenshot of the entire interface for components, or destroy poster artwork just to make it structural. Use verified, host-accessible image URLs, preferably HTTPS. Avoid inline image bytes and placeholder paths; report a missing compatible asset. Resource fields are checked directly; Markdown resource text requires manual review as described under [media resources](#media-resources).
**Implement only known interactions.** For appearance-only reproduction, use a valid static representation and state that behavior is not connected. A pictured button does not reveal its event or URL. Implement bindings and actions when the user explicitly requires a form, data update, or host action. Extra business data and behavior require user input or a verifiable source. **Implement only known interactions.** For appearance-only reproduction, use a valid static representation and state that behavior is not connected. A pictured button does not reveal its event or URL. Implement bindings and actions when the user explicitly requires a form, data update, or host action. Extra business data and behavior require user input or a verifiable source.
**Compare before delivery.** Check key copy, numbers, dates, prices, region order, buttons, tags, and media against the source. Look for invented business facts or substituted brand assets. The component count need not equal the number of visual elements; preserve meaning and use. Report unreadable, unavailable, or unsupported content. Neither this comparison nor protocol preflight proves client visual fidelity. **Compare before delivery.** Check key copy, numbers, dates, prices, region order, buttons, tags, and media against the source. Look for invented business facts or substituted brand assets. The component count need not equal the number of visual elements; preserve meaning and use. Report unreadable, unavailable, or unsupported content. Neither this comparison nor protocol preflight proves client visual fidelity.
At the requested logical width, compare text size, wrapping, and any block alignment required by the reference design. Choose `Text.sizeToken` from measurements in the target renderer; Token names alone do not establish pixel sizes. Keep the reference and report remaining visual differences.
## 2. Information roles and blocks ## 2. Information roles and blocks
First rank the content: order the facts, group them by relationship, and assign visual weight before selecting components. A report may lead with the conclusion; an execution card may lead with progress. Short content needs no extra blocks. These combinations are optional layout notation, not a fixed order or required structure. First rank the content: order the facts, group them by relationship, and assign visual weight before selecting components. A report may lead with the conclusion; an execution card may lead with progress. Keep supporting metadata quieter than the focal content. Short content needs no extra blocks. Do not add titles, metrics, or actions merely to fill a template. These combinations are optional layout notation, not a fixed order or required structure.
| Role | Purpose | Components | Layout notation | | Role | Purpose | Components | Layout notation |
|---|---|---|---| |---|---|---|---|
@@ -46,7 +48,7 @@ First rank the content: order the facts, group them by relationship, and assign
| `facts` | Label-value facts | Column + Row + Text | One-line rows use `align=center`; wrapping values use `align=start` and matching label/value typography so the label aligns with the value's first line; see [key-value rows](#key-value-rows) | | `facts` | Label-value facts | Column + Row + Text | One-line rows use `align=center`; wrapping values use `align=start` and matching label/value typography so the label aligns with the value's first line; see [key-value rows](#key-value-rows) |
| `summary` | Conclusion-first prose | Markdown | Literal or bound content, for example `/content/body` | | `summary` | Conclusion-first prose | Markdown | Literal or bound content, for example `/content/body` |
| `metrics` | A few important measures | Row + Column + Text | `Row(gap=8) > Column(weight=1, padding=12) > Text(value, bold) + Text(label, caption)` | | `metrics` | A few important measures | Row + Column + Text | `Row(gap=8) > Column(weight=1, padding=12) > Text(value, bold) + Text(label, caption)` |
| `data` | Comparison, trend, or composition | Markdown / Chart | For static tabular data, prefer Markdown table syntax over a separate `Table` component. Use a suitable Chart for graphical data and query its contract. | | `data` | Comparison, trend, or composition | Markdown / Chart | For static tabular data, prefer Markdown table syntax over a separate `Table` component. Use a suitable Chart for graphical data and query its contract; see [report composition](patterns/report.md) for selection and data rules |
| `list` | Repeated entries | Column + Card + Text | — | | `list` | Repeated entries | Column + Card + Text | — |
| `timeline` | Time- or stage-ordered progress | Column + Row + Text + Tag | — | | `timeline` | Time- or stage-ordered progress | Column + Row + Text + Tag | — |
| `attachments` | File results | Divider + Text + File | `File(fileName, url, description, onPreview)` | | `attachments` | File results | Divider + Text + File | `File(fileName, url, description, onPreview)` |
@@ -64,23 +66,23 @@ States, icons, illustrated entries, and highlights may support a main block or s
| Content section | Related content needing a bounded visual surface | `Card(padding=12, cornerRadius=8, borderWidth=0.5) > Column(gap=8) > content` | | Content section | Related content needing a bounded visual surface | `Card(padding=12, cornerRadius=8, borderWidth=0.5) > Column(gap=8) > content` |
| Status | Current stage or risk | Tag + Text | | Status | Current stage or risk | Tag + Text |
| Icon | Action, source, or status | Icon or Text.icon; use a registered matching icon, or a supplied image or explanation if none exists. Do not invent a name or default to emoji for a specific icon | | Icon | Action, source, or status | Icon or Text.icon; use a registered matching icon, or a supplied image or explanation if none exists. Do not invent a name or default to emoji for a specific icon |
| Illustrated item | Compact image-and-text content | `Row(align=center, gap=12) > Column(weight=1) + Image(variant=smallFeature)` | | Illustrated item | Compact image-and-text content | `Row(align=center, gap=12) > Column(weight=1) + Image(variant=smallFeature)`; see [content composition](patterns/content.md) for other image variants |
| Highlight | Key conclusion or local emphasis | See [local highlights](#local-highlights); this is a design technique, not a protocol field | | Highlight | Key conclusion or local emphasis | See [local highlights](#local-highlights); this is a design technique, not a protocol field |
Select a relevant pattern from the Skill's [composition-pattern table](../SKILL.md#composition-patterns). Approval, schedules, and tasks share the detail pattern; ongoing generation uses the [progress pattern](patterns/progress.md). For mixed content, select only needed blocks and avoid duplicating headers or actions. When concrete message shapes would help, read the pattern's linked JSON; use the [example index](protocol/examples/README.md) only if unsure which fits. Examples initialize new cards; adapt them for host-created Surfaces or updates as described in the [Skill entrypoint](../SKILL.md#construct-and-validate). The protocol and contract lookup govern fields. Select a relevant pattern from the Skill's [composition-pattern table](../SKILL.md#composition-patterns). Approval, schedules, and tasks share the detail pattern; ongoing generation uses the [progress pattern](patterns/progress.md). For mixed content, select only needed blocks and avoid duplicating headers or actions. When concrete message shapes would help, read the pattern's linked JSON; use the [example index](protocol/examples/README.md) only if unsure which fits. Examples initialize new cards; adapt them for host-created Surfaces or updates as described in the [Skill entrypoint](../SKILL.md#construct-and-validate). The protocol and contract lookup govern fields.
## 3. Host behavior and layout ## 3. Host behavior and layout
The bundled protocol governs field capabilities. Visual numbers are starting points, not extra protocol limits. The bundled protocol governs field capabilities. Visual numbers are starting points, not extra protocol limits. One preview width is neither a minimum supported width nor an acceptance gate; check wrapping, overflow, and actions at the actual narrow and wide container widths supported by the target host.
Reserve Markdown headings for actual document sections; a small field or status label does not need heading hierarchy. Keep independent facts, statuses, and controls in appropriate components rather than flattening the card into one Markdown block. Use Markdown for rich text and Text for short titles and labels. Reserve Markdown headings for actual document sections; a small field or status label does not need heading hierarchy. Keep independent facts, statuses, and controls in appropriate components rather than flattening the card into one Markdown block.
1. `Row.align=stretch` can make containers in a row the same height. Container children and leaf components are handled differently; consult the Row contract for client behavior and integration boundaries. 1. `Row.align=stretch` can make containers in a row the same height. Container children and leaf components are handled differently; consult the Row contract for client behavior and integration boundaries.
2. Check long titles, metrics, and multiple columns at the target conversation width. Wrap or reduce adjacent items when space is limited; do not assume identical client widths. 2. Check long titles, metrics, and multiple columns at the target conversation width. Wrap or reduce adjacent items when space is limited; do not assume identical client widths.
3. `Image.variant=smallFeature` is a starting point for illustrated cards; adjust to the asset and protocol size guidance. 3. `Image.variant=smallFeature` is a starting point for illustrated cards; adjust to the asset and protocol size guidance. Use [content composition](patterns/content.md) for other image variants and media behavior.
4. Usually emphasize one primary action within a visible action area. Assess separate Tabs or states independently, not by counting every Button in the component list. Label actions by what they do, not by phrases such as “click the button below.” 4. Usually emphasize one primary action within a visible action area. Assess separate Tabs or states independently, not by counting every Button in the component list. Label actions by what they do, not by phrases such as “click the button below.”
5. Remove empty rows and dividers without information value. Choose truncation, full display, or collapse for long content; do not hide necessary text with `maxLine`. 5. Remove empty rows and dividers without information value. Choose truncation, full display, or collapse for long content; do not hide necessary text with `maxLine`.
6. Check media URLs under the host policy; lint does not fetch them. Prefer Markdown table syntax for static tabular data. Limit rows or link to details for long tables. If a separate `Table` is explicitly required, its pagination is local, so provide the data at once. 6. Check media URLs under the host policy; lint does not fetch them. Prefer Markdown table syntax for static tabular data. Limit rows or link to details for long tables. If a separate `Table` is explicitly required, its pagination is local, so provide the data at once; see [report composition](patterns/report.md).
7. Query a component for defaults and conditions under which a field takes effect. The host may apply defaults when fields such as `Image.variant` are omitted. 7. Query a component for defaults and conditions under which a field takes effect. The host may apply defaults when fields such as `Image.variant` are omitted.
Keep related content close and separate groups with appropriate space. Set `gap` and `padding` on the containers responsible for layout rather than stacking padding at several levels. Multiples of 2 are a reasonable spacing starting point, adjustable for host defaults, density, and user requirements; they are not validation rules. Keep related content close and separate groups with appropriate space. Set `gap` and `padding` on the containers responsible for layout rather than stacking padding at several levels. Multiples of 2 are a reasonable spacing starting point, adjustable for host defaults, density, and user requirements; they are not validation rules.
@@ -107,7 +109,7 @@ These are authoring defaults, not Schema restrictions or cross-client rendering
Omit root `padding` for ordinary cards: omission allows default insets, while explicit `0` can leave mobile content flush against the edge. Do not add a second whole-body inset by default; local content panels may still need padding. Omit root `padding` for ordinary cards: omission allows default insets, while explicit `0` can leave mobile content flush against the edge. Do not add a second whole-body inset by default; local content panels may still need padding.
`CardHeader` can trigger header-specific spacing that differs across hosts. When explicitly requested, preserve it and verify insets and any requested corner rounding on target clients; do not assume a full-bleed header or identical spacing. These are authoring defaults, not Schema restrictions: `CardHeader` and explicit zero padding remain valid. Use Text or a Row for ordinary titles rather than defaulting to CardHeader. `CardHeader` can trigger header-specific spacing that differs across hosts. When explicitly requested, preserve it and verify insets and any requested corner rounding on target clients; do not assume a full-bleed header or identical spacing. These are authoring defaults, not Schema restrictions: `CardHeader` and explicit zero padding remain valid.
### Key-value rows ### Key-value rows
@@ -148,18 +150,19 @@ Prefer registered Tokens or semantic component variants (`Tag.theme`, `CardHeade
Use a Token or theme for one meaning within a card. Express important status in text, not color alone. The host resolves actual colors, so a local block needs sufficient contrast against its background. Prefer `Tag.theme` for capsule states and contract-supported Tokens for container colors. Component defaults are valid too; a card does not need a quota of explicit Tokens. See the full [Token index](index/tokens.md). Use a Token or theme for one meaning within a card. Express important status in text, not color alone. The host resolves actual colors, so a local block needs sufficient contrast against its background. Prefer `Tag.theme` for capsule states and contract-supported Tokens for container colors. Component defaults are valid too; a card does not need a quota of explicit Tokens. See the full [Token index](index/tokens.md).
Check text against its actual background in the target theme. Use WCAG 1.4.3's 4.5:1 threshold for normal text and 3:1 for large text, following its [size definitions and exceptions](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html). Semantic Tokens and component defaults do not establish sufficient contrast.
Keep ordinary key-value labels neutral; use semantic color for a value only when it conveys status or trend. Three or fewer non-gray semantic colors is a starting point for an ordinary information card, not a limit on charts, brands, or complex states. Keep ordinary key-value labels neutral; use semantic color for a value only when it conveys status or trend. Three or fewer non-gray semantic colors is a starting point for an ordinary information card, not a limit on charts, brands, or complex states.
## 5. Hierarchy and density ## 5. Hierarchy and density
Choose the main visual area from the content: an available relevant image for illustrated content, key metrics for a report, or the current goal for a task. Give it more emphasis than supporting details without inventing content for decoration. When useful, put secondary details in a collapsed section or a details Tab; keep essential risks and required inputs visible, and do not add Tabs to simple cards. Choose the main visual area from the content: an available relevant image for illustrated content, key metrics for a report, or the current goal for a task. Give it more emphasis than supporting details without inventing content for decoration. Remove copy that merely repeats an adjacent heading or action; consolidate only equivalent missing-information notices, preserving distinct business meanings. When useful, put secondary details in a collapsed section or a details Tab; keep essential risks and required inputs visible, and do not add Tabs to simple cards.
- **Text hierarchy:** emphasize focal `Text` with `bold=true` when needed. Use `variant=body` for normal text or `variant=caption` for smaller supporting text. Optional `colorToken` values include `common_level1_base_color` for primary text and `common_level2_base_color` for secondary text; preserve requested typography. - **Text hierarchy:** emphasize focal `Text` with `bold=true` when needed. Use `variant=body` for normal text or `variant=caption` for smaller supporting text. Optional `colorToken` values include `common_level1_base_color` for primary text and `common_level2_base_color` for secondary text; preserve requested typography.
- **Top-level grouping:** two to five groups can guide a complex card. A short notice may have one, and complex content may need more. - **Top-level grouping:** two to five groups can guide a complex card. A short notice may have one, and complex content may need more.
- **Group with containers:** put related fields in one `Column` or `Card`. Use `Divider` between semantic sections, such as content and actions, not between every row. - **Group with containers:** put related fields in one `Column` or `Card`. Use `Divider` between semantic sections, such as content and actions, not between every row.
- **Contain long content:** use `CollapsiblePanel(defaultExpanded=false)` for details. Offer a “View all” `Link` when a real full-content destination exists. - **Contain long content:** use `CollapsiblePanel(defaultExpanded=false)` for details. Offer a “View all” `Link` when a real full-content destination exists.
- **Separate display fallback from business data:** missing information may display “—” or “None yet,” or omit an empty section. Never write display fallback into a form, number, or boolean value. Empty string, unchecked `false`, unselected `[]`, and numeric `0` retain their distinct meanings. - **Separate display fallback from business data:** missing information may display “—” or “None yet,” or omit an empty section. Never write display fallback into a form, number, or boolean value. Empty string, unchecked `false`, unselected `[]`, and numeric `0` retain their distinct meanings.
## 6. Revise feedback at the right field ## 6. Revise feedback at the right field
After a user reviews the card, locate the affected fields and check related effects: After a user reviews the card, locate the affected fields and check related effects:
@@ -178,10 +181,8 @@ Rerun lint and deliver the revised file and check results. Send a real card only
## Media resources ## Media resources
Prefer verified HTTPS assets, including avatars, Markdown images and bound values. Preserve explicitly requested inline Base64 resources and disclose unverified client compatibility. Valid encoding does not establish image decoding, transport limits or client support. Resource preflight adds `resource.base64_image_unverified` warnings and rejects malformed encoding; it does not fetch URLs or resolve bindings and Markdown destinations. Prefer verified HTTPS assets. Retain another URL scheme or relative address only when allowed by the field contract and verified in the target host. This authoring policy avoids inline `data:` resources, including Base64; it does not change public Schema validity or establish client support. Explain the policy and obtain a compatible asset before delivery.
Resource preflight rejects literal `data:` values in component resource fields with `resource.inline_data_unsupported`. Markdown content and `fallbackMarkdown` containing `data:` text receive a nonblocking `resource.markdown_data_unverified` warning for manual review, including when that text is only a code example or comment. This is a marker check, not a Markdown/HTML parser or a complete resource detector. Review image destinations before delivery; an absent warning proves neither absence of inline images nor loading support. For example, an Image URL bound to a `data:` value receives no diagnostic. Table rows and chart data/config remain business values. The check does not fetch URLs or evaluate bindings/functions; verify resolved resources and client loading separately.
Use supplied or verified assets. If one is unavailable, disclose the gap or ask for an accessible URL; do not invent URLs, impersonate a real avatar with an unrelated image, or silently upload private screenshots. Any agreed placeholder must be identified as a substitution, not faithful reconstruction. Use supplied or verified assets. If one is unavailable, disclose the gap or ask for an accessible URL; do not invent URLs, impersonate a real avatar with an unrelated image, or silently upload private screenshots. Any agreed placeholder must be identified as a substitution, not faithful reconstruction.
## Content hierarchy
Choose a focal conclusion, group related evidence, and keep supporting metadata quiet. Do not add titles, metrics, or actions merely to fill a template. See [information roles](#2-information-roles-and-blocks) and [hierarchy and density](#5-hierarchy-and-density) for layout choices.
@@ -14,3 +14,23 @@ Use for an approval request, schedule item, task, order, or another object with
`CardHeader.trailing` is one component ID. `Button.child` references Text; the current renderer does not support wrapping arbitrary layouts such as Row in a Button. `ButtonGroup` items are not child component IDs and do not take `checks`. For a clickable illustrated block, see the container-action combination in [content](content.md). `CardHeader.trailing` is one component ID. `Button.child` references Text; the current renderer does not support wrapping arbitrary layouts such as Row in a Button. `ButtonGroup` items are not child component IDs and do not take `checks`. For a clickable illustrated block, see the container-action combination in [content](content.md).
Preserve recognizable date and timezone in schedules, and show old and new values together when relevant. Update decision and task state from a real business result; clicking alone does not mean success. Add only necessary inputs from the [form pattern](form.md) when the user must provide a comment or parameters. Preserve recognizable date and timezone in schedules, and show old and new values together when relevant. Update decision and task state from a real business result; clicking alone does not mean success. Add only necessary inputs from the [form pattern](form.md) when the user must provide a comment or parameters.
## Short notifications
Use for a short result, risk alert, or status change. Choose by the event semantics, reading behavior, and expected action, not by words such as "notification" or "reminder" alone. A decision, investigation, report, or data-entry request may need a different composition.
Make the event and its consequence easy to scan. Group the cause and related observations together, then place the next step after the explanation. Keep source and status secondary unless the status itself is the focal fact. A short notice can still need these distinct semantic groups; do not flatten them into one paragraph or give every sentence an equally prominent colored box.
| Needed presentation | Optional components | When to choose them |
|---|---|---|
| Compact reminder | `Row > Icon + Text + Link`, then `Text` or `Markdown` | Title, source, and detail destination in one row; add icon and link only if useful |
| Title and explanation | `Column > Text + Markdown` | Add status or actions only when needed |
| Result or exception | `Row > Tag + Text`, then explanation | A distinct status and body; a very short result may use text alone |
| Capacity or quota | `Text + ProgressBar + Text` | A real computable ratio; show a number directly if only balance or remaining amount is known |
`Tag` supplies status styling without hand-built colors. For a requested `CardHeader`, use the [header and inset guidance](../design.md#headers-and-card-insets); use [image reconstruction](../design.md#image-reconstruction) for a branded title icon.
- Keep the event or issue title as the focal fact. Do not replace it with a generic header or duplicate the sole title in a separate identity strip.
- For appearance-only reproduction, `ButtonGroup.buttons[]` items may omit `action` and keep their noninteractive appearance. Standalone `Button.action` remains required by the protocol.
Use [information roles](../design.md#2-information-roles-and-blocks), [hierarchy and density](../design.md#5-hierarchy-and-density), and [target-width guidance](../design.md#3-host-behavior-and-layout) for grouping, emphasis, and wrapping.
@@ -13,6 +13,8 @@ Use for parameters, settings, ratings, and comments. For display-only informatio
`ChoicePicker.variant` sets single or multiple selection. `displayStyle` sets presentation: `chips` for inline tags, `checkbox` for inline vertical choices, and `dropdown` (the default) for a compact entry opening the native picker. `chips` and `checkbox` write local state through `value` binding without firing an action; the submit Button collects their values in `context`. A configured `dropdown` action fires after confirming a selection. `ChoicePicker.variant` sets single or multiple selection. `displayStyle` sets presentation: `chips` for inline tags, `checkbox` for inline vertical choices, and `dropdown` (the default) for a compact entry opening the native picker. `chips` and `checkbox` write local state through `value` binding without firing an action; the submit Button collects their values in `context`. A configured `dropdown` action fires after confirming a selection.
When a separate Button submits edited values, verify that bindings carry the current values into its event context in the target renderer. A changed control display or a picker callback containing the new value does not prove that the independent submit action reads it.
Bind collected values with suitable initial values. Even single-select `ChoicePicker.value` uses an array of strings. Omitting `TextField.value` does not create a writeback path. Put submit validation on standalone `Button.checks`, not on TextField or a ButtonGroup item. Bind collected values with suitable initial values. Even single-select `ChoicePicker.value` uses an array of strings. Omitting `TextField.value` does not create a writeback path. Put submit validation on standalone `Button.checks`, not on TextField or a ButtonGroup item.
Make each field recognizable before and after input. Component labels have different roles: Make each field recognizable before and after input. Component labels have different roles:
@@ -1,22 +0,0 @@
# Notification: short results and status reminders
Use for a short result, risk alert, or status change. When there are many facts or a decision, use the [detail pattern](detail.md).
Start with the [content hierarchy guidance](../design.md#content-hierarchy). A notice with a cause, two observations, and a proposed fix needs distinct semantic groups even when it is short. Keep the source and status quiet, make the consequence scannable, keep related evidence together, and place the next step after the explanation. Do not flatten all roles into one Markdown paragraph or create one equally prominent colored box per sentence.
| Needed presentation | Optional components | When to choose them |
|---|---|---|
| Compact reminder | `Row > Icon + Text + Link`, then `Text` or `Markdown` | Title, source, and detail destination in one row; add icon and link only if useful |
| Title and explanation | `Column > Text + Markdown` | Add status or actions only when needed |
| Result or exception | `Row > Tag + Text`, then explanation | A distinct status and body; a very short result may use text alone |
| Capacity or quota | `Text + ProgressBar + Text` | A real computable ratio; show a number directly if only balance or remaining amount is known |
`Tag` supplies status styling without hand-built colors. For a requested `CardHeader`, use the [header and inset guidance](../design.md#headers-and-card-insets).
Do not select this pattern from words such as “notification”, “reminder”, or “issue” alone. First infer the product job from the user's intent, the event semantics, the reading behavior, and the expected action. A message is notification-shaped when its primary job is to make a recipient aware of an event, status change, risk, or result; a detailed investigation, approval decision, periodic report, or data-entry request may need a different pattern even if its copy contains notification words.
Keep the event or issue title as the focal fact; do not replace it with a generic header or duplicate the sole title in a separate identity strip.
Validate at the actual target container width, including narrower layouts when supported by the host. A 360px preview is only a sample design starting point, not a minimum width or an acceptance gate. Check wrapping, overflow and actions at the widths the card will actually use.
`ButtonGroup.buttons[]` contains inline objects. For static reproduction, each item may omit `action` and keep only its noninteractive appearance. Standalone `Button.action` remains required by the protocol. Use [image reconstruction](../design.md#image-reconstruction) for a branded title icon.
@@ -6,12 +6,16 @@ For one status, a short Text or Markdown block, optionally in one panel, is enou
## State and presentation ## State and presentation
For a standalone Markdown running indicator, prefix `content` with `[AIGenerating]` and remove it on completion, failure, or cancellation. Panel-based execution uses the conventions below.
For an Agent reasoning or tool-execution flow, use this recommended pattern. Ordinary upload or task progress does not require reasoning styling or generation effects: For an Agent reasoning or tool-execution flow, use this recommended pattern. Ordinary upload or task progress does not require reasoning styling or generation effects:
- The outer `CollapsiblePanel(variant=reasoning)` shows the current operation in its title, with the host's `[AIGenerating]` convention and `textEffect: shimmer` while running. On completion, show completion text and omit both the generating marker and `textEffect`. - The outer `CollapsiblePanel(variant=reasoning)` shows the current operation in its title, with the host's `[AIGenerating]` convention and `textEffect: shimmer` while running. On completion, show completion text and omit both the generating marker and `textEffect`.
- When a public planning step belongs in the requested flow, a thinking placeholder such as `[AIGenerating]Thinking...` can precede the first tool group. Use only a concise public plan or operation summary as its timeline item; never expose private reasoning or invent an unobserved step. A placeholder is presentation, not evidence that reasoning occurred. When present, keep this item first; it is not a tool group and has no loading or completion icon. Reuse a host-owned placeholder rather than creating another card.
- Group related tools in nested `indented` panels when useful. While running, set `icon` to the light loading GIF URL and `darkIcon` to the dark loading GIF URL. For the requested DingTalk named-icon completion style, replace `icon` with `"Check_L_outlined"` when the tool group succeeds and omit the loading `darkIcon`. Do not show a success check for failed or cancelled groups. - Group related tools in nested `indented` panels when useful. While running, set `icon` to the light loading GIF URL and `darkIcon` to the dark loading GIF URL. For the requested DingTalk named-icon completion style, replace `icon` with `"Check_L_outlined"` when the tool group succeeds and omit the loading `darkIcon`. Do not show a success check for failed or cancelled groups.
- Panel icons accept image URLs or registered icon names. Tool-row named icons use `Icon.name`. - Panel icons accept image URLs or registered icon names. Tool-row named icons use `Icon.name`.
- Tool rows use `Icon.name`, chosen from [IconName](../protocol/common-types-visual.json#/$defs/IconName) for the tool type, not image URLs. `SpinLoading_L_outlined` alone does not enable rotation. Keep tool icons separate from panel status images; the outer panel uses the generation marker rather than a loading icon. - Tool rows use `Icon.name`, chosen from [IconName](../protocol/common-types-visual.json#/$defs/IconName) for the tool type, not image URLs. `SpinLoading_L_outlined` alone does not enable rotation. Keep tool icons separate from panel status images; the outer panel uses the generation marker rather than a loading icon.
- When authorized diagnostics need input or output details, show selected, sanitized excerpts rather than raw tool dumps; exclude credentials, private reasoning, and unrelated inputs. Make the call its own nested `indented` panel: the title names the call, `icon` carries the tool-type icon name, and a Markdown child shows the excerpt as a fenced code block. Set `maxHeight` so long code scrolls inside the panel, and replace only that Markdown definition when the result arrives. Code-block text color, syntax colors, and background are renderer-owned; Markdown has no color field, and `<font color>` inside a fence is shown as literal text. Use `Text.colorToken` outside the block when a value must be highlighted.
- For the compact example style, use `common_level2_base_color` on panel titles and tool rows, and `common_action_text_style__font_size` on tool-row Text. These are styling defaults, not protocol requirements; preserve requested styling. Panel-title size is renderer-owned; do not invent a size field. - For the compact example style, use `common_level2_base_color` on panel titles and tool rows, and `common_action_text_style__font_size` on tool-row Text. These are styling defaults, not protocol requirements; preserve requested styling. Panel-title size is renderer-owned; do not invent a size field.
- Children may contain any contract-supported composition, including Markdown, File, charts, or forms. Content placement and reveal timing are task choices, not restrictions imposed by the execution state. - Children may contain any contract-supported composition, including Markdown, File, charts, or forms. Content placement and reveal timing are task choices, not restrictions imposed by the execution state.
@@ -36,10 +40,10 @@ Initialize new bound fields with correctly typed values before using them. A tex
The message positions below are one-based. Read one matching JSON, not both by default. The message positions below are one-based. Read one matching JSON, not both by default.
| Example | Batch 1 | Batch 2 | Batch 3 | | Example | Batch 1 | Batch 2 | Batch 3 | Batch 4 |
|---|---|---|---| |---|---|---|---|---|
| [agent-run-progress.json](../protocol/examples/agent-run-progress.json) | Messages 1–3: initialize reading | Message 4: finish reading, start analysis | None; intentionally stays running | | [agent-run-progress.json](../protocol/examples/agent-run-progress.json) | Messages 1–3: initialize thinking | Message 4: start reading | Message 5: finish reading, start analysis | None; intentionally stays running |
| [agent-run.json](../protocol/examples/agent-run.json) | Messages 1–3: initialize reading | Message 4: finish reading, start analysis | Message 5: complete and reveal results | | [agent-run.json](../protocol/examples/agent-run.json) | Messages 1–3: initialize thinking | Message 4: start reading | Message 5: finish reading, start analysis | Message 6: complete and reveal results |
The batches describe successive A2UI states on the same Surface; applying the entire array at once may hide intermediate states. Timing belongs to the consuming application, not the JSON. Keep the running-only example unfinished when demonstrating that state. For a requested complete lifecycle, include the actual completed, failed, or cancelled content state; do not infer success from the end of a message array. The batches describe successive A2UI states on the same Surface; applying the entire array at once may hide intermediate states. Timing belongs to the consuming application, not the JSON. Keep the running-only example unfinished when demonstrating that state. For a requested complete lifecycle, include the actual completed, failed, or cancelled content state; do not infer success from the end of a message array.
@@ -9,9 +9,11 @@ Use for a data summary, analytical report, monitoring, or execution result. Sele
| Metrics at wide and narrow widths | `ColumnLayout > metric groups` | Query and enable `responsive` when narrow layouts must become vertical; this is not an automatic wrapping grid | | Metrics at wide and narrow widths | `ColumnLayout > metric groups` | Query and enable `responsive` when narrow layouts must become vertical; this is not an automatic wrapping grid |
| Status or exception list | `Column/List > Row(Icon/Tag, Text)` | Status and separate explanation per item; choose a registered icon | | Status or exception list | `Column/List > Row(Icon/Tag, Text)` | Status and separate explanation per item; choose a registered icon |
| Static tabular records | `Markdown` with table syntax | Exact comparison by column; prefer this over a separate `Table` component | | Static tabular records | `Markdown` with table syntax | Exact comparison by column; prefer this over a separate `Table` component |
| Trend or distribution | `Chart` | Data suited to a graphic; add a table separately when useful | | Trend or distribution | `Chart` | Data suited to a graphic; query its contract, and add a table separately when useful |
| Method and full report | `CollapsiblePanel`, `File`, or `Link` | Supplemental details, an attachment, or a real detail destination | | Method and full report | `CollapsiblePanel`, `File`, or `Link` | Supplemental details, an attachment, or a real detail destination |
See [information roles](../design.md#2-information-roles-and-blocks) for the underlying layout notation.
For static tabular data, use table syntax in `Markdown.content` by default. Use a separate `Table` only when explicitly requested or when a required capability calls for it; query its contract first. `Table.data` is structured data and its pagination runs locally. `Chart.data` holds chart type and data, not component IDs. `ColumnLayout.columns` does not automatically wrap any number of items into groups; build actual columns according to its responsive contract. For static tabular data, use table syntax in `Markdown.content` by default. Use a separate `Table` only when explicitly requested or when a required capability calls for it; query its contract first. `Table.data` is structured data and its pagination runs locally. `Chart.data` holds chart type and data, not component IDs. `ColumnLayout.columns` does not automatically wrap any number of items into groups; build actual columns according to its responsive contract.
Preserve numeric units, time ranges, and comparison baselines. Do not represent missing data as zero. Use [layout guidance](../design.md#3-host-behavior-and-layout), [text hierarchy](../design.md#5-hierarchy-and-density), and [color guidance](../design.md#4-color-discipline) rather than copying cramped, truncated, or gradient-heavy sample cards. Preserve numeric units, time ranges, and comparison baselines. Do not represent missing data as zero. Use [layout guidance](../design.md#3-host-behavior-and-layout), [text hierarchy](../design.md#5-hierarchy-and-density), and [color guidance](../design.md#4-color-discipline) rather than copying cramped, truncated, or gradient-heavy sample cards.
@@ -116,7 +116,7 @@
}, },
"content": { "content": {
"$ref": "./common-types-basic.json#/$defs/DynamicString", "$ref": "./common-types-basic.json#/$defs/DynamicString",
"description": "Markdown body. In addition to standard Markdown, DingTalk supports these @-mention extensions: `<a atId=\"{openDingTalkId}\">nickname</a>` mentions a specific member, and `<a atId=\"all\">everyone</a>` mentions everyone (`all` is a reserved ID). Every `atId` in the body must be listed in the card-creation parameter `atIdList`; the reserved ID `all` does not need to be listed. The server resolves each `openDingTalkId` to a user. Write only the display name inside the tag and do not add `@` yourself; the renderer prepends `@` when rendering an at node, so a self-written one renders as `@@`. If an `atId` is not listed or cannot be resolved, it falls back to plain text without highlighting or click behavior and does not report an error. The `<@openDingTalkId>` and `<@all>` placeholders used by IM Markdown messages **do not work** in the card flow and are displayed literally. Server-side resolution of openDingTalkId is not yet available, so mentions currently do not take effect and degrade to plain text as described above. The body syntax and this field's contract stay unchanged once it ships." "description": "Markdown body. In addition to standard Markdown, DingTalk supports these @-mention extensions: `<a atId=\"{openDingTalkId}\">nickname</a>` mentions a specific member, and `<a atId=\"all\">everyone</a>` mentions everyone (`all` is a reserved ID). Every member `atId` in the body must be declared at send time via `--at-open-dingtalk-ids` (sent as the card-creation parameter `atOpenDingTalkIds`, at most 100 declarations including duplicates); the reserved ID `all` is declared with `--at-all` (sent as `atAll`) and never enters the id list. The server resolves each declared `openDingTalkId` to a user and rejects card creation with an INVALID_PARAM error when a declared id is invalid or cannot be resolved. Write only the display name inside the tag and do not add `@` yourself; the renderer prepends `@` when rendering an at node, so a self-written one renders as `@@`. An `atId` used in the body but not declared is neither highlighted nor notified, so declare every id you use. The `<@openDingTalkId>` and `<@all>` placeholders used by IM Markdown messages **do not work** in the card flow and are displayed literally. When sending with `--at-all`, the CLI rewrites the body's `<a atId=\"all\">` tag to the server highlight alias and sends `atAll=true`. The body syntax and this field's contract stay unchanged once it ships."
}, },
"weight": { "weight": {
"type": "number", "type": "number",
@@ -30,7 +30,7 @@
{ {
"id": "executionPanel", "id": "executionPanel",
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "[AIGenerating]Reading...", "title": "[AIGenerating]Thinking...",
"children": [ "children": [
"executionTimeline" "executionTimeline"
], ],
@@ -44,6 +44,43 @@
"id": "executionTimeline", "id": "executionTimeline",
"component": "Column", "component": "Column",
"children": [ "children": [
"thinking"
],
"gap": 6
},
{
"id": "thinking",
"component": "Text",
"text": "Plan: read the sample sales data first, then total orders and revenue by region.",
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-progress",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Reading...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"textEffect": "shimmer"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking",
"toolGroup_read" "toolGroup_read"
], ],
"gap": 6 "gap": 6
@@ -53,7 +90,7 @@
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Reading...", "title": "Reading...",
"children": [ "children": [
"toolGroup_read_row" "toolGroup_read_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
@@ -63,29 +100,23 @@
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif" "darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
}, },
{ {
"id": "toolGroup_read_row", "id": "toolGroup_read_item",
"component": "Row", "component": "CollapsiblePanel",
"align": "center", "title": "Read sample data",
"children": [ "children": [
"toolGroup_read_icon", "toolGroup_read_detail"
"toolGroup_read_text" ],
]
},
{
"id": "toolGroup_read_icon",
"component": "Icon",
"name": "Folder_L_outlined",
"colorToken": "common_level2_base_color"
},
{
"id": "toolGroup_read_text",
"component": "Text",
"text": "Read sample data",
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size", "variant": "indented",
"bold": false, "fallbackMarkdown": "Read sample data",
"maxLine": 2, "defaultExpanded": false,
"weight": 1 "icon": "Folder_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```"
} }
] ]
} }
@@ -112,7 +143,7 @@
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Read complete", "title": "Read complete",
"children": [ "children": [
"toolGroup_read_row" "toolGroup_read_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
@@ -123,17 +154,23 @@
"id": "executionTimeline", "id": "executionTimeline",
"component": "Column", "component": "Column",
"children": [ "children": [
"thinking",
"toolGroup_read", "toolGroup_read",
"toolGroup_summary" "toolGroup_summary"
], ],
"gap": 6 "gap": 6
}, },
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```\n\n**Result** · 4 rows × 4 columns\n\n```csv\nregion,month,orders,revenue\nEast,2026-07,1280,356400\nWest,2026-07,964,271900\nNorth,2026-07,742,198300\nSouth,2026-07,1105,309800\n```"
},
{ {
"id": "toolGroup_summary", "id": "toolGroup_summary",
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Analyzing...", "title": "Analyzing...",
"children": [ "children": [
"toolGroup_summary_row" "toolGroup_summary_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
@@ -143,29 +180,23 @@
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif" "darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
}, },
{ {
"id": "toolGroup_summary_row", "id": "toolGroup_summary_item",
"component": "Row", "component": "CollapsiblePanel",
"align": "center", "title": "Calculate summary",
"children": [ "children": [
"toolGroup_summary_icon", "toolGroup_summary_detail"
"toolGroup_summary_text" ],
]
},
{
"id": "toolGroup_summary_icon",
"component": "Icon",
"name": "CodeProgram_L_outlined",
"colorToken": "common_level2_base_color"
},
{
"id": "toolGroup_summary_text",
"component": "Text",
"text": "Calculate summary",
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size", "variant": "indented",
"bold": false, "fallbackMarkdown": "Calculate summary",
"maxLine": 2, "defaultExpanded": false,
"weight": 1 "icon": "CodeProgram_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_summary_detail",
"component": "Markdown",
"content": "```python\nimport pandas as pd\n\ndf = pd.read_csv(\"data/sales_sample.csv\")\nsummary = (\n df.groupby(\"region\")[[\"orders\", \"revenue\"]]\n .sum()\n .sort_values(\"revenue\", ascending=False)\n)\n```"
} }
] ]
} }
@@ -30,7 +30,7 @@
{ {
"id": "executionPanel", "id": "executionPanel",
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "[AIGenerating]Reading...", "title": "[AIGenerating]Thinking...",
"children": [ "children": [
"executionTimeline" "executionTimeline"
], ],
@@ -44,6 +44,43 @@
"id": "executionTimeline", "id": "executionTimeline",
"component": "Column", "component": "Column",
"children": [ "children": [
"thinking"
],
"gap": 6
},
{
"id": "thinking",
"component": "Text",
"text": "Plan: read the sample sales data first, then total orders and revenue by region.",
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
}
]
}
},
{
"version": "v1.0",
"updateComponents": {
"surfaceId": "open-streaming",
"components": [
{
"id": "executionPanel",
"component": "CollapsiblePanel",
"title": "[AIGenerating]Reading...",
"children": [
"executionTimeline"
],
"colorToken": "common_level2_base_color",
"variant": "reasoning",
"fallbackMarkdown": "Execution details",
"textEffect": "shimmer"
},
{
"id": "executionTimeline",
"component": "Column",
"children": [
"thinking",
"toolGroup_read" "toolGroup_read"
], ],
"gap": 6 "gap": 6
@@ -53,7 +90,7 @@
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Reading...", "title": "Reading...",
"children": [ "children": [
"toolGroup_read_row" "toolGroup_read_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
@@ -63,29 +100,23 @@
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif" "darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
}, },
{ {
"id": "toolGroup_read_row", "id": "toolGroup_read_item",
"component": "Row", "component": "CollapsiblePanel",
"align": "center", "title": "Read sample data",
"children": [ "children": [
"toolGroup_read_icon", "toolGroup_read_detail"
"toolGroup_read_text" ],
]
},
{
"id": "toolGroup_read_icon",
"component": "Icon",
"name": "Folder_L_outlined",
"colorToken": "common_level2_base_color"
},
{
"id": "toolGroup_read_text",
"component": "Text",
"text": "Read sample data",
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size", "variant": "indented",
"bold": false, "fallbackMarkdown": "Read sample data",
"maxLine": 2, "defaultExpanded": false,
"weight": 1 "icon": "Folder_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```"
} }
] ]
} }
@@ -112,27 +143,34 @@
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Read complete", "title": "Read complete",
"children": [ "children": [
"toolGroup_read_row" "toolGroup_read_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
"fallbackMarkdown": "Execution details" "fallbackMarkdown": "Execution details",
"icon": "Check_L_outlined"
}, },
{ {
"id": "executionTimeline", "id": "executionTimeline",
"component": "Column", "component": "Column",
"children": [ "children": [
"thinking",
"toolGroup_read", "toolGroup_read",
"toolGroup_summary" "toolGroup_summary"
], ],
"gap": 6 "gap": 6
}, },
{
"id": "toolGroup_read_detail",
"component": "Markdown",
"content": "```python\nread_file(\n \"data/sales_sample.csv\",\n limit=5,\n)\n```\n\n**Result** · 4 rows × 4 columns\n\n```csv\nregion,month,orders,revenue\nEast,2026-07,1280,356400\nWest,2026-07,964,271900\nNorth,2026-07,742,198300\nSouth,2026-07,1105,309800\n```"
},
{ {
"id": "toolGroup_summary", "id": "toolGroup_summary",
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Analyzing...", "title": "Analyzing...",
"children": [ "children": [
"toolGroup_summary_row" "toolGroup_summary_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
@@ -142,29 +180,23 @@
"darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif" "darkIcon": "https://g.alicdn.com/dingding/card_a2ui/0.1.1/img/loading-circular/loading_circular_dark.D-pS11K.gif"
}, },
{ {
"id": "toolGroup_summary_row", "id": "toolGroup_summary_item",
"component": "Row", "component": "CollapsiblePanel",
"align": "center", "title": "Calculate summary",
"children": [ "children": [
"toolGroup_summary_icon", "toolGroup_summary_detail"
"toolGroup_summary_text" ],
]
},
{
"id": "toolGroup_summary_icon",
"component": "Icon",
"name": "CodeProgram_L_outlined",
"colorToken": "common_level2_base_color"
},
{
"id": "toolGroup_summary_text",
"component": "Text",
"text": "Calculate summary",
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size", "variant": "indented",
"bold": false, "fallbackMarkdown": "Calculate summary",
"maxLine": 2, "defaultExpanded": false,
"weight": 1 "icon": "CodeProgram_L_outlined",
"maxHeight": 240
},
{
"id": "toolGroup_summary_detail",
"component": "Markdown",
"content": "```python\nimport pandas as pd\n\ndf = pd.read_csv(\"data/sales_sample.csv\")\nsummary = (\n df.groupby(\"region\")[[\"orders\", \"revenue\"]]\n .sum()\n .sort_values(\"revenue\", ascending=False)\n)\n```"
} }
] ]
} }
@@ -190,11 +222,12 @@
"component": "CollapsiblePanel", "component": "CollapsiblePanel",
"title": "Analysis complete", "title": "Analysis complete",
"children": [ "children": [
"toolGroup_summary_row" "toolGroup_summary_item"
], ],
"colorToken": "common_level2_base_color", "colorToken": "common_level2_base_color",
"variant": "indented", "variant": "indented",
"fallbackMarkdown": "Execution details" "fallbackMarkdown": "Execution details",
"icon": "Check_L_outlined"
}, },
{ {
"id": "root", "id": "root",
@@ -217,7 +250,7 @@
{ {
"id": "answer", "id": "answer",
"component": "Markdown", "component": "Markdown",
"content": "Analysis complete. Sample file attached." "content": "**July 2026 sales summary**\n\nTotal revenue **1,136,400** from **4,091** orders across 4 regions. East leads with 356,400 (31.4%).\n\n| Region | Orders | Revenue | Share |\n|---|---:|---:|---:|\n| East | 1,280 | 356,400 | 31.4% |\n| South | 1,105 | 309,800 | 27.3% |\n| West | 964 | 271,900 | 23.9% |\n| North | 742 | 198,300 | 17.4% |"
}, },
{ {
"id": "artifact", "id": "artifact",
@@ -226,6 +259,11 @@
"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
"mimeType": "application/pdf", "mimeType": "application/pdf",
"description": "Public sample PDF; not a generated report" "description": "Public sample PDF; not a generated report"
},
{
"id": "toolGroup_summary_detail",
"component": "Markdown",
"content": "```python\nimport pandas as pd\n\ndf = pd.read_csv(\"data/sales_sample.csv\")\nsummary = (\n df.groupby(\"region\")[[\"orders\", \"revenue\"]]\n .sum()\n .sort_values(\"revenue\", ascending=False)\n)\n```\n\n**Result** · 4 rows × 3 columns\n\n```csv\nregion,orders,revenue\nEast,1280,356400\nSouth,1105,309800\nWest,964,271900\nNorth,742,198300\n```"
} }
] ]
} }
@@ -0,0 +1,32 @@
# A2UI templates
Localized scene labels and summaries are intentional discovery copy. These complete local public examples use fictional data, without Alibaba business content or source business identities. Preserve supplied real-task facts when deriving a card. Each scene has one complete JSON for mobile and desktop; read only the selected file.
## Reuse workflow
Choose by information structure, data types, collection behavior, and required interactions, not scene name alone. For changes confined to bound data, locate `"updateDataModel"` and read its data first. Copy the complete JSON to a working file, edit intended values with a JSON parser, keep message Surface IDs consistent, and validate the complete result. A data excerpt alone is not a new card. Inspect components when changing literal text, layout, resources, or actions.
For an existing instance, keep its IDs and state and send only authorized deltas; do not replay initialization or overwrite user input. Consult the selected pattern only for the behavior being changed.
Preserve the selected template's frame and insets as written. Some templates set root `padding` to `0` and inset an inner content container; others omit root `padding` and rely on host insets. Do not add, remove, or zero root `padding` when reusing a template. The ordinary new-card defaults in design guidance do not replace an existing template's intentional styling.
## Reuse boundaries
`Fixed` records the highest referenced array index plus one: these are indexed component slots, and extra values do not add items. `Repeated` identifies collections used by component children templates, including Column and Row; it does not describe Table or Chart datasets. Inspect components when collection shape changes.
`Events` are application wiring points that require real business handlers. Client-side function calls, such as host actions like `copyText` or `showModal`, are not listed; inspect a control's action before reusing it for a business operation. `Input events` lists button events whose context includes bound input paths, including skip actions that retain input. It does not prove edited values reach that event on a client or that a business operation succeeds.
Data-only changes affect existing bindings. A new status field does not automatically disable buttons, change visibility, or add a completed state: inspect the component definitions for those bindings. These public examples do not supply a complete business state machine; production bindings and authorized data paths belong to the business runtime.
| Scene | Description | Related patterns | Reuse boundary | A2UI | Notes |
|---|---|---|---|---|---|
| 企业信息 | 展示企业概况、风险与交易信息,支持预览下载报告附件 | [detail](../patterns/detail.md), [content](../patterns/content.md) | Fixed: none; Repeated: `/report/background`, `/report/findings`; Events: `company_investigation_download_attachment`, `company_investigation_preview_attachment`; Input events: none | [company-investigation](company-investigation/a2ui.json) | — |
| 混合审批汇报 | 同时包含待审批和已处理,两个 Tabs 分别展示 | [report](../patterns/report.md), [detail](../patterns/detail.md) | Fixed: none; Repeated: `/approvals/handled`, `/approvals/pending`; Events: none; Input events: none | [expense-approval](expense-approval/a2ui.json) | — |
| 单类审批列表 | 仅一类审批,无 Tabs;items 支持单条或多条 | [report](../patterns/report.md), [detail](../patterns/detail.md) | Fixed: none; Repeated: `/approvals/items`; Events: none; Input events: none | [expense-approval-list](expense-approval-list/a2ui.json) | — |
| 会议邀约 | 选择主题、参会人及线上线下方式,编辑地点和议程,校验后提交会议创建请求 | [form](../patterns/form.md) | Fixed: `/meeting/topics`=2; Repeated: none; Events: `meeting_invitation_submit`; Input events: `meeting_invitation_submit` | [meeting-invitation](meeting-invitation/a2ui.json) | Submits current form data after validation; a service callback must create the meeting and confirm success. Verify conditional visibility and picker rendering on target clients. |
| 会后总结 | 整理会议结论、待办与相关事项,支持创建任务设置提醒 | [detail](../patterns/detail.md) | Fixed: `/summary/conclusions`=3, `/summary/conclusions/2/details`=3, `/summary/todos`=3; Repeated: none; Events: `post_meeting_create_todo`, `post_meeting_ignore`, `post_meeting_set_reminder`; Input events: none | [post-meeting-summary](post-meeting-summary/a2ui.json) | — |
| 晨间日报·日程 | 汇总今日日程、冲突与重点关注,支持响应邀请处理事务 | [detail](../patterns/detail.md) | Fixed: `/agenda`=4, `/approvals`=2, `/attention`=2, `/timeline/segments`=12, `/todos`=3; Repeated: none; Events: `daily_briefing_accept_event`, `daily_briefing_approve_approval`, `daily_briefing_reject_approval`, `daily_briefing_reject_event`, `daily_briefing_select_section`, `daily_briefing_skip`, `daily_briefing_start_processing`; Input events: none | [daily-agenda-briefing](daily-agenda-briefing/a2ui.json) | — |
| 今日待办事项 | 集中展示今日审批、待办与会前准备,支持审批及查看相关资料 | [detail](../patterns/detail.md) | Fixed: `/approvals`=2, `/attention`=3, `/preparations`=2, `/preparations/0/notes`=2, `/preparations/1/notes`=1, `/todos`=3; Repeated: none; Events: `daily_briefing_approve_approval`, `daily_briefing_download_document`, `daily_briefing_preview_document`, `daily_briefing_reject_approval`, `daily_briefing_select_section`, `daily_briefing_skip`, `daily_briefing_start_processing`; Input events: none | [daily-approval-briefing](daily-approval-briefing/a2ui.json) | — |
| 任务处理 | 勾选会议协调、沟通与准备事项,交由助手继续推进处理 | [form](../patterns/form.md) | Fixed: none; Repeated: `/ui/taskRow`; Events: `task_delegation_select`, `task_delegation_skip`, `task_delegation_submit`; Input events: `task_delegation_skip`, `task_delegation_submit` | [task-delegation](task-delegation/a2ui.json) | Checkbox rows store index arrays internally; selection and submission events expose boolean values. |
| 工作周报 | 整理本周完成、下周计划与风险事项,生成结构化工作周报 | [report](../patterns/report.md) | Fixed: `/report/completed`=3, `/report/completed/2/details`=3, `/report/planned`=3, `/report/planned/2/details`=3, `/report/risks`=2; Repeated: none; Events: none; Input events: none | [weekly-work-report](weekly-work-report/a2ui.json) | — |
| 面试反馈 | 确认面试优点、不足与待改进及结论,服务确认后新增职级与评论消息并锁定已提交表单 | [form](../patterns/form.md) | Fixed: none; Repeated: none; Events: `interview_feedback_submit`, `interview_review_confirm`; Input events: `interview_feedback_submit`, `interview_review_confirm` | [interview-feedback](interview-feedback/a2ui.json) | The two-message flow requires service callbacks to save results, lock submitted forms, and send the next message. |
@@ -0,0 +1,955 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-company-investigation-card-a2ui",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "summary-title-icon",
"component": "Text",
"text": "📝",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "summary-title-label",
"component": "Text",
"text": "综合结论",
"maxLine": 30,
"bold": false,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "summary-title",
"component": "Row",
"children": [
"summary-title-icon",
"summary-title-label"
],
"gap": 4,
"align": "center",
"weight": 1
},
{
"id": "risk-tag",
"component": "Tag",
"text": {
"path": "/report/risk"
},
"theme": "orange"
},
{
"id": "summary-heading",
"component": "Row",
"children": [
"summary-title",
"risk-tag"
],
"gap": 8,
"align": "center"
},
{
"id": "summary",
"component": "Markdown",
"content": {
"path": "/report/summary"
}
},
{
"id": "summary-card-content",
"component": "Column",
"children": [
"summary-heading",
"summary"
],
"gap": 8,
"align": "stretch"
},
{
"id": "summary-card",
"component": "Card",
"child": "summary-card-content",
"padding": 10,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "extended_yellow0_color",
"borderColorToken": "common_line_light_color",
"backgroundColor": "#1FFF9200"
},
{
"id": "company-facts-0-label",
"component": "Text",
"text": "社会信用代码",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-0-value",
"component": "Text",
"text": {
"path": "/company/registration"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-0",
"component": "Row",
"children": [
"company-facts-0-label",
"company-facts-0-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-1-label",
"component": "Text",
"text": "法定代表人",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-1-value",
"component": "Text",
"text": {
"path": "/company/representative"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-1",
"component": "Row",
"children": [
"company-facts-1-label",
"company-facts-1-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-2-label",
"component": "Text",
"text": "成立日期",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-2-value",
"component": "Text",
"text": {
"path": "/company/founded"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-2",
"component": "Row",
"children": [
"company-facts-2-label",
"company-facts-2-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-3-label",
"component": "Text",
"text": "经营状态",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-3-value",
"component": "Text",
"text": {
"path": "/company/state"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_link_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-3",
"component": "Row",
"children": [
"company-facts-3-label",
"company-facts-3-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-4-label",
"component": "Text",
"text": "注册资本",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-4-value",
"component": "Text",
"text": {
"path": "/company/capital"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-4",
"component": "Row",
"children": [
"company-facts-4-label",
"company-facts-4-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-5-label",
"component": "Text",
"text": "注册地址",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-5-value",
"component": "Text",
"text": {
"path": "/company/address"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-5",
"component": "Row",
"children": [
"company-facts-5-label",
"company-facts-5-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-6-label",
"component": "Text",
"text": "所属行业",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-6-value",
"component": "Text",
"text": {
"path": "/company/industry"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-6",
"component": "Row",
"children": [
"company-facts-6-label",
"company-facts-6-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts-7-label",
"component": "Text",
"text": "参保人数",
"maxLine": 30,
"weight": 7,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-7-value",
"component": "Text",
"text": {
"path": "/company/employees"
},
"maxLine": 30,
"weight": 18,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "company-facts-7",
"component": "Row",
"children": [
"company-facts-7-label",
"company-facts-7-value"
],
"gap": 6,
"align": "center"
},
{
"id": "company-facts",
"component": "Column",
"children": [
"company-facts-0",
"company-facts-1",
"company-facts-2",
"company-facts-3",
"company-facts-4",
"company-facts-5",
"company-facts-6",
"company-facts-7"
],
"gap": 6,
"align": "stretch"
},
{
"id": "facts-title-icon",
"component": "Text",
"text": "🪪",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "facts-title-label",
"component": "Text",
"text": "工商信息",
"maxLine": 30,
"bold": true,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "facts-title",
"component": "Row",
"children": [
"facts-title-icon",
"facts-title-label"
],
"gap": 4,
"align": "center"
},
{
"id": "company-card-content",
"component": "Column",
"children": [
"facts-title",
"company-facts"
],
"gap": 8,
"align": "stretch"
},
{
"id": "company-card",
"component": "Card",
"child": "company-card-content",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "common_fg_z1_color",
"borderColorToken": "common_line_light_color"
},
{
"id": "risk-title-icon",
"component": "Text",
"text": "⚠️",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "risk-title-label",
"component": "Text",
"text": "企业风险",
"maxLine": 30,
"bold": true,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "risk-title",
"component": "Row",
"children": [
"risk-title-icon",
"risk-title-label"
],
"gap": 4,
"align": "center"
},
{
"id": "risk-detail-icon",
"component": "Text",
"text": "❗",
"variant": "body",
"bold": false,
"maxLine": 1
},
{
"id": "risk-detail-label",
"component": "Text",
"text": {
"path": "/report/riskDetail"
},
"maxLine": 30,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "risk-detail",
"component": "Row",
"children": [
"risk-detail-icon",
"risk-detail-label"
],
"gap": 4,
"align": "start"
},
{
"id": "risk-callout-content",
"component": "Column",
"children": [
"risk-detail"
],
"gap": 8,
"align": "stretch"
},
{
"id": "risk-callout",
"component": "Card",
"child": "risk-callout-content",
"padding": 10,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "extended_yellow0_color",
"borderColorToken": "common_line_light_color",
"backgroundColor": "#1FFF9200"
},
{
"id": "findings-title",
"component": "Text",
"text": "风险明细",
"maxLine": 30,
"bold": true,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "findings",
"component": "Column",
"children": {
"componentId": "findings-item",
"path": "/report/findings"
},
"gap": 8,
"align": "stretch"
},
{
"id": "background-title",
"component": "Text",
"text": "补充信息",
"maxLine": 30,
"bold": true,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "background",
"component": "Column",
"children": {
"componentId": "background-item",
"path": "/report/background"
},
"gap": 8,
"align": "stretch"
},
{
"id": "risk-card-content",
"component": "Column",
"children": [
"risk-title",
"risk-callout",
"risk-details-group"
],
"gap": 8,
"align": "stretch"
},
{
"id": "risk-card",
"component": "Card",
"child": "risk-card-content",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "common_fg_z1_color",
"borderColorToken": "common_line_light_color"
},
{
"id": "transactions-section-icon",
"component": "Text",
"text": "🤝",
"variant": "body",
"bold": false,
"maxLine": 1,
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "transactions-section-label",
"component": "Text",
"text": "历史成交",
"maxLine": 30,
"bold": false,
"weight": 1,
"colorToken": "common_level1_base_color",
"sizeToken": "common_h4_text_style__font_size"
},
{
"id": "transactions-section",
"component": "Row",
"children": [
"transactions-section-icon",
"transactions-section-label"
],
"gap": 4,
"align": "center"
},
{
"id": "transactions-title",
"component": "Text",
"text": {
"path": "/report/transactionsTitle"
},
"maxLine": 30,
"colorToken": "common_level1_base_color",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "transactions",
"component": "Table",
"data": {
"path": "/report/table"
}
},
{
"id": "transactions-card-content",
"component": "Column",
"children": [
"transactions-section",
"transactions-title",
"transactions"
],
"gap": 8,
"align": "stretch"
},
{
"id": "transactions-card",
"component": "Card",
"child": "transactions-card-content",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"backgroundColorToken": "common_fg_z1_color",
"borderColorToken": "common_line_light_color"
},
{
"id": "attachment-file-icon",
"component": "Image",
"url": "https://g.alicdn.com/dingding/card_a2ui/0.3.1/img/ding-file-icons/markdown_light.BlEB_2F.png",
"darkUrl": "https://g.alicdn.com/dingding/card_a2ui/0.3.1/img/ding-file-icons/markdown_dark.JXjoMuz.png",
"variant": "avatar",
"fit": "contain",
"cornerRadius": 0,
"previewEnabled": false
},
{
"id": "attachment-name",
"component": "Text",
"text": {
"path": "/attachment/fileName"
},
"maxLine": 1,
"bold": true,
"colorToken": "common_level1_base_color",
"variant": "body",
"sizeToken": "common_action_text_style__font_size"
},
{
"id": "attachment-description",
"component": "Text",
"text": {
"path": "/attachment/description"
},
"maxLine": 1,
"variant": "caption",
"colorToken": "common_level2_base_color",
"sizeToken": "common_footnote_text_style__font_size"
},
{
"id": "attachment-details",
"component": "Column",
"children": [
"attachment-name",
"attachment-description"
],
"gap": 2,
"align": "stretch",
"weight": 1
},
{
"id": "attachment-preview-icon",
"component": "Text",
"text": "",
"icon": {
"name": "Visibility_L_outlined"
},
"colorToken": "common_level2_base_color"
},
{
"id": "attachment-preview",
"component": "Row",
"children": [
"attachment-preview-icon"
],
"gap": 0,
"align": "center",
"action": {
"event": {
"name": "company_investigation_preview_attachment",
"context": {
"fileName": {
"path": "/attachment/fileName"
},
"url": {
"path": "/attachment/url"
}
}
}
}
},
{
"id": "attachment-download-icon",
"component": "Icon",
"name": "DownloadAndSave_L_outlined",
"colorToken": "common_level2_base_color"
},
{
"id": "attachment-download",
"component": "Row",
"children": [
"attachment-download-icon"
],
"gap": 0,
"align": "center",
"action": {
"event": {
"name": "company_investigation_download_attachment",
"context": {
"fileName": {
"path": "/attachment/fileName"
},
"url": {
"path": "/attachment/url"
}
}
}
}
},
{
"id": "attachment",
"component": "Row",
"children": [
"attachment-file-icon",
"attachment-details",
"attachment-preview",
"attachment-download"
],
"gap": 12,
"align": "center"
},
{
"id": "attachment-card",
"component": "Card",
"child": "attachment",
"padding": 12,
"cornerRadius": 10,
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"backgroundColor": "#00000000"
},
{
"id": "title",
"component": "Text",
"text": {
"path": "/company/name"
},
"maxLine": 30,
"sizeToken": "common_h2_text_style__font_size",
"bold": true,
"colorToken": "common_level1_base_color"
},
{
"id": "source",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "> 📊${/report/source}"
}
}
},
{
"id": "frame",
"component": "Column",
"children": [
"body"
],
"gap": 0,
"align": "stretch",
"padding": 0,
"cornerRadius": 12
},
{
"id": "root",
"component": "Column",
"children": [
"frame"
],
"gap": 0,
"align": "stretch",
"padding": 0
},
{
"id": "findings-section",
"component": "Column",
"children": [
"findings-title",
"findings"
],
"gap": 8,
"align": "stretch"
},
{
"id": "background-section",
"component": "Column",
"children": [
"background-title",
"background"
],
"gap": 8,
"align": "stretch"
},
{
"id": "risk-details-group",
"component": "Column",
"children": [
"findings-section",
"background-section"
],
"gap": 16,
"align": "stretch"
},
{
"id": "body",
"component": "Column",
"children": [
"title",
"summary-card",
"company-card",
"risk-card",
"transactions-card",
"attachment-card",
"source"
],
"gap": 10,
"align": "stretch",
"padding": 12
},
{
"id": "findings-bullet",
"component": "Text",
"text": "•",
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_link_color",
"maxLine": 1
},
{
"id": "findings-text",
"component": "Text",
"text": {
"path": "text"
},
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_level1_base_color",
"maxLine": 30,
"weight": 1
},
{
"id": "findings-item",
"component": "Row",
"children": [
"findings-bullet",
"findings-text"
],
"gap": 12,
"align": "start"
},
{
"id": "background-bullet",
"component": "Text",
"text": "•",
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_link_color",
"maxLine": 1
},
{
"id": "background-text",
"component": "Text",
"text": {
"path": "text"
},
"sizeToken": "common_action_text_style__font_size",
"colorToken": "common_level1_base_color",
"maxLine": 30,
"weight": 1
},
{
"id": "background-item",
"component": "Row",
"children": [
"background-bullet",
"background-text"
],
"gap": 12,
"align": "start"
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-company-investigation-card-a2ui",
"path": "/",
"value": {
"company": {
"name": "示例企业甲",
"registration": "DEMO-COMPANY-001",
"representative": "用户甲",
"founded": "2020 年 1 月 1 日",
"state": "正常经营",
"capital": "500 万元",
"address": "示例地址(虚构,不用于联系)",
"industry": "企业软件服务",
"employees": "80 人"
},
"report": {
"risk": "低风险",
"summary": "以下为虚构企业的演示结论:存在待核实事项,**请完成核验后再决定是否合作。**",
"riskDetail": "演示风险:存在待核实的主体信息和合同材料。",
"findings": [
{
"text": "示例检查项一:合同材料待补齐。"
},
{
"text": "示例检查项二:主体信息待复核。"
},
{
"text": "所有内容均为合成数据,不代表真实企业。"
}
],
"background": [
{
"text": "示例企业已提交基础材料。"
},
{
"text": "后续需要补充核验结果。"
}
],
"transactionsTitle": "演示成交累计3单,共¥15,000元。",
"table": {
"meta": [
{
"colKey": "month",
"colName": "月份",
"type": "text"
},
{
"colKey": "product",
"colName": "产品",
"type": "text"
},
{
"colKey": "amount",
"colName": "金额",
"type": "text"
}
],
"data": [
{
"month": "1月",
"product": "示例产品",
"amount": "4,000"
},
{
"month": "3月",
"product": "示例产品",
"amount": "5,000"
},
{
"month": "5月",
"product": "示例产品",
"amount": "6,000"
}
]
},
"source": "数据来源:合成演示数据,非真实调查结果"
},
"attachment": {
"fileName": "历史报价文件.md",
"description": "Markdown文件",
"url": ""
}
}
}
}
]
@@ -0,0 +1,347 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-expense-approval-list-a2ui",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "intro",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "<a atId=\"${/recipient/id}\">${/recipient/name}</a> ${/summary}"
}
}
},
{
"id": "pending-title",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "${applicant/name} ${expense/type}报销${expense/amount}元"
}
},
"maxLine": 20,
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"bold": true,
"weight": 1
},
{
"id": "pending-recommendation",
"component": "Text",
"text": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "approve"
}
},
"then": "建议批准",
"else": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": "",
"else": "待核实"
}
}
}
},
"sizeToken": "common_footnote_text_style__font_size",
"colorToken": "common_link_color",
"visible": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": false,
"else": {
"path": "showRecommendation"
}
}
},
"maxLine": 1,
"bold": true
},
{
"id": "applicant-value",
"component": "Row",
"children": [
"pending-title",
"pending-recommendation"
],
"gap": 8,
"align": "center"
},
{
"id": "pending-reason",
"component": "Text",
"text": {
"path": "ai/reason"
},
"sizeToken": "common_body_text_style__font_size",
"colorToken": "common_level2_base_color",
"maxLine": 20
},
{
"id": "pending-divider",
"component": "Row",
"children": [
"pending-divider-line"
],
"gap": 0,
"align": "center",
"visible": {
"path": "showDivider"
}
},
{
"id": "pending-reject-label",
"component": "Text",
"text": "拒绝",
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-reject",
"component": "Row",
"weight": 1,
"children": [
"pending-reject-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-approve-label",
"component": "Text",
"text": "批准",
"colorToken": "common_link_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-approve",
"component": "Row",
"weight": 1,
"children": [
"pending-approve-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-actions",
"component": "Row",
"children": [
"pending-reject",
"pending-approve"
],
"gap": 10,
"align": "center",
"visible": {
"call": "eq",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "pending-item",
"component": "Column",
"children": [
"pending-divider",
"applicant-value",
"pending-reason",
"pending-actions",
"pending-result-row"
],
"gap": 16,
"align": "stretch"
},
{
"id": "pending-list",
"component": "Column",
"children": {
"componentId": "pending-item",
"path": "/approvals/items"
},
"gap": 16,
"align": "stretch"
},
{
"id": "pending-panel",
"component": "Column",
"padding": 12,
"cornerRadius": 16,
"borderWidth": 0.5,
"borderColorToken": "common_line_light_color",
"backgroundColorToken": "common_fg_z1_color",
"children": [
"pending-list"
],
"gap": 0,
"align": "stretch"
},
{
"id": "root",
"component": "Column",
"children": [
"intro",
"pending-panel"
],
"gap": 10,
"align": "stretch"
},
{
"id": "pending-status",
"component": "Text",
"text": {
"path": "resultLabel"
},
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-result",
"component": "Row",
"weight": 1,
"children": [
"pending-status"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-result-row",
"component": "Row",
"children": [
"pending-result"
],
"gap": 0,
"align": "center",
"visible": {
"call": "ne",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "pending-divider-line",
"component": "Divider",
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"axis": "horizontal",
"borderColor": "#1F111F2C",
"weight": 1
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-expense-approval-list-a2ui",
"path": "/",
"value": {
"identity": {
"requestId": "demo-approval-briefing"
},
"recipient": {
"id": "demo-approval-recipient",
"name": "林小夏"
},
"summary": "今天有2条待审批需要你确认。",
"approvals": {
"items": [
{
"id": "demo-expense-001",
"revision": 1,
"showRecommendation": true,
"showDivider": false,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-1",
"name": "顾宁"
},
"expense": {
"type": "出差",
"amount": 1000,
"currency": "CNY"
},
"ai": {
"decision": "approve",
"reason": "报销金额、出差行程和票据匹配,费用标准符合公司规定"
}
},
{
"id": "demo-expense-002",
"revision": 1,
"showRecommendation": true,
"showDivider": true,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-2",
"name": "周言"
},
"expense": {
"type": "餐饮",
"amount": 2400,
"currency": "CNY"
},
"ai": {
"decision": "reject",
"reason": "餐饮费用超出公司标准,且部分支出缺少有效票据,建议退回补充说明"
}
}
]
}
}
}
}
]
@@ -0,0 +1,572 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-expense-approval-card-a2ui",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "intro",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "<a atId=\"${/recipient/id}\">${/recipient/name}</a> ${/summary}"
}
}
},
{
"id": "pending-title",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "${applicant/name} ${expense/type}报销${expense/amount}元"
}
},
"maxLine": 20,
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"bold": true,
"weight": 1
},
{
"id": "pending-recommendation",
"component": "Text",
"text": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "approve"
}
},
"then": "建议批准",
"else": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": "",
"else": "待核实"
}
}
}
},
"sizeToken": "common_footnote_text_style__font_size",
"colorToken": "common_link_color",
"visible": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": false,
"else": {
"path": "showRecommendation"
}
}
},
"maxLine": 1,
"bold": true
},
{
"id": "applicant-value",
"component": "Row",
"children": [
"pending-title",
"pending-recommendation"
],
"gap": 8,
"align": "center"
},
{
"id": "pending-reason",
"component": "Text",
"text": {
"path": "ai/reason"
},
"sizeToken": "common_body_text_style__font_size",
"colorToken": "common_level2_base_color",
"maxLine": 20
},
{
"id": "pending-divider",
"component": "Row",
"children": [
"pending-divider-line"
],
"gap": 0,
"align": "center",
"visible": {
"path": "showDivider"
}
},
{
"id": "pending-reject-label",
"component": "Text",
"text": "拒绝",
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-reject",
"component": "Row",
"weight": 1,
"children": [
"pending-reject-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-approve-label",
"component": "Text",
"text": "批准",
"colorToken": "common_link_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-approve",
"component": "Row",
"weight": 1,
"children": [
"pending-approve-label"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-actions",
"component": "Row",
"children": [
"pending-reject",
"pending-approve"
],
"gap": 10,
"align": "center",
"visible": {
"call": "eq",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "pending-item",
"component": "Column",
"children": [
"pending-divider",
"applicant-value",
"pending-reason",
"pending-actions",
"pending-result-row"
],
"gap": 16,
"align": "stretch"
},
{
"id": "pending-list",
"component": "Column",
"children": {
"componentId": "pending-item",
"path": "/approvals/pending"
},
"gap": 16,
"align": "stretch"
},
{
"id": "pending-panel",
"component": "Column",
"padding": 12,
"cornerRadius": 16,
"borderWidth": 0.5,
"borderColorToken": "common_line_light_color",
"backgroundColorToken": "common_fg_z1_color",
"children": [
"pending-list"
],
"gap": 0,
"align": "stretch"
},
{
"id": "handled-title",
"component": "Text",
"text": {
"call": "formatString",
"args": {
"value": "${applicant/name} ${expense/type}报销${expense/amount}元"
}
},
"maxLine": 20,
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"bold": true,
"weight": 1
},
{
"id": "handled-recommendation",
"component": "Text",
"text": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "approve"
}
},
"then": "建议批准",
"else": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": "",
"else": "待核实"
}
}
}
},
"sizeToken": "common_footnote_text_style__font_size",
"colorToken": "common_link_color",
"visible": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"path": "ai/decision"
},
"b": "reject"
}
},
"then": false,
"else": {
"path": "showRecommendation"
}
}
},
"maxLine": 1,
"bold": true
},
{
"id": "handled-header",
"component": "Row",
"children": [
"handled-title",
"handled-recommendation"
],
"gap": 8,
"align": "center"
},
{
"id": "handled-reason",
"component": "Text",
"text": {
"path": "ai/reason"
},
"sizeToken": "common_body_text_style__font_size",
"colorToken": "common_level2_base_color",
"maxLine": 20
},
{
"id": "handled-divider",
"component": "Row",
"children": [
"handled-divider-line"
],
"gap": 0,
"align": "center",
"visible": {
"path": "showDivider"
}
},
{
"id": "handled-status",
"component": "Text",
"text": {
"path": "resultLabel"
},
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size"
},
{
"id": "handled-item",
"component": "Column",
"children": [
"handled-divider",
"handled-header",
"handled-reason",
"handled-result-row"
],
"gap": 16,
"align": "stretch"
},
{
"id": "handled-list",
"component": "Column",
"children": {
"componentId": "handled-item",
"path": "/approvals/handled"
},
"gap": 16,
"align": "stretch"
},
{
"id": "handled-panel",
"component": "Column",
"padding": 12,
"cornerRadius": 16,
"borderWidth": 0.5,
"borderColorToken": "common_line_light_color",
"backgroundColorToken": "common_fg_z1_color",
"children": [
"handled-list"
],
"gap": 0,
"align": "stretch"
},
{
"id": "tabs",
"component": "Tabs",
"tabs": [
{
"title": {
"path": "/view/pendingLabel"
},
"child": "pending-panel"
},
{
"title": {
"path": "/view/handledLabel"
},
"child": "handled-panel"
}
]
},
{
"id": "root",
"component": "Column",
"children": [
"intro",
"tabs"
],
"gap": 10,
"align": "stretch"
},
{
"id": "pending-status",
"component": "Text",
"text": {
"path": "resultLabel"
},
"colorToken": "common_level1_base_color",
"sizeToken": "common_body_text_style__font_size",
"maxLine": 1
},
{
"id": "pending-result",
"component": "Row",
"weight": 1,
"children": [
"pending-status"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "pending-result-row",
"component": "Row",
"children": [
"pending-result"
],
"gap": 0,
"align": "center",
"visible": {
"call": "ne",
"args": {
"a": {
"path": "status"
},
"b": "pending"
}
}
},
{
"id": "handled-result",
"component": "Row",
"weight": 1,
"children": [
"handled-status"
],
"justify": "center",
"align": "center",
"padding": 8,
"cornerRadius": 24,
"backgroundColorToken": "common_bg_color"
},
{
"id": "handled-result-row",
"component": "Row",
"children": [
"handled-result"
],
"gap": 0,
"align": "center"
},
{
"id": "pending-divider-line",
"component": "Divider",
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"axis": "horizontal",
"borderColor": "#1F111F2C",
"weight": 1
},
{
"id": "handled-divider-line",
"component": "Divider",
"borderWidth": 1,
"borderColorToken": "common_line_light_color",
"axis": "horizontal",
"borderColor": "#1F111F2C",
"weight": 1
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-expense-approval-card-a2ui",
"path": "/",
"value": {
"identity": {
"requestId": "demo-approval-briefing"
},
"recipient": {
"id": "demo-approval-recipient",
"name": "林小夏"
},
"summary": "今天有3条审批需要跟你汇报,其中1条已按你的授权自动处理。",
"view": {
"pendingLabel": "待处理·2",
"handledLabel": "已为您处理·1"
},
"approvals": {
"pending": [
{
"id": "demo-expense-001",
"revision": 1,
"showRecommendation": true,
"showDivider": false,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-1",
"name": "顾宁"
},
"expense": {
"type": "出差",
"amount": 1000,
"currency": "CNY"
},
"ai": {
"decision": "approve",
"reason": "报销金额、出差行程和票据匹配,费用标准符合公司规定"
}
},
{
"id": "demo-expense-002",
"revision": 1,
"showRecommendation": true,
"showDivider": true,
"status": "pending",
"resultLabel": "",
"applicant": {
"id": "demo-applicant-2",
"name": "周言"
},
"expense": {
"type": "餐饮",
"amount": 2400,
"currency": "CNY"
},
"ai": {
"decision": "reject",
"reason": "餐饮费用超出公司标准,且部分支出缺少有效票据,建议退回补充说明"
}
}
],
"handled": [
{
"id": "demo-expense-003",
"revision": 2,
"showRecommendation": false,
"showDivider": false,
"resultLabel": "已批准",
"status": "approved",
"applicant": {
"id": "demo-applicant-3",
"name": "陈安"
},
"expense": {
"type": "交通",
"amount": 120,
"currency": "CNY"
},
"ai": {
"decision": "approve",
"reason": "金额在授权范围内,票据及费用标准符合公司规定"
}
}
]
}
}
}
}
]
@@ -0,0 +1,508 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-interview-feedback",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "review-intro",
"component": "Markdown",
"content": {
"path": "/interview/reviewMarkdown"
}
},
{
"id": "strengths-label",
"component": "Text",
"text": "优点",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level2_base_color",
"bold": true
},
{
"id": "strengths",
"component": "TextField",
"label": "",
"value": {
"path": "/form/strengths"
},
"variant": "longText",
"disabled": false
},
{
"id": "strengths-group",
"component": "Column",
"children": [
"strengths-label",
"strengths"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "pending-label",
"component": "Text",
"text": "不足与待改进",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level2_base_color",
"bold": true
},
{
"id": "pending",
"component": "TextField",
"label": "",
"value": {
"path": "/form/pending"
},
"variant": "longText",
"disabled": false
},
{
"id": "pending-group",
"component": "Column",
"children": [
"pending-label",
"pending"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "conclusion-label",
"component": "Text",
"text": "候选人的面试结果",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level2_base_color",
"bold": true
},
{
"id": "conclusion",
"component": "ChoicePicker",
"variant": "mutuallyExclusive",
"displayStyle": "checkbox",
"value": {
"path": "/form/conclusion"
},
"options": [
{
"label": "通过",
"value": "pass"
},
{
"label": "不通过",
"value": "reject"
}
],
"disabled": false
},
{
"id": "conclusion-group",
"component": "Column",
"children": [
"conclusion-label",
"conclusion"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "review-confirm-label",
"component": "Text",
"text": "确认并填入至系统",
"maxLine": 30,
"colorToken": "common_link_color",
"bold": true
},
{
"id": "review-confirm",
"component": "Button",
"child": "review-confirm-label",
"variant": "primary",
"disabled": false,
"action": {
"event": {
"name": "interview_review_confirm",
"context": {
"identity": {
"path": "/identity"
},
"form": {
"path": "/form"
},
"step": {
"path": "/view/step"
}
}
}
},
"checks": [
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/strengths"
}
}
},
"message": "请补充候选人优点"
},
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/pending"
}
}
},
"message": "请补充待验证项"
},
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/conclusion"
}
}
},
"message": "请选择面试结论"
}
],
"weight": 1
},
{
"id": "review-hint",
"component": "Text",
"text": "确认后将自动回填至面试系统",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level3_base_color"
},
{
"id": "review-footer",
"component": "Column",
"children": [
"review-footer-inset",
"review-confirm-row",
"review-hint"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "review",
"component": "Column",
"children": [
"review-intro",
"review-fields",
"review-footer"
],
"gap": 16,
"align": "stretch",
"padding": 0,
"visible": {
"call": "eq",
"args": {
"a": {
"path": "/view/step"
},
"b": 1
}
}
},
{
"id": "recommendation-intro",
"component": "Markdown",
"content": {
"path": "/interview/recommendationMarkdown"
}
},
{
"id": "level-label",
"component": "Text",
"text": "职级(可更正)",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level2_base_color",
"bold": true
},
{
"id": "level",
"component": "TextField",
"label": "",
"value": {
"path": "/form/level"
},
"variant": "shortText",
"placeholder": "请输入职级",
"disabled": false
},
{
"id": "level-group",
"component": "Column",
"children": [
"level-label",
"level"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "comment-label",
"component": "Text",
"text": "评论(选填)",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level2_base_color",
"bold": true
},
{
"id": "comment",
"component": "TextField",
"label": "",
"value": {
"path": "/form/comment"
},
"variant": "shortText",
"placeholder": "补充评论",
"disabled": false
},
{
"id": "comment-group",
"component": "Column",
"children": [
"comment-label",
"comment"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "submit-label",
"component": "Text",
"text": "确认并提交至系统",
"maxLine": 30,
"colorToken": "common_link_color",
"bold": true
},
{
"id": "submit",
"component": "Button",
"child": "submit-label",
"variant": "primary",
"disabled": false,
"action": {
"event": {
"name": "interview_feedback_submit",
"context": {
"identity": {
"path": "/identity"
},
"form": {
"path": "/form"
},
"step": {
"path": "/view/step"
}
}
}
},
"checks": [
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/level"
}
}
},
"message": "请输入职级"
}
],
"weight": 1
},
{
"id": "submit-hint",
"component": "Text",
"text": "提交后将自动回填至面试系统",
"maxLine": 30,
"variant": "caption",
"colorToken": "common_level3_base_color"
},
{
"id": "submit-footer",
"component": "Column",
"children": [
"submit-footer-inset",
"submit-row",
"submit-hint"
],
"gap": 8,
"align": "stretch",
"padding": 0
},
{
"id": "recommendation",
"component": "Column",
"children": [
"recommendation-intro",
"level-group",
"comment-group",
"submit-footer"
],
"gap": 12,
"align": "stretch",
"padding": 0,
"visible": {
"call": "eq",
"args": {
"a": {
"path": "/view/step"
},
"b": 2
}
}
},
{
"id": "review-confirm-row",
"component": "Row",
"children": [
"review-confirm"
],
"gap": 0,
"align": "center",
"padding": 0
},
{
"id": "submit-row",
"component": "Row",
"children": [
"submit"
],
"gap": 0,
"align": "center",
"padding": 0
},
{
"id": "root",
"component": "Column",
"children": [
"review",
"recommendation",
"invalid-step"
],
"gap": 0,
"align": "stretch"
},
{
"id": "review-fields",
"component": "Column",
"children": [
"strengths-group",
"pending-group",
"conclusion-group"
],
"gap": 20,
"align": "stretch",
"padding": 0
},
{
"id": "review-footer-inset",
"component": "Column",
"children": [],
"padding": 0,
"gap": 0
},
{
"id": "submit-footer-inset",
"component": "Column",
"children": [],
"padding": 0,
"gap": 0
},
{
"id": "invalid-step",
"component": "Text",
"text": "表单状态异常,请重新打开或联系发起人。",
"maxLine": 30,
"colorToken": "common_level2_base_color",
"visible": {
"call": "not",
"args": {
"value": {
"call": "or",
"args": {
"values": [
{
"call": "eq",
"args": {
"a": {
"path": "/view/step"
},
"b": 1
}
},
{
"call": "eq",
"args": {
"a": {
"path": "/view/step"
},
"b": 2
}
}
]
}
}
}
}
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-interview-feedback",
"path": "/",
"value": {
"identity": {
"interviewId": "demo-interview-001",
"requestId": "demo-interview-feedback"
},
"interview": {
"candidateName": "张三",
"recommendedLevel": "P7+",
"reviewMarkdown": "刚才这场跟 **张三** 的面试,听记我已读完:",
"recommendationMarkdown": "好,建议他的评级为 **P7+**,职级可更正,也可以补充评论:"
},
"view": {
"step": 1
},
"form": {
"strengths": "复杂业务经验扎实:熟悉制造、零售等行业排班场景,具备企业工作流程理解与产品落地能力。\n\n具备双平台视角:有两类协同办公产品工作背景,能具体分析 IM、文档协作与 AI 整合的体验差异。\n\n具备 AI 应用认知:有 Agent 辅助配置、Skill 简化排班等案例,实践深度待验证。",
"pending": "IM 经验待验证:经历集中于假勤、人事领域,大规模 IM 产品迭代与基础能力经验尚需验证。\n\n岗位预期需对齐:期望承担“业务+产品”角色,需确认对 IM 基础能力与 AI 落地职责的认可及长期投入意愿。",
"conclusion": [
"pass"
],
"level": "P7+",
"comment": ""
}
}
}
}
]
@@ -0,0 +1,518 @@
[
{
"version": "v1.0",
"createSurface": {
"surfaceId": "demo-meeting-invitation",
"catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json",
"dataModel": {},
"components": [
{
"id": "intro",
"component": "Markdown",
"content": {
"call": "formatString",
"args": {
"value": "<a atId=\"${/meeting/recipient/openDingTalkId}\">${/meeting/recipient/displayName}</a> ${/meeting/introduction}"
}
}
},
{
"id": "topic",
"component": "ChoicePicker",
"variant": "mutuallyExclusive",
"displayStyle": "checkbox",
"value": {
"path": "/form/topic"
},
"options": [
{
"label": {
"path": "/meeting/topics/0/label"
},
"value": "pilot"
},
{
"label": {
"path": "/meeting/topics/1/label"
},
"value": "scope"
},
{
"label": "自定义主题",
"value": "custom"
}
]
},
{
"id": "custom-topic",
"component": "TextField",
"label": "",
"placeholder": "请输入会议主题",
"variant": "shortText",
"value": {
"path": "/form/customTopic"
},
"visible": {
"call": "eq",
"args": {
"a": {
"call": "arrayGet",
"args": {
"value": {
"path": "/form/topic"
},
"index": 0
}
},
"b": "custom"
}
}
},
{
"id": "step-1",
"component": "Text",
"text": "1.",
"maxLine": 8,
"colorToken": "common_link_color"
},
{
"id": "title-1",
"component": "Text",
"text": "会议主题是什么?",
"maxLine": 8,
"bold": true,
"colorToken": "common_level1_base_color",
"weight": 1
},
{
"id": "heading-1",
"component": "Row",
"children": [
"step-1",
"title-1"
],
"gap": 8,
"align": "start",
"padding": 0
},
{
"id": "section-1",
"component": "Column",
"children": [
"heading-1",
"topic",
"custom-topic"
],
"gap": 12,
"align": "stretch",
"padding": 0
},
{
"id": "attendee-summary",
"component": "Text",
"text": {
"path": "/meeting/attendeeSummary"
},
"maxLine": 8,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
},
{
"id": "attendee-missing",
"component": "Text",
"text": {
"path": "/meeting/missingAttendeeHint"
},
"maxLine": 8,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
},
{
"id": "attendee-hints",
"component": "Column",
"children": [
"attendee-summary",
"attendee-missing"
],
"gap": 2,
"align": "stretch",
"padding": 0
},
{
"id": "attendees",
"component": "UserPicker",
"title": "请选择参会人",
"placeholder": "请选择参会人",
"maxSelections": 10,
"value": {
"path": "/form/attendees"
}
},
{
"id": "step-2",
"component": "Text",
"text": "2.",
"maxLine": 8,
"colorToken": "common_link_color"
},
{
"id": "title-2",
"component": "Text",
"text": "需要邀请哪些人参加?",
"maxLine": 8,
"bold": true,
"colorToken": "common_level1_base_color",
"weight": 1
},
{
"id": "heading-2",
"component": "Row",
"children": [
"step-2",
"title-2"
],
"gap": 8,
"align": "start",
"padding": 0
},
{
"id": "section-2",
"component": "Column",
"children": [
"heading-2",
"attendee-hints",
"attendees"
],
"gap": 12,
"align": "stretch",
"padding": 0
},
{
"id": "meeting-modes",
"component": "ChoicePicker",
"variant": "mutuallyExclusive",
"displayStyle": "checkbox",
"value": {
"path": "/form/meetingModes"
},
"options": [
{
"label": "在线会议,自动生成入会链接",
"value": "online"
},
{
"label": "线下会议,补充会议地点",
"value": "offline"
}
]
},
{
"id": "location-label",
"component": "Text",
"text": "请补充会议地点",
"maxLine": 8,
"colorToken": "common_level2_base_color",
"visible": {
"call": "eq",
"args": {
"a": {
"call": "arrayGet",
"args": {
"value": {
"path": "/form/meetingModes"
},
"index": 0
}
},
"b": "offline"
}
}
},
{
"id": "location",
"component": "TextField",
"label": "",
"placeholder": "请输入",
"variant": "shortText",
"value": {
"path": "/form/location"
},
"visible": {
"call": "eq",
"args": {
"a": {
"call": "arrayGet",
"args": {
"value": {
"path": "/form/meetingModes"
},
"index": 0
}
},
"b": "offline"
}
}
},
{
"id": "step-3",
"component": "Text",
"text": "3.",
"maxLine": 8,
"colorToken": "common_link_color"
},
{
"id": "title-3",
"component": "Text",
"text": "会议在哪里进行?",
"maxLine": 8,
"bold": true,
"colorToken": "common_level1_base_color",
"weight": 1
},
{
"id": "heading-3",
"component": "Row",
"children": [
"step-3",
"title-3"
],
"gap": 8,
"align": "start",
"padding": 0
},
{
"id": "section-3",
"component": "Column",
"children": [
"heading-3",
"meeting-modes",
"location-label",
"location"
],
"gap": 12,
"align": "stretch",
"padding": 0
},
{
"id": "agenda-hint",
"component": "Text",
"text": "已根据聊天内容整理,可直接修改:",
"maxLine": 8,
"colorToken": "common_level2_base_color",
"sizeToken": "common_action_text_style__font_size",
"bold": false
},
{
"id": "agenda",
"component": "TextField",
"label": "",
"variant": "longText",
"value": {
"path": "/form/agenda"
}
},
{
"id": "step-4",
"component": "Text",
"text": "4.",
"maxLine": 8,
"colorToken": "common_link_color"
},
{
"id": "title-4",
"component": "Text",
"text": "会议主要讨论什么?",
"maxLine": 8,
"bold": true,
"colorToken": "common_level1_base_color",
"weight": 1
},
{
"id": "heading-4",
"component": "Row",
"children": [
"step-4",
"title-4"
],
"gap": 8,
"align": "start",
"padding": 0
},
{
"id": "section-4",
"component": "Column",
"children": [
"heading-4",
"agenda-hint",
"agenda"
],
"gap": 12,
"align": "stretch",
"padding": 0
},
{
"id": "submit-label",
"component": "Text",
"text": "生成会邀",
"maxLine": 8,
"bold": true,
"colorToken": "common_link_color",
"sizeToken": "common_body_text_style__font_size"
},
{
"id": "generate",
"component": "Button",
"child": "submit-label",
"variant": "primary",
"action": {
"event": {
"name": "meeting_invitation_submit",
"context": {
"requestId": {
"path": "/meeting/requestId"
},
"form": {
"path": "/form"
}
}
}
},
"checks": [
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/topic"
}
}
},
"message": "请选择会议主题"
},
{
"condition": {
"call": "required",
"args": {
"value": {
"path": "/form/attendees"
}
}
},
"message": "请选择参会人"
},
{
"condition": {
"call": "cond",
"args": {
"if": {
"call": "eq",
"args": {
"a": {
"call": "arrayGet",
"args": {
"value": {
"path": "/form/topic"
},
"index": 0
}
},
"b": "custom"
}
},
"then": {
"call": "required",
"args": {
"value": {
"path": "/form/customTopic"
}
}
},
"else": true
}
},
"message": "请输入自定义主题"
}
]
},
{
"id": "footer",
"component": "Row",
"children": [
"generate"
],
"gap": 0,
"align": "center",
"justify": "start",
"padding": 0
},
{
"id": "form",
"component": "Column",
"children": [
"section-1",
"section-2",
"section-3",
"section-4",
"footer"
],
"gap": 16,
"align": "stretch",
"padding": 0
},
{
"id": "root",
"component": "Column",
"children": [
"intro",
"form"
],
"gap": 18,
"align": "stretch"
}
]
}
},
{
"version": "v1.0",
"updateDataModel": {
"surfaceId": "demo-meeting-invitation",
"path": "/",
"value": {
"meeting": {
"requestId": "demo-meeting-invitation",
"recipient": {
"openDingTalkId": "demo-attendee-1",
"displayName": "林小夏"
},
"introduction": "我来帮你约一下和客户沟通试点方案的会议。主题已帮你预填好,请补充参会人并确认会议安排:",
"topics": [
{
"id": "pilot",
"label": "示例科技 · 协同办公试点方案沟通"
},
{
"id": "scope",
"label": "示例科技 · 试点需求与实施范围确认"
}
],
"attendeeSummary": "尚未选择参会人",
"missingAttendeeHint": "请根据会议需要添加参会人"
},
"form": {
"topic": [
"pilot"
],
"customTopic": "",
"attendees": [],
"meetingModes": [
"online"
],
"location": "",
"agenda": "1. 确认首批试点的部门、人数和使用场景\n2. 对齐通讯录同步、权限管理的接入要求\n3. 讨论试点排期,明确双方负责人和下一步安排"
}
}
}
}
]
@@ -15,6 +15,19 @@ metadata:
默认目标是完成用户明确提出的 AI 表格业务操作,包括查询、创建、更新、同步和其他已授权写入。契约评审是辅助场景,仅在用户明确要求核对合成 JSON、dry-run、参数合法性、Help/Schema 或恢复方案时启用;这类请求默认只读,不把评审要求扩展成线上业务操作。用户同时提出业务操作和契约核对时,先按真实业务目标执行必要的契约校验,再继续执行已明确授权的业务操作。 默认目标是完成用户明确提出的 AI 表格业务操作,包括查询、创建、更新、同步和其他已授权写入。契约评审是辅助场景,仅在用户明确要求核对合成 JSON、dry-run、参数合法性、Help/Schema 或恢复方案时启用;这类请求默认只读,不把评审要求扩展成线上业务操作。用户同时提出业务操作和契约核对时,先按真实业务目标执行必要的契约校验,再继续执行已明确授权的业务操作。
## 过程展示建议
创建与修改采用相同的展示建议:结合核验结果按需展示阶段进展,可使用 Markdown 标题与链接(如 `[项目台账](完整URL)`)、卡片或当前宿主支持的其他格式;在满足宿主明确交付要求的前提下,遵循用户指定的展示方式。最终汇总保留当前有效的具名链接及名称转义,不拆成名称与裸 URL;用户或宿主指定格式除外。
- 展示链接时,名称与对象对应,复用当前有效回执中对象自身的完整 `url` 或身份匹配的 `relation=target` 链接,保留原编码;不得截断、缩写或用 `...` / `…` 替代 URL 任一部分。不用父级链接冒充目标对象,不拼接地址或为补链接重放写入。独立读回优先,显式 null/空值不由旧回执覆盖。
- 如实描述结果,保留 pending/unknown/partial/STOP;链接本身不证明成功、核验完成或流程启用。缺链接时可简短说明。
- 字段 target 须满足 `objectType=field` 且 `objectId=当前 fieldId`;不额外展示错配链接。字段自身无可用链接时直说缺失,核验状态仍按有效回执保留。
- **回执转述边界:**明确的合成演练或回执转述,以用户原意为准;准备性 Schema 查询不将其转为契约评审。核验状态按给定回执的字段语义转述,不以本 Agent 是否亲自执行或独立读回改写该字段;纯 JSON 也保持指定字段语义。转述不等于真实实跑或独立核验;真实写入仍须按结果契约读回,缺失或 pending/unknown 的状态不得擅自提升为 verified。
业务核验与宿主交付分别判断。对当前宿主明确要求交付或登记的最终产物,按其适用范围和规则执行,普通链接不替代规定的登记。展示或登记失败、工具不可用或状态未确认时,应简短分别说明当前已知的业务核验、登记与展示状态,不因此重放业务写入;无需重复对象或链接清单;用户明确要求字段完整汇总时除外,应按宿主及用户指定格式保留各项已有完整链接与原核验状态。展示请求已提交不等于登记或渲染成功。
收尾时检查所有用户最终目标,某个目标进入恢复流程,不遗漏其他满足交付条件的目标;不额外登记未被用户指定为最终交付对象的内部 Table、字段或中间产物。已确认完成的登记不重复提交;结果未知的登记请求不自动重发,保留未确认状态并说明限制,仅在宿主提供明确查询或安全恢复契约时按其处理。用户要求预发交付时,指定预发前缀不证明环境映射成立;缺少可信完整预发 URL 时,保留业务结果并说明预发交付未完成,不自行拼接、换域登记或擅自改交生产链接。
## 先区分操作、用法与契约评审 ## 先区分操作、用法与契约评审
用户只要求评审返回值或恢复方案时,不执行业务操作,也不按业务任务扩展到上游写入命令。发现目标以用户原始消息为准,Agent 自行摘要或传入 Skill 的参数不是新增请求,不能将“核对该对账命令”改写成“比较对账与原创建命令”。保留用户正在核对的完整原子路径或 `+` Shortcut 名,不做同义替换。背景里提到的原创建命令不是额外发现目标。 用户只要求评审返回值或恢复方案时,不执行业务操作,也不按业务任务扩展到上游写入命令。发现目标以用户原始消息为准,Agent 自行摘要或传入 Skill 的参数不是新增请求,不能将“核对该对账命令”改写成“比较对账与原创建命令”。保留用户正在核对的完整原子路径或 `+` Shortcut 名,不做同义替换。背景里提到的原创建命令不是额外发现目标。
@@ -41,7 +54,7 @@ metadata:
- 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。 - 不猜命令、flag、字段、ID、账号或时间。后续 ID 必须来自真实返回;零命中、多候选或类型不明时停止并消歧。
- 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。 - 解析目标、读取上下文和最终执行必须使用同一 profile;不得跨组织复用 userId、openDingTalkId 或 openConversationId。多账号组织只使用明确的 `isOrgCurrent=true` 默认账号;没有默认账号时要求用户指定,禁止选择第一项、最近登录或最近使用账号。
- 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。 - 不输出或记录 token、refresh token、appSecret、webhook token 等凭据;宿主已注入认证时不要索要凭据。
- 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;本轮用户已明确要求执行、目标与影响无歧义的非破坏性写操作时,该明确指令就是本次确认,首次调用直接携带 Runtime 所需的 `--yes`,不先制造 `confirmation_required`。文件导入是非幂等操作,仍按 Golden Route 先触发 Runtime 确认门禁,确认后再追加 `--yes`。删除、停用自动化等破坏性或高风险动作仍须先说明对象、动作与影响并取得独立确认。 - 写操作必须符合用户明确意图。是否需要确认以最终 Runtime gate 和 Schema 为准;本轮用户已明确要求执行、目标与影响无歧义的非破坏性写操作时,该明确指令就是本次确认,首次调用直接携带 Runtime 所需的 `--yes`,不先制造 `confirmation_required`。文件导入是非幂等操作,仍按 Golden Route 先触发 Runtime 确认门禁,确认后再追加 `--yes`。删除、停用自动化等破坏性或高风险动作仍须先说明对象、动作与影响并取得独立确认。仅补确认标记时,保持目标、名称、字段、类型及其他业务参数不变,不夹带修正,不使用占位类型或重复 JSON 键。
- 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。 - 写后按任务结果契约验证;不能仅凭退出码宣称成功。部分结果、未知投递状态和失败项必须如实保留。
- Runtime Schema 是当前能力与安全语义的权威来源;同一 leaf 任务内只读一次并复用。只有 Schema 缺字段、含义不清或与真实执行冲突时才读 leaf Help;版本、profile 变化或出现契约错误时立即作废缓存。 - Runtime Schema 是当前能力与安全语义的权威来源;同一 leaf 任务内只读一次并复用。只有 Schema 缺字段、含义不清或与真实执行冲突时才读 leaf Help;版本、profile 变化或出现契约错误时立即作废缓存。
- mutation 返回、HTTP 200 和退出码 0 都只是调用回执,不是最终对象状态。写后必须清除受影响的读取缓存,用返回的真实 ID/唯一键发起独立读回,不复用写前结果。 - mutation 返回、HTTP 200 和退出码 0 都只是调用回执,不是最终对象状态。写后必须清除受影响的读取缓存,用返回的真实 ID/唯一键发起独立读回,不复用写前结果。
@@ -127,10 +140,11 @@ Help/Schema 是离线契约查询,不调用线上业务,也不读写用户
| 用户意图 | 唯一推荐入口 | 关键边界 | | 用户意图 | 唯一推荐入口 | 关键边界 |
|---|---|---| |---|---|---|
| 从已确认的 AITable URL 解析稳定 ID | `dws aitable +url-resolve --url <URL>` | 只解析 URL 中已有的 baseId/tableId/viewId/recordId,不远程解析 dlink;原始 `/i/nodes/` URL 必须先按上文规范化,dlink 目标 nodeId 直接作为 baseId | | 从已确认的 AITable URL 解析稳定 ID | `dws aitable +url-resolve --url <URL>` | 解析表、视图、记录、字段、表单、App/Page、Application 和流程 ID;保留完整 URL 的定位参数,不远程解析 dlink;原始节点先按上文确认类型,dlink 使用已确认目标 |
| 按名称唯一定位并操作 Base/Table | `dws aitable +resolve-base --name <名称>` → `dws aitable +resolve-table --base <ID> --name <表名>` | 默认精确匹配;只有用户明确接受模糊匹配时才加 `--fuzzy` | | 按名称唯一定位并操作 Base/Table | `dws aitable +resolve-base --name <名称>` → `dws aitable +resolve-table --base <ID> --name <表名>` | 默认精确匹配;只有用户明确接受模糊匹配时才加 `--fuzzy` |
| 搜索 Base 候选或检查是否存在 | `dws aitable +base-search --query <关键词>` | 用户说“搜索/找一下/候选/如果没有就创建”时直接走本入口,不先调用 `+resolve-base`;返回 `hasMore/nextCursor`,仅 `hasMore=true` 时续页;AITable Base 名称不得路由到 `dws aisearch person` | | 搜索 Base 候选或检查是否存在 | `dws aitable +base-search --query <关键词>` | 用户说“搜索/找一下/候选/如果没有就创建”时直接走本入口,不先调用 `+resolve-base`;返回 `hasMore/nextCursor`,仅 `hasMore=true` 时续页;AITable Base 名称不得路由到 `dws aisearch person` |
| 浏览 Base 下的数据表 | `dws aitable +list-tables --base <ID>` | 只返回 tableId/tableName,不加载字段 | | 浏览 Base 下的数据表 | `dws aitable +list-tables --base <ID>` | 只返回 tableId/tableName,不加载字段 |
| 新建独立空白 Base,未指定自定义表结构 | `dws aitable base create --name <名称>` | 保留平台默认结构,不额外创建默认同名表或写入业务记录;不以 `tables=[]` 表达空白 Base;未独立核验记录数时,不宣称已验证零记录 |
| 新建 Base 与整套表字段 | `dws aitable +base-bootstrap --name <名称> --tables '[{"name":"<表名>","fields":[{"fieldName":"<字段名>","type":"text"}]}]'` | 表对象键必须是 `name`,不是 `tableName`;字段使用 `fieldName/type/config`,可选 `description`;参数已足够时直接执行 | | 新建 Base 与整套表字段 | `dws aitable +base-bootstrap --name <名称> --tables '[{"name":"<表名>","fields":[{"fieldName":"<字段名>","type":"text"}]}]'` | 表对象键必须是 `name`,不是 `tableName`;字段使用 `fieldName/type/config`,可选 `description`;参数已足够时直接执行 |
| 复制 Base | `dws aitable +base-copy --base-id <B_OR_URL> [--target-folder-id <FOLDER_ID_OR_URL>] [--only-struct] [--new-name <名称>]` | `base-id` 可传 Base ID 或标准节点 URL;`target-folder-id` 可传 dentryUuid、标准节点 URL 或 Drive 文件夹 URL,省略时复制到源 Base 所在工作区根目录;参数解析与目标验证由 AITable MCP 负责 | | 复制 Base | `dws aitable +base-copy --base-id <B_OR_URL> [--target-folder-id <FOLDER_ID_OR_URL>] [--only-struct] [--new-name <名称>]` | `base-id` 可传 Base ID 或标准节点 URL;`target-folder-id` 可传 dentryUuid、标准节点 URL 或 Drive 文件夹 URL,省略时复制到源 Base 所在工作区根目录;参数解析与目标验证由 AITable MCP 负责 |
| 已有 Base 新建一张表与字段 | `dws aitable +table-bootstrap --base-id <ID> --name <表名> --fields '<JSON数组>'` | 字段使用 `fieldName/type/config`,可选 `description`;自动按 15 个字段分片并读回验证 | | 已有 Base 新建一张表与字段 | `dws aitable +table-bootstrap --base-id <ID> --name <表名> --fields '<JSON数组>'` | 字段使用 `fieldName/type/config`,可选 `description`;自动按 15 个字段分片并读回验证 |
@@ -248,7 +262,7 @@ Golden/次级直达覆盖时不读 Reference;否则按最终专有能力读取
## 错误最短路径 ## 错误最短路径
1. 零/多候选、字段歧义或分页不完整:停止并返回证据;仅有效分页会话可透传真实 `nextCursor`。失效游标先执行上方“记录稳定约束”,不能套用普通断点续传。 1. 零/多候选、字段歧义或分页不完整:停止并返回证据;仅有效分页会话可透传真实 `nextCursor`。失效游标先执行上方“记录稳定约束”,不能套用普通断点续传。
2. 类型错误只复核目标字段,不删字段或丢输入;`partial_success` 从 checkpoint 续跑,未知写入先回读。 2. 类型错误只复核目标字段,不删字段或丢输入;`partial_success` 或未知写入结果应先保留原 ID、已完成步骤及已知副作用,对受影响的原对象只读核实后,再按当前契约和授权补齐。只读核实仍无法确定写入结果或补齐范围时,停止后续写入并说明未确认项,不重放创建。checkpoint、失败计数或输入错误不构成重放许可;不得自行新建替代 Base,或通过改名、清理旧对象掩盖问题;确需替代或清理时,说明影响并按既有规则取得相应授权。原 partial 与恢复核验结果分别说明。
3. 错误提供 `actions` / `available_flags` 时只按其中的 `next_command` 修正一次;`retryable=false` 或目标 ID 类型不符时停止。 3. 错误提供 `actions` / `available_flags` 时只按其中的 `next_command` 修正一次;`retryable=false` 或目标 ID 类型不符时停止。
4. 数据源同步 `errorCode=4014` 表示同步运行中重复触发,可稍后重试;非数据源表触发同步前先用 `+base-get` 确认 `sync=true`。 4. 数据源同步 `errorCode=4014` 表示同步运行中重复触发,可稍后重试;非数据源表触发同步前先用 `+base-get` 确认 `sync=true`。
@@ -257,3 +271,11 @@ Golden/次级直达覆盖时不读 Reference;否则按最终专有能力读取
- Excel 式单元格、区域和公式操作 → `dingtalk-misc` 的 Sheet。 - Excel 式单元格、区域和公式操作 → `dingtalk-misc` 的 Sheet。
- Base 作为整体在普通文件夹间移动或做外层存储重命名 → Drive;Base 结构复制/删除,以及 Base 内 Table、Dashboard、Section 的创建、复制、移动、重命名、删除 → AITable。 - Base 作为整体在普通文件夹间移动或做外层存储重命名 → Drive;Base 结构复制/删除,以及 Base 内 Table、Dashboard、Section 的创建、复制、移动、重命名、删除 → AITable。
- 记录主键文档正文 → 取得真实 nodeId 后切 `dingtalk-doc`。 - 记录主键文档正文 → 取得真实 nodeId 后切 `dingtalk-doc`。
## 对象访问链接
对象访问链接复用 Web 定位参数:数据表/视图/字段使用 `sheetId/viewId/fieldId`,应用模式使用编码 `appId/pageId`,普通仪表盘和说明文档使用 `applicationId`,自动化使用 `flowId/autoOpenAutomation=true`。字段复合创建及续跑结果逐项保留 `sheetId/fieldId` 链接;展示方式参考前文“过程展示建议”。表单管理入口 `entrance=form` 与公开填写链接不同,不能因此开启分享;自动打开流程编辑器不会启用流程。字段业务 type 不作为链接对象类型。
`+url-resolve` 默认仅做本地解析,覆盖 Base、Table、View、Record、Field、Form、App/Page、Application 和 Workflow;参数冲突、缺父级或混用不同对象族时明确拒绝,不能把子对象降级为 Base 验证。`--verify` 只调用对应只读接口并核对所属关系;Application 经 `list_nsheet_nodes` 的 `nodeId/nodeType` 核对仪表盘/说明文档导航节点,不能用底层文档 UUID 或 Base 存在证明目标节点存在。自动化入口保留的后台表/视图/Application 参数不作为目标;无 flowId 时仅验证 Base,有 flowId 时验证流程。仅 App 链接暂无只读验证入口,必须明确拒绝验证,不能调用会隐式创建 App 的 `get_app/list_app_pages`;含 Page 的链接通过 `get_app_page` 同时核对 App/Page。公开表单分享 URL 不作为 Base 对象链接解析。
`+url-resolve` 使用统一 `ok/outcome` 信封,成功的稳定 ID 位于 `data.target`;仅 `data.verified=true` 表示只读验证通过。失败保留错误分类和非零退出码,不能把本地解析成功当作远程存在证明。
@@ -35,6 +35,8 @@ dws aitable record create --base-id <B> --table-id <T> \
## 更新、同步与批量修改 ## 更新、同步与批量修改
记录复合写入沿用服务端的 fieldId/唯一字段名寻址,CLI 不在写前读取字段目录或改写 cells。回读先直接比较;仅缺少对应字段键或需要类型投影时,才读取 `get_fields`,将校验期望中的字段名精确映射为 fieldId,同一轮校验复用目录。字段目录读取失败、不完整或映射存在歧义时,保留写入回执和已知 recordId,返回非零退出码、unknown 与 verification.status=pending,不代表写入失败,也不自动重发写入;`+record-upsert`、`+record-batch-create` 与按键创建复用有界只读回读重试(500ms、1500ms);预算耗尽仍报告未确认,不重发写入。目录是写后观测,不能证明与写入时的 schema 原子一致。`+record-upsert-by-key` 的 `--key-field-id` 仍只接受字段 ID,原有唯一键预检不变。dry-run 不新增字段请求;`+record-bulk-patch` 仍查询匹配记录,按键 upsert 仍查询唯一键以生成计划。
已知 recordId: 已知 recordId:
```bash ```bash
@@ -72,9 +72,9 @@ Base、Table、应用模式 App/Page/Widget、普通 Field、普通 Record、Vie
|------|------|----------------|----------| |------|------|----------------|----------|
| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `field get` 拿 fieldId 与类型;完整结果必须用 `--all --page-limit 0` 自动翻页;filters 中的实体展示名须先解析为稳定 ID/结构化值 | | `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `field get` 拿 fieldId 与类型;完整结果必须用 `--all --page-limit 0` 自动翻页;filters 中的实体展示名须先解析为稳定 ID/结构化值 |
| `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit | | `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit |
| `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 | | `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId;单次最多 100 条 |
| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;`cells` key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId;`--records` 是 `[{recordId,cells},...]` 数组 | | `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;`cells` key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId;`--records` 是 `[{recordId,cells},...]` 数组 |
| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable/aitable-record-update.md)、[aitable-cell-value.md](./aitable/aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key=fieldId,value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 | | `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable/aitable-record-update.md)、[aitable-cell-value.md](./aitable/aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId;value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 |
| `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 | | `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 |
| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 | | `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 |
| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) | | `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) |
@@ -479,9 +479,9 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
## 注意事项 ## 注意事项
- 所有管理和记录写入操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称;`psql` 的用户 SQL 使用 `psql -l/-t` 返回的真实逻辑表名和列名 - 对象寻址使用真实 ID(baseId/tableId/fieldId/recordId);记录写入的 cells key 也支持当前表内唯一字段名;`psql` 的用户 SQL 使用 `psql -l/-t` 返回的真实逻辑表名和列名
- `aitable psql` 输出 PostgreSQL 表格文本,不支持也不添加 `--format json`;其他结构化读取仍按全局规则使用 `--format json` - `aitable psql` 输出 PostgreSQL 表格文本,不支持也不添加 `--format json`;其他结构化读取仍按全局规则使用 `--format json`
- records 的 cells key 是 fieldId,不是字段名称 - records 的 cells key 支持 fieldId 或当前表内唯一字段名;推荐 fieldId,字段名须精确匹配
- cells 写入/读取格式见 [aitable-cell-value.md](./aitable/aitable-cell-value.md) - cells 写入/读取格式见 [aitable-cell-value.md](./aitable/aitable-cell-value.md)
- 最佳实践见 [aitable-best-practices.md](./aitable/aitable-best-practices.md) - 最佳实践见 [aitable-best-practices.md](./aitable/aitable-best-practices.md)
@@ -6,8 +6,8 @@
## 顶层规则 ## 顶层规则
- cells 的 key **必须是 fieldId**(如 `fldXXX`),不是字段名称 - cells 的 key 支持 **fieldId 或当前表内唯一字段名**;推荐 fieldId(如 `fldXXX`),字段名须精确匹配
- fieldId 必须从 `field get` 返回中获取 - 使用 fieldId 时,从 `field get` 返回中获取真实 ID
- 不同字段类型的 value 格式不同,混用会报错 - 不同字段类型的 value 格式不同,混用会报错
- 系统只读字段(creator/lastModifier/createdTime/lastModifiedTime/formula)不可写入 - 系统只读字段(creator/lastModifier/createdTime/lastModifiedTime/formula)不可写入
@@ -337,7 +337,7 @@
| 错误 | 正确做法 | | 错误 | 正确做法 |
|------|----------| |------|----------|
| cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` | | cells key 使用不存在、重名或未精确匹配的字段名 | 优先用真实 fieldId;字段名须在当前表内唯一且精确匹配 |
| progress 写入 `75` | 写入 `0.75`(范围 0~1) | | progress 写入 `75` | 写入 `0.75`(范围 0~1) |
| attachment 直接传本地文件路径 | 本地文件先上传获得 fileToken;在线 URL 使用 `[{"url":"https://..."}]`,写后验证异步转存结果 | | attachment 直接传本地文件路径 | 本地文件先上传获得 fileToken;在线 URL 使用 `[{"url":"https://..."}]`,写后验证异步转存结果 |
| user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` | | user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` |
@@ -24,8 +24,8 @@ Flags:
| 错误 | 说明 | | 错误 | 说明 |
|------|------| |------|------|
| 参数名用 `--data` | ❌ 参数名是 `--records`,不是 `--data` | | 参数名用 `--data` | ❌ 参数名是 `--records`,不是 `--data` |
| cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) | | cells key 使用不存在、重名或未精确匹配的字段名 | 支持 fieldId 或当前表内唯一字段名;推荐 fieldId,字段名须精确匹配 |
| 不先获取 fieldId | ❌ 必须先 `field get` 获取 fieldId,再写入记录 | | 猜测 fieldId | 使用 fieldId 时,先 `field get` 获取真实 ID;也可使用已确认的当前表内唯一字段名 |
| 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 | | 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 |
| 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) | | 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) |
@@ -24,14 +24,14 @@ dws aitable record upsert \
```jsonc ```jsonc
{ {
"recordId": "rec1", // 可选;带 → update,缺省 → create "recordId": "rec1", // 可选;带 → update,缺省 → create
"cells": { // 必填;key 是 fieldId,value 按字段类型 "cells": { // 必填;key 支持 fieldId 或当前表内唯一字段名,value 按字段类型
"fldTitleId": "新标题", "fldTitleId": "新标题",
"fldNumberId": 42 "fldNumberId": 42
} }
} }
``` ```
`cells` 写入格式与 `record create` / `record update` **完全一致**(key 必须是 fieldId 不是字段名;按字段类型见 [aitable-cell-value.md](./aitable-cell-value.md))。 `cells` 写入格式与 `record create` / `record update` **完全一致**(key 支持 fieldId 或当前表内唯一字段名,推荐 fieldId,字段名须精确匹配;按字段类型见 [aitable-cell-value.md](./aitable-cell-value.md))。
## 返回结构 ## 返回结构
@@ -85,5 +85,5 @@ dws aitable record upsert --base-id BASE --table-id TBL --records-file ./batch.j
## 注意事项 ## 注意事项
- **单次最多 100 条**(创建 + 更新合计),超出请客户端拆批。 - **单次最多 100 条**(创建 + 更新合计),超出请客户端拆批。
- `cells` 的 key 必须是 fieldId 不是字段名(先用 `record query` 或 `field get` 拿 fieldId)。 - `cells` 的 key 支持 fieldId 或当前表内唯一字段名;推荐从 `field get` 获取真实 fieldId,字段名须精确匹配。
- 只读字段(formula / lookup / 系统字段)不能写入 — upsert 链路与 update 链路同样限制。 - 只读字段(formula / lookup / 系统字段)不能写入 — upsert 链路与 update 链路同样限制。
@@ -37,6 +37,9 @@
| `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | | `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` |
| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | | `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` |
| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | | `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` |
| `aitable` | AI表格字段定位 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=entrance%3Ddata%26sheetId%3D{tableId}%26fieldId%3D{fieldId}` | 已确认的 `baseId/tableId/fieldId`;字段定位依赖前端能力 |
| `aitable` | 应用模式页面 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=entrance%3Dapps%26appId%3D{appId}%26pageId%3D{pageId}` | 编码 `appId` 与真实 `pageId` |
| `aitable` | 自动化流程编辑 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=entrance%3Dautomation%26flowId%3D{flowId}%26autoOpenAutomation%3Dtrue` | 真实 `flowId`;导航不会启用流程 |
| `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | | `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` |
| `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | | `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` |
| `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | | `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` |
@@ -77,7 +77,7 @@ metadata:
## 写生命周期 ## 写生命周期
- 钉钉回复任务:接入方默认先发 A2UI 占位、执行中更新;主动发送默认完整投递。按[占位与流式](references/card/a2ui.md)执行。 - 钉钉回复任务:接入方默认先发 A2UI 占位、执行中更新;主动发送默认完整投递。按[占位与流式](references/card/a2ui.md)执行;卡片摘要与转发同见此文。
- 多步骤写先校验目标、限制、内容、确认,再沿稳定 ID 串行执行并验证;不截断或换目标。临时资源保留 ID/阶段,失败仅做已授权清理。 - 多步骤写先校验目标、限制、内容、确认,再沿稳定 ID 串行执行并验证;不截断或换目标。临时资源保留 ID/阶段,失败仅做已授权清理。
## 上下文预算 ## 上下文预算
@@ -2,6 +2,10 @@
使用 `dws chat message send-a2ui-card` 创建卡片,使用 `update-a2ui-card` 更新和完结。`--content` 接受 JSON 字符串数组,每个元素是一条 A2UI 消息;传 `-` 时从 stdin 读取同样的内容。 使用 `dws chat message send-a2ui-card` 创建卡片,使用 `update-a2ui-card` 更新和完结。`--content` 接受 JSON 字符串数组,每个元素是一条 A2UI 消息;传 `-` 时从 stdin 读取同样的内容。
## 卡片摘要与转发
创建时用 `--summary` 传一句简短可读的降级文案,客户端无法渲染卡片时展示;它不是完结后的会话摘要,后者见[组件注解](#组件注解)。不要传序列化消息、模板摘要或实例 ID;省略时为 `互动卡片`,显式空值或纯空白会被拒绝。`--support-forward` 默认 `false`,仅在用户或业务允许分享时开启;转发不授予业务操作权限。两者只能在创建时设置,`update-a2ui-card` 没有对应参数,`dataModel.summary`、`fallbackMarkdown` 也不能代替。统一入口 `chat +messages-send --a2ui-messages` 对应 `--card-summary` 和 `--support-forward`。
## 回复占位与主动发送 ## 回复占位与主动发送
回复用户消息,或处理明确需要交互反馈的事件任务时,默认先发 A2UI 占位卡,再按实际进度更新同一卡片,不要求用户额外指定“流式”。简短回复从占位直接更新为答案,不编造阶段。事件发生本身不构成回复授权。 回复用户消息,或处理明确需要交互反馈的事件任务时,默认先发 A2UI 占位卡,再按实际进度更新同一卡片,不要求用户额外指定“流式”。简短回复从占位直接更新为答案,不编造阶段。事件发生本身不构成回复授权。
@@ -39,13 +43,71 @@
```bash ```bash
dws chat message send-a2ui-card \ dws chat message send-a2ui-card \
--open-dingtalk-id '<接收人openDingTalkId>' \ --open-dingtalk-id '<接收人openDingTalkId>' \
--summary "任务进度与结果" \
--content '["{\"version\":\"v1.0\",\"createSurface\":{\"surfaceId\":\"example-card\",\"catalogId\":\"https://dingtalk.com/card/a2ui/catalogs/public/catalog.json\",\"dataModel\":{\"answer\":{\"displayText\":\"任务已开始。\"},\"status\":\"doing\"}}}","{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"root\",\"component\":\"Column\",\"align\":\"stretch\",\"children\":[\"executionPanel\"]},{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"[AIGenerating]思考中…\",\"textEffect\":\"shimmer\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"},{\"id\":\"answer\",\"component\":\"Markdown\",\"content\":{\"path\":\"/answer/displayText\"}}]}}"]' \ --content '["{\"version\":\"v1.0\",\"createSurface\":{\"surfaceId\":\"example-card\",\"catalogId\":\"https://dingtalk.com/card/a2ui/catalogs/public/catalog.json\",\"dataModel\":{\"answer\":{\"displayText\":\"任务已开始。\"},\"status\":\"doing\"}}}","{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"root\",\"component\":\"Column\",\"align\":\"stretch\",\"children\":[\"executionPanel\"]},{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"[AIGenerating]思考中…\",\"textEffect\":\"shimmer\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"},{\"id\":\"answer\",\"component\":\"Markdown\",\"content\":{\"path\":\"/answer/displayText\"}}]}}"]' \
-f json -f json
``` ```
群聊时将 `--open-dingtalk-id` 换成 `--conversation-id '<群openConversationId>'`,选择一个目标。创建状态默认 `PROCESSING`;发送成功后保存返回的 `bizId`,用于后续更新。 群聊时将 `--open-dingtalk-id` 换成 `--conversation-id '<群openConversationId>'`,选择一个目标。创建状态默认 `PROCESSING`;发送成功后保存返回的 `bizId`,用于后续更新。
允许转发时,在创建命令中加上 `--support-forward`,默认 `false`。 示例未开启转发;允许分享时在创建命令中加上 `--support-forward`。
## @ 成员或所有人
群聊卡片的 Markdown 组件支持 @ 提醒。正文中写 `<a atId="openDingTalkId">昵称</a>` @ 成员,写 `<a atId="all">everyone</a>` @ 所有人;同时必须通过发送参数声明:
- 每个成员的 openDingTalkId 用 `--at-open-dingtalk-ids` 声明(可逗号分隔或重复传参),发送时透传为请求的 `atOpenDingTalkIds`;声明总数含重复不超过 100。
- @ 所有人必须加 `--at-all`,发送时透传为 `atAll: true`;保留值 `all` 只出现在正文,不进入 `atOpenDingTalkIds`。线上请求中 CLI 会把正文的 `<a atId="all">` 改写为服务端高亮别名 `<a atId="10">`,本地文件与校验仍使用 `all`。
通知校验只在显式传入 @ 参数时启用:可静态确定的 Markdown 标签与声明不一致时 exit 3。支持字面量及同批 `createSurface.dataModel` / `updateDataModel` 中可解析的绝对 `content.path`;合法绑定允许创建时先通知、后续填入正文,CLI 不执行运行时表达式,也不能预知后续绑定值。行内代码、围栏代码、缩进代码及转义的标签示例不作为 @。
不传 @ 参数时保留既有正文和默认摘要,群聊、单聊均不做新增通知校验或标签改写;标签本身不保证通知。单聊仍拒绝显式 @ 参数。创建且显式 `--at-all` 时,仅改写已确定的 Markdown 字符串值,保留其他 JSON 字段顺序、空白和未改动转义;摘要仍来自原始消息。
服务端按声明发通知、失败即拒:声明的 id 无效或无法解析、声明超限、单聊携带 @ 或别名冲突时,服务端返回 INVALID_PARAM(MENTION_OPEN_ID_INVALID / MENTION_RESOLUTION_FAILED / MENTION_LIMIT_EXCEEDED / MENTIONS_GROUP_ONLY / MENTION_ALIAS_CONFLICT 等)拒绝建卡,命令退出码非 0。服务端只认声明、不扫描正文:正文用了但未声明的 atId 既不高亮也不通知,因此 CLI 在发送前按上段一致性校验拦截。
```bash
dws chat message send-a2ui-card \
--conversation-id '<群openConversationId>' \
--at-open-dingtalk-ids '<成员openDingTalkId>' \
--at-all \
--content '["{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"answer\",\"component\":\"Markdown\",\"content\":\"<a atId=\\\"<成员openDingTalkId>\\\">张三</a> <a atId=\\\"all\\\">everyone</a> 请查看任务进度。\"}]}}"]' \
-f json
```
静态匹配使用 `atId` 为首个且唯一属性、值使用双引号的 `<a atId="...">` 写法;绑定路径仅解析同批已提供数据。无 @ 参数时沿用既有透传行为;显式通知时,内层消息 JSON 无效会在发送前拒绝。
`aicard lint --preflight ... --at-open-dingtalk-ids ...` 仅检查成员声明与正文的一致性以及 IM 占位符误用;它没有 `--at-all`,不检查所有人标签配对或 100 个声明上限,不能代替发送入口的完整预检。
`--at-all` 沿用现有卡片发送入口的确认策略,不新增交互确认;发送者须有群聊提醒授权。群设置拒绝 @所有人时,以服务端实际错误为准,不应假定已发送成功。正式发布前必须确认服务端已支持 `atOpenDingTalkIds`/`atAll` 及 fail-closed 契约,并执行真实通知验收;旧服务端可能忽略字段,出现有标签却未通知的结果。
统一发送命令 `dws chat +messages-send --as user --a2ui-messages ...` 支持同样的 `--at-open-dingtalk-ids`/`--at-all` 参数与一致性校验。更新卡片(`update-a2ui-card`)不接受 @ 参数。更新保留输入消息,不重新推断通知对象或改写任意数据模型字符串。
## 流式 @:固定通知栏与绑定正文
推荐在创建时用独立 Markdown 组件展示 @,正文仍按前文绑定 `/answer/displayText`。这样占位、流式和 FINISH 都更新正文,不会重发通知,也不必在无组件上下文的增量数据中猜测 @ 标签。
下面生成可供 `--content -` 使用的消息字符串数组;用实际 openDingTalkId 替换参数,并沿用创建回执的 `bizId`:
```bash
python3 - '<成员openDingTalkId>' <<'PYCODE' > mention-stream.json
import json, sys
member = sys.argv[1]
messages = [
{"version": "v1.0", "createSurface": {"surfaceId": "mention-stream", "catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json", "dataModel": {"answer": {"displayText": "思考中…"}}}},
{"version": "v1.0", "updateComponents": {"surfaceId": "mention-stream", "components": [
{"id": "root", "component": "Column", "children": ["mentions", "answer"]},
{"id": "mentions", "component": "Markdown", "content": f'<a atId="{member}">成员</a> <a atId="all">所有人</a>'},
{"id": "answer", "component": "Markdown", "content": {"path": "/answer/displayText"}}
]}}
]
json.dump([json.dumps(item, ensure_ascii=False) for item in messages], sys.stdout, ensure_ascii=False)
PYCODE
dws chat message send-a2ui-card --conversation-id '<群ID>' --at-open-dingtalk-ids '<成员openDingTalkId>' --at-all --content - < mention-stream.json
```
更新时只提交 `/answer/displayText` 的累计正文,并以非空 FINISH 完结;不要重放 `mentions` 组件。只有 `updateDataModel` 的增量没有组件类型上下文,不能安全推断任意值为 Markdown。确需重发 @ 组件时,保留已确认的创建时 wire 表达;当前服务端的所有人高亮别名为 `10`,更新不产生新的通知。`all` 的更新渲染兼容性须结合实际服务端/客户端验证,不把接口接受当作展示证明。
止血:`DWS_A2UI_MENTIONS_DISABLED=1` 或 `true` 会拒绝新的显式 @ 通知,并停止创建时标签改写。默认关闭止血开关;无通知参数的原路径不变。每次 CLI 调用读取变量,宿主必须向 DWS 子进程传入;它不会中断已运行请求或撤回已发消息。恢复时去掉变量,必要时回滚二进制。
## 更新和完结 ## 更新和完结
@@ -69,11 +131,12 @@ dws chat message update-a2ui-card \
-f json -f json
``` ```
可按同样方式继续分批更新。任务完成后写入完整实际结果,并将 `--flow-status` 设为 `FINISH`: 可按同样方式继续分批更新。任务完成后写入完整实际结果,把承载结果的 `answer` 注解为产物,并将 `--flow-status` 设为 `FINISH`:
```bash ```bash
dws chat message update-a2ui-card \ dws chat message update-a2ui-card \
--biz-id '<本次发送返回的bizId>' \ --biz-id '<本次发送返回的bizId>' \
--a2ui-annotations '[{"surfaceId":"example-card","componentId":"answer","type":"artifact"}]' \
--flow-status FINISH \ --flow-status FINISH \
--content '["{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"处理完成\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"}]}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/answer/displayText\",\"value\":\"已完成资料读取。\\n\\n已整理出分析结果。\\n\\n任务已完成。\"}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/status\",\"value\":\"finished\"}}"]' \ --content '["{\"version\":\"v1.0\",\"updateComponents\":{\"surfaceId\":\"example-card\",\"components\":[{\"id\":\"executionPanel\",\"component\":\"CollapsiblePanel\",\"title\":\"处理完成\",\"children\":[\"answer\"],\"fallbackMarkdown\":\"任务进度\"}]}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/answer/displayText\",\"value\":\"已完成资料读取。\\n\\n已整理出分析结果。\\n\\n任务已完成。\"}}","{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"example-card\",\"path\":\"/status\",\"value\":\"finished\"}}"]' \
-f json -f json
@@ -91,4 +154,6 @@ dws chat message update-a2ui-card \
本例的 `componentId` 为 `answer`,`type` 为 `artifact`,不要把组件 ID 当作注解类型。更新时也可关联此前已创建的组件。该参数可选,支持 `[]`;省略时创建请求不携带注解字段,更新请求沿用原有的空数组。 本例的 `componentId` 为 `answer`,`type` 为 `artifact`,不要把组件 ID 当作注解类型。更新时也可关联此前已创建的组件。该参数可选,支持 `[]`;省略时创建请求不携带注解字段,更新请求沿用原有的空数组。
卡片进入 `FINISH`、`CONFIRMING` 或 `CONFIRMED` 时,服务端从 `type` 为 `artifact` 的组件异步生成会话摘要,因此触发这些状态的更新要携带注解。注解组件中没有可展示内容(`Text`、`Markdown`、`Image`、`ImageList`、`Link`、`File`、`Video`、`AudioPlayer`,或包含它们的容器)时,会话摘要显示服务端默认文案,如“暂无可展示的最终产物”。注解承载主结果的组件,`root` 仅作兜底;不要注解思考过程、凭据或无关输入,也不要为生成摘要而改变业务状态。更新受理不代表摘要已生成,无法回读时如实说明未验证。
发送后核对创建结果和客户端展示,再检查同一 `bizId` 的更新和完结效果。遇到 `a2ui dws catalog validation failed` 时,可对照 Catalog 检查组件属性、JSON 格式、Catalog 地址和 surface 创建顺序。 发送后核对创建结果和客户端展示,再检查同一 `bizId` 的更新和完结效果。遇到 `a2ui dws catalog validation failed` 时,可对照 Catalog 检查组件属性、JSON 格式、Catalog 地址和 surface 创建顺序。
@@ -33,11 +33,16 @@ streaming 不是任意组件 Schema:
A2UI 原子命令规则: A2UI 原子命令规则:
- `send-a2ui-card` 调用 `im.create_and_send_a2ui_card`。 - `send-a2ui-card` 调用 `im.create_and_send_a2ui_card`。
- 创建时可选 `--extension '{"scene":"example"}'`,接受 key 非空白、value 为字符串的 JSON 对象并透传为 `extension`;支持 `{}` 和空字符串值。省略时不携带该字段。业务 key 由服务端白名单控制,传入不代表已放行;系统字段保持服务端受控语义。`null`、非字符串值及空白 key 会在发送前拒绝。
- 隐藏发送者头像:目标环境已将 `hide_avatar` 加入服务端白名单时,创建卡片可传 `--extension '{"hide_avatar":"true"}'`。值是字符串 `"true"`,不是布尔值;它是卡片消息扩展,不放入 A2UI `createSurface`、组件或 surface metadata。未放行时该业务 key 会被过滤,DWS 不会修改白名单;未知 key 的含义须先确认。此扩展不影响服务端生成的系统字段和权限元数据。
- `update-a2ui-card` 调用 `im.update_a2ui_card`。 - `update-a2ui-card` 调用 `im.update_a2ui_card`。
- `--content` 必须是 JSON 字符串数组,元素为 A2UI 协议 JSON,例如 - `--content` 必须是 JSON 字符串数组,元素为 A2UI 协议 JSON,例如
`'["{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"surface\",\"path\":\"/status\",\"value\":\"finished\"}}"]'`; `'["{\"version\":\"v1.0\",\"updateDataModel\":{\"surfaceId\":\"surface\",\"path\":\"/status\",\"value\":\"finished\"}}"]'`;
CLI 解析为 `a2uiMessages`,send-a2ui-card 额外生成 `summary`,值为数组元素按换行拼接 CLI 解析为 `a2uiMessages`;`--summary` 写入顶层 `summary`,作为卡片无法渲染时的降级文案,
(真实 MCP 契约无 `fallbackText` 字段)。 省略时为 `互动卡片`,显式空白校验失败。不要传 `fallbackText`,也不要在 `createSurface` 中加 `summary`。
- 创建时 `--support-forward` 写入顶层布尔值 `supportForward`,默认 `false`,不要包在 `cardProperty` 中。
- `FINISH`/`CONFIRMING`/`CONFIRMED` 后,服务端按 `artifact` 注解异步生成会话摘要,无可提取产物时写入默认文案;
`update-a2ui-card` 没有摘要参数,更新受理不代表摘要已生成。
- 群聊目标写入顶层 `openConversationId`;单聊目标写入顶层 `receiverOpenDingTalkId` - 群聊目标写入顶层 `openConversationId`;单聊目标写入顶层 `receiverOpenDingTalkId`
(传入 userId 时由 CLI 自动解析转换为 openDingTalkId)。 (传入 userId 时由 CLI 自动解析转换为 openDingTalkId)。
- A2UI wire 层 `flowStatus` 为字符串枚举;CLI `--flow-status` 接受枚举名和兼容数字 1–9: - A2UI wire 层 `flowStatus` 为字符串枚举;CLI `--flow-status` 接受枚举名和兼容数字 1–9:
@@ -47,8 +52,9 @@ A2UI 原子命令规则:
`protocolVersion="1.0"`;创建默认 `flowStatus=PROCESSING`。 `protocolVersion="1.0"`;创建默认 `flowStatus=PROCESSING`。
- 两个 A2UI 命令均支持可选 `--a2ui-annotations`,接受 JSON 对象数组并透传为 - 两个 A2UI 命令均支持可选 `--a2ui-annotations`,接受 JSON 对象数组并透传为
`a2uiAnnotations`。省略时创建请求不携带该字段,更新请求保持发送 `[]`。 `a2uiAnnotations`。省略时创建请求不携带该字段,更新请求保持发送 `[]`。
- A2UI 命令不提供 `--at-open-dingtalk-ids` / `--at-all`;@ 仅 streaming - A2UI 创建命令支持群聊 `--at-open-dingtalk-ids` / `--at-all`,分别透传为
`send-card` 群聊可用。 `atOpenDingTalkIds` / `atAll`;单聊不接受通知参数。更新命令不提供这些参数,
不新增通知;通知与正文绑定规则见 [A2UI 指南](a2ui.md)。
参数、required 和 confirmation 读取 参数、required 和 confirmation 读取
`dws schema --cli-path "chat +messages-send-card" --compact -f json` 或 `dws schema --cli-path "chat +messages-send-card" --compact -f json` 或
@@ -97,7 +97,7 @@ Golden Route 已给出命令且参数足够时,禁止读取 reference;其余
## 错误最短路径 ## 错误最短路径
1. 零命中、多候选、类型不明或分页不完整:停止写入,展示候选或 continuation;禁止默认第一项。 1. 零命中、多候选、类型不明或分页不完整:停止写入,展示候选或 continuation;禁止默认第一项。
2. `+fetch` 若确认目标是目录,浏览请求复用完整 URL 执行 `+list --folder`,不要改用 `drive +list`。Help 不参与选路;先读一次精确 leaf Schema。仅真实 `unknown flag`/契约漂移查一次 leaf Help;`unknown command` 只查一次 shortcut 清单,禁止试探后缀和 `dws doc --help | grep/head`。 2. `+fetch` 若确认目标是目录,浏览请求复用完整 URL 执行 `+list --folder`,不要改用 `drive +list`。Help 不参与选路;先读一次精确 leaf Schema。仅真实 `unknown flag`/契约漂移查一次 leaf Help;`unknown command` 按统一恢复路径处理,禁止试探后缀和 `dws doc --help | grep/head`。
3. `REVISION_CONFLICT`:重新读取当前 revision,展示差异;未经用户确认不得改成无 revision 覆盖。 3. `REVISION_CONFLICT`:重新读取当前 revision,展示差异;未经用户确认不得改成无 revision 覆盖。
4. `doc_write_commit_unknown`:先回读;禁止自动重试创建或追加。 4. `doc_write_commit_unknown`:先回读;禁止自动重试创建或追加。
5. 认证、权限或 profile 错误:只读 `dingtalk-shared` 对应 reference,禁底层命令绕过。 5. 认证、权限或 profile 错误:只读 `dingtalk-shared` 对应 reference,禁底层命令绕过。
@@ -20,6 +20,14 @@
稳定返回 ID/任务 ID 只用于定位目标、恢复流程或继续查询,不能单独证明写操作成功。成功证据优先级从高到低为:与该操作匹配的明确服务端成功终态 → 契约要求的写后读回匹配 → 仅 transport/退出码;异步任务必须使用返回的任务 ID 查询到成功终态。只有成功终态成立且所有必要回读均匹配时才报告成功;`partial_success`、`unknown`、未完成任务或仅返回资源/任务 ID 都不得报告完成。Runtime 已给出充分回读证据时,不为“再确认一次”重复请求。 稳定返回 ID/任务 ID 只用于定位目标、恢复流程或继续查询,不能单独证明写操作成功。成功证据优先级从高到低为:与该操作匹配的明确服务端成功终态 → 契约要求的写后读回匹配 → 仅 transport/退出码;异步任务必须使用返回的任务 ID 查询到成功终态。只有成功终态成立且所有必要回读均匹配时才报告成功;`partial_success`、`unknown`、未完成任务或仅返回资源/任务 ID 都不得报告完成。Runtime 已给出充分回读证据时,不为“再确认一次”重复请求。
## 收尾交付
创建或更新用户要求的最终在线资源并完成必要核验后,按顺序收尾:复用已确认的最终文档 URL(当次回执的 `documentUrl`/`docUrl`/`url`,缺失时用 `doc info` 只读补查)→ 当前宿主提供资源展示能力时,按其声明完成展示请求;所有交付物处理后再最终回复,以 `[名称](完整URL)` 形式保留具名链接,URL 保留原编码,不截断、不缩写、不用省略号替代。宿主未提供展示能力时,直接交付已确认的链接。业务成功标志、正文链接或浏览器已打开,均不替代宿主要求的展示步骤;按宿主回执描述实际交付状态,展示请求已受理不等于界面已展示。
本轮相同 URL 的展示请求已受理后不重复调用;普通只读引用、用户明确排除展示时跳过展示步骤;用户明确要求展示已有资源时,无须新增业务写入。URL 无法确认或展示请求失败时,保留业务结果与已有确定链接并说明限制,不猜地址、不用父级入口冒充直达、不重复业务写入。
创建后的完整示例:`+create` 返回 `docUrl` 且 `verified=true` 时,Runtime 已完成写后核验,直接进入交付——按宿主交付规则请求展示 `[文档名](docUrl)` 并回复用户;复用该核验结果,不额外 `+fetch`,不为展示效果重新创建文档。
## 分页与完整性 ## 分页与完整性
列表和搜索结果需要区分: 列表和搜索结果需要区分:
@@ -365,7 +365,7 @@ dws doc +create --name "<文档名>" --content "短内容" --doc-format markdown
- 回读文本顺序和临时 Markdown 一致。 - 回读文本顺序和临时 Markdown 一致。
- 没有把字面量 `\n` 渲染成一整行。 - 没有把字面量 `\n` 渲染成一整行。
- 如果返回 `chunksWritten > 1`,看 `degradations`:为空即表示分片没有改变渲染结构,无需人工核对边界;非空时按其中的 `kind` 与 `line` 定点检查(如 `table_split` 表示该表被拆成多张、每张带重发的表头)。 - 如果返回 `chunksWritten > 1`,看 `degradations`:为空即表示分片没有改变渲染结构,无需人工核对边界;非空时按其中的 `kind` 与 `line` 定点检查(如 `table_split` 表示该表被拆成多张、每张带重发的表头)。
- 最终回复必须给用户 `docUrl`;如果只拿到 `nodeId`,说明链接字段未返回,并报告已尝试 `doc info`。 - 最终回复必须给用户 `docUrl`,交付顺序与展示步骤按 [contracts.md](../../contracts.md)「收尾交付」执行;如果只拿到 `nodeId`,说明链接字段未返回,并报告已尝试 `doc info`。
## 缺失补救 ## 缺失补救
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-event name: dingtalk-event
description: 钉钉个人 IM、OA 审批、VoIP 通话邀请、待办、互动卡片与好友事件长连接监听。Use when 用户说监听消息/@我/某人/某群/全部消息、已读/撤回/reaction、群成员加入/群成员退出,监听审批任务创建/完成/转交、审批实例发起/抄送/终止/完成、VoIP 通话邀请、待办创建/更新/删除、好友申请/好友添加成功,或互动卡片回调。命令前缀:dws event。 description: 钉钉个人 IM、OA 审批、VoIP 通话邀请、待办、日历、互动卡片、好友与听记事件长连接监听。Use when 监听消息/@我/已读/撤回/reaction/群成员加入/群成员退出,审批任务创建/完成/转交、审批实例发起/抄送/终止/完成、待办创建/更新/删除、日程创建/新增参与/修改/取消、好友申请/添加、听记创建/纪要生成或互动卡片回调。命令前缀:dws event。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -9,11 +9,11 @@ metadata:
- dws - dws
--- ---
# 钉钉个人 IM、OA 审批、VoIP、待办、互动卡片与好友事件 # 钉钉个人事件
> **前置:执行 `dws` 前必须完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。**Shared references 仅按需加载。 > **前置:执行 `dws` 前必须完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。**Shared references 仅按需加载。
本 Skill 只负责实时个人事件;发送/历史消息、审批处理、待办操作分别走 `dingtalk-chat`、`dingtalk-misc`、`dingtalk-todo`。 实时个人事件以外,消息、审批、待办、日程操作分别走 `dingtalk-chat`、`dingtalk-misc`、`dingtalk-todo`、`dingtalk-calendar`。
<!-- dws-intent: event.listen.im -->消息、reaction、已读和撤回的默认监听入口是 `dws event +listen-im`; <!-- dws-intent: event.listen.im -->消息、reaction、已读和撤回的默认监听入口是 `dws event +listen-im`;
只有群生命周期、Filter DSL、原始 envelope 或底层订阅控制才使用 `event consume`。 只有群生命周期、Filter DSL、原始 envelope 或底层订阅控制才使用 `event consume`。
@@ -32,15 +32,14 @@ metadata:
| 同一人/群的消息、表情、已读或撤回 | `dws event +listen-im --kind <sender|group> --events message,reaction,read,recall ...` | | 同一人/群的消息、表情、已读或撤回 | `dws event +listen-im --kind <sender|group> --events message,reaction,read,recall ...` |
| 监听全部单聊或全部群消息 | `dws event +listen-im --kind <all-direct|all-group>`(`user_im_message_receive_o2o_all`/`user_im_message_receive_group_all`);只有用户明确要求“全部”时使用 | | 监听全部单聊或全部群消息 | `dws event +listen-im --kind <all-direct|all-group>`(`user_im_message_receive_o2o_all`/`user_im_message_receive_group_all`);只有用户明确要求“全部”时使用 |
| 群改名、成员进退、群解散 | EventKey 为 `user_im_group_updated`、`user_im_group_member_added`、`user_im_group_member_exited`、`user_im_group_disbanded`,读取 [EventKey 索引](references/event-im-keys.md) | | 群改名、成员进退、群解散 | EventKey 为 `user_im_group_updated`、`user_im_group_member_added`、`user_im_group_member_exited`、`user_im_group_disbanded`,读取 [EventKey 索引](references/event-im-keys.md) |
| OA 审批任务或实例事件 | 读取 [OA 事件参考](references/event-oa.md),使用精确 `event consume` EventKey | | OA 审批任务或实例事件 | [OA 参考](references/event-oa.md):`event consume` |
| 查看 OA 事件目录 | `dws event list --category oa` | | VoIP 通话邀请 | [VoIP 参考](references/event-voip.md):`event consume` |
| VoIP 通话邀请 | 读取 [VoIP 事件参考](references/event-voip.md),使用精确 `event consume` EventKey | | 待办创建/更新/删除 | [Todo 参考](references/event-todo.md):`event consume` 与 `--role-types` |
| 待办创建、更新或删除事件 | 读取 [Todo 事件参考](references/event-todo.md),使用精确 `event consume` EventKey 与 `--role-types` | | 日程创建/新增参与/修改/取消 | [日历参考](references/event-calendar.md):`event consume user_calendar_event_changed` |
| 查看 Todo 事件目录 | `dws event list --category todo` | | 好友申请/添加成功 | [好友参考](references/event-contact.md):`event consume` |
| 好友申请/好友添加成功事件 | 读取 [好友事件参考](references/event-contact.md),使用精确 `event consume` EventKey | | 互动卡片回调 | [卡片参考](references/event-card.md) |
| 查看好友事件目录 | `dws event list --category contact` | | 听记创建/纪要生成 | <!-- dws-intent: event.listen.minutes -->`dws event consume`,见 [听记参考](references/event-minutes.md) |
| 互动卡片回调 | 读取 [互动卡片事件参考](references/event-card.md);命令与字段见下方卡片契约 | | 查看事件目录 | `dws event list --category <分类>` |
| 查看互动卡片事件目录 | `dws event list --category card` |
| 已知 EventKey 或需要底层订阅控制 | `dws event consume`;参数与约束以 leaf Schema 为准 | | 已知 EventKey 或需要底层订阅控制 | `dws event consume`;参数与约束以 leaf Schema 为准 |
| 查看状态 / 停止 | `dws event status` / `dws event stop <subscribe_id> --dry-run`,确认后再 `--yes` | | 查看状态 / 停止 | `dws event status` / `dws event stop <subscribe_id> --dry-run`,确认后再 `--yes` |
@@ -51,12 +50,14 @@ metadata:
- `group` 必须且只能传 `--chat-id` 或 `--chat-query` 之一。 - `group` 必须且只能传 `--chat-id` 或 `--chat-query` 之一。
- `--query` 只用于纯 `message` 监听;混入 reaction/read/recall 时不得使用。 - `--query` 只用于纯 `message` 监听;混入 reaction/read/recall 时不得使用。
OA 七个 EventKey 使用 `ruleType=all`、`filterRule={}`,不接受目标或消息过滤;Todo 三个 EventKey 仅接受 `--role-types creator,executor,participant`,省略时取并集;好友两个 EventKey 约束同 OA。每项独立订阅并共享 bus。 OA 七个 EventKey 使用 `ruleType=all`、`filterRule={}`,不接受目标或消息过滤;Todo 三个 EventKey 仅接受 `--role-types creator,executor,participant`,省略时取并集;好友、听记各两个 EventKey 约束同 OA。每项独立订阅并共享 bus。
<!-- dws-intent: event.listen.card -->互动卡片使用 `dws event consume user_card_action_triggered --flatten -f ndjson`、`ruleType=all`、`filterRule={}`;不接受目标、角色或消息过滤。结构化上下文在 `payload.body.actionData.context`:`questions[].id` 关联 `answers[question_id]` 读 `selected`,未知字段保留。 <!-- dws-intent: event.listen.card -->互动卡片使用 `dws event consume user_card_action_triggered --flatten -f ndjson`、`ruleType=all`、`filterRule={}`;不接受目标、角色或消息过滤。结构化上下文在 `payload.body.actionData.context`:`questions[].id` 关联 `answers[question_id]` 读取 `selected`,详见 [卡片参考](references/event-card.md)。
<!-- dws-intent: event.listen.contact -->好友申请/好友添加成功事件使用 `dws event consume`;查询或操作好友关系走 `dws contact`,不要轮询模拟事件。 <!-- dws-intent: event.listen.contact -->好友申请/好友添加成功事件使用 `dws event consume`;查询或操作好友关系走 `dws contact`,不要轮询模拟事件。
<!-- dws-intent: event.listen.calendar -->主日历创建/新增参与/修改/取消使用 `dws event consume user_calendar_event_changed --flatten -f ndjson`;过滤与去重见 [日历参考](references/event-calendar.md)。
姓名/群名必须唯一解析,零命中或多候选在创建订阅前停止。解析、监听、状态、停止使用同一 `--profile`,不得跨组织搬运 ID。 姓名/群名必须唯一解析,零命中或多候选在创建订阅前停止。解析、监听、状态、停止使用同一 `--profile`,不得跨组织搬运 ID。
### EventKey 索引 ### EventKey 索引
@@ -81,13 +82,15 @@ OA 七个 EventKey 使用 `ruleType=all`、`filterRule={}`,不接受目标或
- `event stop` 取消订阅并影响本地 consumer:先 `--dry-run`,确认后 `--yes`。 - `event stop` 取消订阅并影响本地 consumer:先 `--dry-run`,确认后 `--yes`。
- 多事件属于一次原始操作;任一订阅启动失败时 Runtime 回滚本次已创建项,不拆成新命令绕过重试预算。 - 多事件属于一次原始操作;任一订阅启动失败时 Runtime 回滚本次已创建项,不拆成新命令绕过重试预算。
- 全部 30 个公开个人 EventKey(16 IM + 7 OA + 1 VoIP + 3 Todo + 1 卡片 + 2 好友)遵循 Agent/host `0/2/1`:`retryable=false`→`max_additional_attempts=0`;`retryable=true`→`max_additional_attempts=2`;`retryable=unknown`→`max_additional_attempts=1`。它不是 CLI 持久化硬总次数上限;进程内不会自动重试,也不持久化或计算跨调用的 Agent/host 尝试次数。 - 全部 33 个公开个人 EventKey(16 IM + 7 OA + 1 VoIP + 3 Todo + 1 卡片 + 2 好友 + 2 听记 + 1 日历)遵循 Agent/host `0/2/1`:`retryable=false`→`max_additional_attempts=0`;`retryable=true`→`max_additional_attempts=2`;`retryable=unknown`→`max_additional_attempts=1`。它不是 CLI 持久化硬总次数上限;进程内不会自动重试,也不持久化或计算跨调用的 Agent/host 尝试次数。
- 遵守 `retry_after_seconds`/`next_retry_at`;`in_flight`、`cooldown`、`terminal_hold` 时不并发或换 `subscribe_id`/`trace_id` 绕过。 - 遵守 `retry_after_seconds`/`next_retry_at`;`in_flight`、`cooldown`、`terminal_hold` 时不并发或换 `subscribe_id`/`trace_id` 绕过。
- 认证、profile、订阅保护状态和 bus 排障按失败类型读取 [订阅运维](references/event-im-operations.md)。 - 认证、profile、订阅保护状态和 bus 排障按失败类型读取 [订阅运维](references/event-im-operations.md)。
### 本地订阅保护 ### 本地订阅保护
状态在 `~/.dws/events/open/personal_stream/<identity_hash>/personal_subscription_attempts.json`(`DWS_CONFIG_DIR` 改根);目录 `0700`,`personal_subscription_attempts.json` 与 `personal_subscription_attempts.lock` 为 `0600`。连续 `24h` 无失败后重置,`terminal_hold` 为 `1h`。紧急恢复只删除 `personal_subscription_attempts.json`,不要删除 lock 文件;这会清空该 identity 的全部保护记录。 默认记录为 `~/.dws/events/open/personal_stream/<identity_hash>/personal_subscription_attempts.json`,`DWS_CONFIG_DIR` 改变根目录。identity 目录权限 `0700`,JSON 与 `personal_subscription_attempts.lock` 为 `0600`;连续 24h 无失败后重置,`terminal_hold` 为 1h。
紧急恢复前确认没有创建进程,只删除 `personal_subscription_attempts.json`,不要删除 lock 文件;这会清空该 identity 的全部保护记录,随后仅尝试一次原始操作。详见 [订阅运维](references/event-im-operations.md)。
## 何时查询 Schema ## 何时查询 Schema
@@ -98,15 +101,16 @@ OA 七个 EventKey 使用 `ruleType=all`、`filterRule={}`,不接受目标或
## Reference ## Reference
| Topic | Reference | 何时读取 | | 何时读取 | Reference |
|---|---|---| |---|---|
| 任务索引 | [event-im.md](references/event-im.md) | 不确定加载哪个 reference | | 任务索引 | [event-im.md](references/event-im.md) |
| EventKey、目标规则与底层 consume | [event-im-keys.md](references/event-im-keys.md) | 群生命周期、显式 EventKey 或多事件组合 | | EventKey、目标规则与底层 consume | [event-im-keys.md](references/event-im-keys.md) |
| ready、bounded consume 与退出清理 | [event-im-lifecycle.md](references/event-im-lifecycle.md) | 启动/托管/关闭 consumer | | ready、bounded consume 与退出清理 | [event-im-lifecycle.md](references/event-im-lifecycle.md) |
| 扁平字段与事件到 Chat 交接 | [event-im-output.md](references/event-im-output.md) | 解析事件或自动回复 | | 扁平字段与事件到 Chat 交接 | [event-im-output.md](references/event-im-output.md) |
| Filter、status/stop、重试与排障 | [event-im-operations.md](references/event-im-operations.md) | 订阅控制或失败恢复 | | Filter、status/stop、重试与排障 | [event-im-operations.md](references/event-im-operations.md) |
| OA 审批事件 | [event-oa.md](references/event-oa.md) | 七个 OA EventKey 与审批字段解析 | | OA 审批事件 | [event-oa.md](references/event-oa.md) |
| VoIP 通话邀请事件 | [event-voip.md](references/event-voip.md) | VoIP EventKey 与邀请字段、敏感输出边界 | | VoIP 通话邀请事件 | [event-voip.md](references/event-voip.md) |
| Todo 待办事件 | [event-todo.md](references/event-todo.md) | Todo EventKey、角色范围与待办字段 | | Todo 待办事件 | [event-todo.md](references/event-todo.md) |
| 好友事件 | [event-contact.md](references/event-contact.md) | 好友 EventKey、申请与添加字段解析 | | 好友事件 | [event-contact.md](references/event-contact.md) |
| 互动卡片回调事件 | [event-card.md](references/event-card.md) | 回调订阅与 payload 解析 | | 互动卡片回调事件 | [event-card.md](references/event-card.md) |
| 日历创建/修改/取消、过滤与去重 | [event-calendar.md](references/event-calendar.md) |
@@ -0,0 +1,64 @@
# 日历个人事件
<!-- dws-intent: event.listen.calendar -->实时日程变化使用 `dws event consume` 个人事件长连接。当前事件码为
`user_calendar_event_changed`,支持主日历创建及新增参与 `eventCreated`、修改
`eventUpdated`、取消 `eventCancelled`。创建覆盖普通日程、循环主日程,以及邀请或主动
加入产生的新增参与用户;不补发历史。其他日历本和循环例外等范围以服务端实际支持为准。
查询、创建、修改日程仍使用 `dws calendar`。取消日程通知与停止事件订阅是两个操作。
## 发现与监听
```bash
dws event list --category calendar --format json
dws event schema user_calendar_event_changed --flatten --format json
dws event consume user_calendar_event_changed --calendar-ids primary --change-types eventCreated,eventUpdated,eventCancelled --flatten --duration 10m -f ndjson
```
使用目标用户的 OAuth 登录态与同一 `--profile`。等待 stderr 的 `[event] ready` 后再触发
测试日程;stdout 只有事件。时限到达或 SIGTERM 干净退出会取消本次新建订阅,复用的
`--subscribe-id` 保留;停止已有订阅先用 `event stop <subscribe_id> --dry-run` 预览。
## 过滤规则
`--calendar-ids` 和 `--change-types` 为逗号分隔的数组,规范化后由既有订阅控制面把
JSON 对象序列化为 **String 类型**的 `filterRule`:
```json
{"calendarIds":["primary"],"changeTypes":["eventCancelled","eventCreated","eventUpdated"]}
```
- 省略其中一个维度,该维度通配;全部省略下发 `{}`。通配可自然覆盖服务端后续能力。
- 显式 `--calendar-ids=` 或 `--change-types=` 表示空数组,不匹配任何消息。
- `*` 不可和具体值混用。数组按集合规范化,重复值和顺序不影响订阅请求的幂等键。
- `primary` 表示目标用户主日历;其他值必须是有查看详情权限的 OpenAPI 日历本 ID,
不要传内部 folderId 或 uid。能保存过滤范围不表示本期会生产非主日历事件。
- 具体变化类型接受 `eventCreated`、`eventUpdated`、`eventCancelled`,可单独或组合订阅。
只传 `eventCreated` 的旧订阅仍只匹配创建及新增参与;要接收修改和取消,应新建订阅并
扩展过滤或省略 `--change-types`。不要用 `eventDeleted` 代替 `eventCancelled`。
- 不接受 `--user`、`--open-dingtalk-id`、`--group`、`--role-types`、`--query` 或
`--filter-json`。过滤参数不能覆盖已有 `--subscribe-id` 的范围,也不能与其他业务
事件混用;没有业务过滤时可和其他兼容事件共享 bus。
## 扁平输出
| 字段 | 含义 |
|---|---|
| `type` | 固定为事件码 |
| `event_id` / `timestamp` | 事件中心事件 ID 和时间 |
| `subscribe_id` | 本次接收的个人事件订阅 ID |
| `biz_id` | 业务投递唯一 ID;按此幂等,重试不变 |
| `event_time` | 业务时间戳(毫秒),重试不变,不表示消息顺序 |
| `change_type` | `eventCreated`、`eventUpdated`、`eventCancelled`;保留未来类型 |
| `calendar_id` | OpenAPI 日历本 ID,`primary` 按目标用户解释 |
| `calendar_event_id` | OpenAPI 日程资源 ID;取消后可能已不可查询 |
| `operator_uid` | 可选操作人内部 uid;源载荷字符串或整数均输出为字符串,缺失时省略 |
`calendar_event_id` 不能用于投递去重:同一日程可以命中多个订阅并产生不同的
`biz_id`。消息可能重复或乱序;按资源 ID 通过已授权的日历 API 查询最新状态。
收到 `eventCancelled` 时记录该资源已取消;详情可能不存在,不把无法查询详情视为通知失败。
不要把接收人当操作人。缺失或错误的必填业务字段会输出安全的基础身份并告警,
不把原始路由 uid/orgId 回退到扁平结果。默认不加 `--flatten` 时仍保留 transport envelope。
CLI 使用现有个人事件订阅与 Stream 接口。客户端目录中的 enabled/public 只表示客户端支持,不证明服务端已部署或事件投递已验证。事件开通、接收范围及三种变化类型的端到端投递都需要在目标环境单独验收。
取消后仍可能收到在途消息;重新订阅使用平台生成的新订阅 ID,不手工复用已取消 ID。
@@ -0,0 +1,96 @@
# 听记事件
先读事件产品入口 [SKILL.md](../SKILL.md) 的命令规则、调用流和子进程契约。本参考覆盖当前公开的两个听记个人事件:创建听记、纪要生成。
<!-- dws-intent: event.listen.minutes -->实时监听听记创建或纪要生成必须使用 `dws event consume` 长连接,不要轮询听记列表或反复读取纪要来模拟事件。
## Prerequisite
听记个人事件使用当前用户 OAuth 登录态。未登录或 token 失效时,先执行:
```bash
dws auth login
```
非默认组织使用全局 `--profile <corpId 或 profile 名>`。事件范围由服务端按订阅和读取权限决定,不需要也不接受目标用户、群、角色或消息过滤参数。
## Event catalog
| 事件码 | 订阅规则 | 接收语义 | 必填参数 |
|---|---|---|---|
| `user_minutes_task_created` | `all` | 听记对象、必要关联及默认权限初始化成功;创建失败、仅受理请求、复用已有听记不通知 | 无 |
| `user_minutes_summary_generated` | `all` | 默认纪要首次生成成功、保存完成且可读取;成功后重新生成、切换模板、人工修改不通知 | 无 |
只承认上表 2 个听记事件码。CLI 为每个事件发送 `ruleType=all`、`filterRule={}` 的独立订阅请求;不要添加 `--user`、`--open-dingtalk-id`、`--group`、`--query`、`--role-types` 或 `--filter-json`。
## Intent mapping
| 用户说 | 下一步 |
|---|---|
| “新建听记时通知我” | `dws event consume user_minutes_task_created --flatten -f ndjson` |
| “纪要生成好了通知我” / “会议纪要出来后处理” | `dws event consume user_minutes_summary_generated --flatten -f ndjson` |
| “同时监听全部听记事件” | 一个 consume 放入两个听记 event key,不加目标或过滤参数 |
| “查看听记事件目录” | `dws event list --category minutes` |
| “查看听记事件输出字段” | 对对应事件运行 `dws event schema <event_key> --flatten` |
## Commands
查看稳定的扁平输出 schema:
```bash
dws event schema user_minutes_task_created --flatten
dws event schema user_minutes_summary_generated --flatten
```
单独监听一种事件:
```bash
dws event consume user_minutes_task_created --flatten -f ndjson
dws event consume user_minutes_summary_generated --flatten --duration 10m -f ndjson
```
同时监听两种事件:
```bash
dws event consume \
user_minutes_task_created \
user_minutes_summary_generated \
--flatten \
-f ndjson
```
多事件 consume 会为两个 event key 分别创建订阅和逻辑 consumer,并共享当前组织的 personal bus、远程连接、stdout 和生命周期。
## Output contract
`--flatten` 模式的两个听记事件字段相同:
```json
{
"type": "user_minutes_summary_generated",
"event_id": "...",
"timestamp": 0,
"subscribe_id": "...",
"biz_id": "...",
"event_time": 0,
"schema_version": 1,
"task_uuid": "...",
"creator_open_dingtalk_id": "10001"
}
```
- `type`、`event_id`、`timestamp`、`subscribe_id` 是平台传输元数据;`event_id` 不假定等于 `biz_id`。
- `biz_id`、`event_time` 由生产方生成,同一次业务事件重试保持不变,消费端需按 `biz_id` 幂等处理。事件可能重复或乱序,不要等待创建事件才处理纪要事件。
- `schema_version` 首版为 `1`;`task_uuid` 是听记稳定标识。
- `creator_open_dingtalk_id` 是创建者标识:正 int64 转为十进制字符串以保持精度,非空字符串原样保留,缺失、null、0、负数、布尔值视为非法;它不一定是当前接收人,与内部 uid 的映射以平台确认结果为准。
- 事件只携带标识,不含听记或纪要正文。创建事件不表示纪要已就绪;需要内容时用同一 `--profile` 执行 `dws minutes get info --id <task_uuid>` 或 `dws minutes get summary --id <task_uuid>`。CLI 不自动读取内容或执行业务动作。
- payload 缺失、为空、无法解析,或 `schemaVersion` 不为 1、`taskUuid` 为空、`creatorOpenDingtalkId` 非法时,consume 会在 stderr 记录 warning(同一进程只提示一次),并把原始 transport envelope 写到 stdout,保证事件不被静默丢弃。
- 不传 `--flatten` 时保持兼容 transport envelope,业务 payload 位于 `.data | fromjson`。
## Lifecycle
- 单事件等待 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>`。
- 两事件先保存两条 `[event] subscription event_key=<key> subscribe_id=<id>`,再等待 `[event] ready event_count=2 bus_pid=<pid>`。
- 不补推订阅前的历史事件;取消后停止新投递,已进入下游的消息仍可能到达。
- 临时验证使用 `--max-events 1` 或 `--duration 10m`;任务完成后优雅结束 consume,本次新建的订阅会自动取消。
- 外部停止已有订阅时先运行 `dws event stop <subscribe_id> --dry-run`,确认后再加 `--yes`。不要 `kill -9`,否则会跳过自动退订。
@@ -1,6 +1,6 @@
--- ---
name: dingtalk-minutes name: dingtalk-minutes
description: 钉钉 AI 听记。Use when 查询或修改听记摘要、完整逐字稿、关键词、标签、行动项、录音、上传、听记内置思维导图、发言人洞察、ASR 热词/识别词配置或分享权限。独立 .amind 脑图文件的创建和主题读写走 dingtalk-misc 的 mind reference;写文档走 dingtalk-doc;建待办走 dingtalk-todo;日程走 dingtalk-calendar。命令前缀:dws minutes。 description: 钉钉 AI 听记。Use when 查询或修改听记摘要、完整逐字稿、关键词、标签、纪要模板及默认模板、行动项、录音、上传、听记内置思维导图、发言人洞察、ASR 热词/识别词配置或分享权限。独立 .amind 脑图文件的创建和主题读写走 dingtalk-misc 的 mind reference;写文档走 dingtalk-doc;建待办走 dingtalk-todo;日程走 dingtalk-calendar。命令前缀:dws minutes。
metadata: metadata:
cli_version: ">=0.2.14" cli_version: ">=0.2.14"
category: product category: product
@@ -42,6 +42,8 @@ metadata:
| 按标题或时间找听记 | `dws minutes +search --scope all --query "<关键词>" --page-all` | `scope` 可选 `mine/shared/all`;至少提供 query/start/end 之一。`all --page-all` 分别追完 mine/shared 后去重,不把单个 noLimit 端点当完整全集 | | 按标题或时间找听记 | `dws minutes +search --scope all --query "<关键词>" --page-all` | `scope` 可选 `mine/shared/all`;至少提供 query/start/end 之一。`all --page-all` 分别追完 mine/shared 后去重,不把单个 noLimit 端点当完整全集 |
| 浏览我创建、共享给我或全部可访问听记 | `+list-mine` / `+list-shared` / `+list-all` | 默认是可续拉预览;要声称完整必须加 `--page-all` 并检查 `complete=true`。用户只说“我的听记”不等于明确 `mine`,范围不清时用 `all` | | 浏览我创建、共享给我或全部可访问听记 | `+list-mine` / `+list-shared` / `+list-all` | 默认是可续拉预览;要声称完整必须加 `--page-all` 并检查 `complete=true`。用户只说“我的听记”不等于明确 `mine`,范围不清时用 `all` |
| 按真实标签找听记 | `dws minutes tag list --format json`,再 `tag query --tag-id <真实tagId>` | 标签不等于关键词;空标签即结束。按标签查找的分页细节见局部意图 | | 按真实标签找听记 | `dws minutes tag list --format json`,再 `tag query --tag-id <真实tagId>` | 标签不等于关键词;空标签即结束。按标签查找的分页细节见局部意图 |
| 创建或删除自定义标签,为听记添加或移除标签 | `tag create` / `tag delete` / `tag add` / `tag remove` | 前缀为 `dws minutes`;先锁定真实标签和听记。创建定义不自动关联,删除定义与移除关联含义不同,详见局部意图 |
| 查询系统或个人纪要模板,维护个人模板及默认模板 | `template list-system` / `template list-custom` / `template create` / `template update` / `template delete` / `template set-default` | 前缀为 `dws minutes`;系统 templateId 与个人 templateKey 分开,变更不重新生成已有听记,详见局部意图 |
| 看我最新创建的一条 | `dws minutes +latest [--keyword <关键词>]` | 只在用户明确说“最新”时用;不能用它替代具名目标搜索,也不能在录音 start 后拿 latest 猜新录音 ID | | 看我最新创建的一条 | `dws minutes +latest [--keyword <关键词>]` | 只在用户明确说“最新”时用;不能用它替代具名目标搜索,也不能在录音 start 后拿 latest 猜新录音 ID |
| 读取基础信息、摘要或关键词 | `dws minutes +detail --id <taskUuid> --artifacts basic,summary,keywords` | 已有 taskUuid 且只读取现有产物时直接使用,不要进入上传 workflow;任一产物失败都按 partial/非零处理,不把缺失项说成空内容 | | 读取基础信息、摘要或关键词 | `dws minutes +detail --id <taskUuid> --artifacts basic,summary,keywords` | 已有 taskUuid 且只读取现有产物时直接使用,不要进入上传 workflow;任一产物失败都按 partial/非零处理,不把缺失项说成空内容 |
| 读取逐字稿 | `dws minutes +transcript --id <taskUuid> [--direction 1] [--single-page]` | 已有 taskUuid 且只读取逐字稿时直接使用,不要借 `+upload-and-analyze --resume-id` 代读。默认正序并追完分页;倒序必须传 `--direction 1`,用户明确只要第一页时传 `--single-page`,不得随后自动续页。交付前检查 `data.direction/data.complete/data.pages` 与 `meta.pagination` | | 读取逐字稿 | `dws minutes +transcript --id <taskUuid> [--direction 1] [--single-page]` | 已有 taskUuid 且只读取逐字稿时直接使用,不要借 `+upload-and-analyze --resume-id` 代读。默认正序并追完分页;倒序必须传 `--direction 1`,用户明确只要第一页时传 `--single-page`,不得随后自动续页。交付前检查 `data.direction/data.complete/data.pages` 与 `meta.pagination` |
@@ -106,7 +108,7 @@ Golden Route 参数足够时直接执行,不预读 Reference。每个 Case 最
| 触发条件 | Reference | | 触发条件 | Reference |
|---|---| |---|---|
| ASR 热词、上传会话恢复、复杂异步轮询、批量权限 workflow | [复杂流程](references/07-minutes.md) | | ASR 热词、上传会话恢复、复杂异步轮询、批量权限 workflow | [复杂流程](references/07-minutes.md) |
| 发言人替换、批量文本替换、下载媒体、离线导出等低频意图 | [局部意图](references/intent-guide.md) | | 纪要模板、标签维护、发言人替换、批量文本替换、下载媒体、离线导出等低频意图 | [局部意图](references/intent-guide.md) |
| 必须落到原子命令,或需要 URL/参数/确认事实 | [原子命令](references/minutes.md) | | 必须落到原子命令,或需要 URL/参数/确认事实 | [原子命令](references/minutes.md) |
## 错误最短路径 ## 错误最短路径
@@ -15,6 +15,11 @@
| “把摘要、关键词、完整逐字稿、待办归档成一包” | `dws minutes +export-pack --id <taskUuid> --output <新目录>` | 逐字稿必须完整;所有必需产物验证后才原子发布目录;目标目录已存在时拒绝覆盖 | | “把摘要、关键词、完整逐字稿、待办归档成一包” | `dws minutes +export-pack --id <taskUuid> --output <新目录>` | 逐字稿必须完整;所有必需产物验证后才原子发布目录;目标目录已存在时拒绝覆盖 |
| “归档时也带媒体” | `dws minutes +export-pack --id <taskUuid> --output <新相对目录> --include-media --format json` | manifest 不保存签名 URL;媒体未就绪导致归档不完整时必须明确失败 | | “归档时也带媒体” | `dws minutes +export-pack --id <taskUuid> --output <新相对目录> --include-media --format json` | manifest 不保存签名 URL;媒体未就绪导致归档不完整时必须明确失败 |
| “按标签找听记” | `dws minutes tag list --format json`,再用真实 `tagId` 执行 `dws minutes tag query --tag-id <tagId> --limit 10 --format json` | 不按标签名猜 ID;空标签直接结束,有 `nextToken` 时续拉或明确结果不完整 | | “按标签找听记” | `dws minutes tag list --format json`,再用真实 `tagId` 执行 `dws minutes tag query --tag-id <tagId> --limit 10 --format json` | 不按标签名猜 ID;空标签直接结束,有 `nextToken` 时续拉或明确结果不完整 |
| “新建或删除一个听记标签” | `dws minutes tag create --display-name "<名称>"` / `dws minutes tag delete --tag-id <真实tagId>` | 创建定义不自动关联听记;删除定义会异步清理所有听记关联,须锁定本人自定义标签 |
| “给这条听记加标签或移除标签” | `dws minutes tag add --id <taskUuid> --tag-ids <真实tagIds>` / `dws minutes tag remove --id <taskUuid> --tag-ids <真实tagIds>` | 增量操作单条听记,原始输入1–20项;失败后保留已成功项并核对未知写结果 |
| “看看可用纪要模板” | `dws minutes template list-system --format json` / `dws minutes template list-custom --format json` | 系统 templateId 与个人 templateKey 分开;空页仍按 meta.pagination 判断是否续页 |
| “创建、修改或删除我的纪要模板” | `template create` / `template update` / `template delete`(前缀 `dws minutes`) | 用户确认标题和提示词后创建;update/delete使用真实 templateKey,不重新生成已有听记 |
| “以后听记默认用这个模板” | `dws minutes template set-default --template-id <真实id>` 或 `--template-key <真实key>` | 必须二选一,不用空串占位;只影响后续听记 |
| “查语音备忘” | `dws minutes audio-memo list --format json` | 属于独立原子查询;需要时间范围或分页参数时读取该 compact leaf Schema | | “查语音备忘” | `dws minutes audio-memo list --format json` | 属于独立原子查询;需要时间范围或分页参数时读取该 compact leaf Schema |
需要根据逐字稿总结指定发言人的内容时读取 [发言人匹配流程](10-minutes-speaker-match.md);用户已经确认“匿名发言人 → 姓名”的对应关系、准备执行标注替换时读取 [发言人纠正流程](11-minutes-speaker-correct.md)。两者都不能凭职位、语言风格或列表顺序自动认定身份。 需要根据逐字稿总结指定发言人的内容时读取 [发言人匹配流程](10-minutes-speaker-match.md);用户已经确认“匿名发言人 → 姓名”的对应关系、准备执行标注替换时读取 [发言人纠正流程](11-minutes-speaker-correct.md)。两者都不能凭职位、语言风格或列表顺序自动认定身份。
@@ -42,6 +47,26 @@ dws minutes tag query --tag-id <tagId> --limit 10 --cursor <nextToken> --format
- 不按标签名称猜 `tagId`;`tag list` 明确返回空数组时直接交付“当前无标签”,不再查 Help 或拿其他分组补位。 - 不按标签名称猜 `tagId`;`tag list` 明确返回空数组时直接交付“当前无标签”,不再查 Help 或拿其他分组补位。
- `tag query` 是单页原子查询;返回真实 `nextToken` 时继续续拉,或明确说明当前结果不完整。 - `tag query` 是单页原子查询;返回真实 `nextToken` 时继续续拉,或明确说明当前结果不完整。
## 标签维护与纪要模板
```text
dws minutes tag create --display-name "项目复盘" --format json
dws minutes tag add --id <taskUuid> --tag-ids <真实tagId> --format json
dws minutes tag remove --id <taskUuid> --tag-ids <真实tagId> --format json
dws minutes template list-system --locale zh_CN --format json
dws minutes template list-custom --format json
dws minutes template create --title "项目复盘" --prompt "总结结论、风险和行动项" --format json
dws minutes template update --template-key <真实key> --title "项目复盘纪要" --format json
dws minutes template set-default --template-key <真实key> --format json
```
- 写前按最终 leaf Schema 和 Runtime gate 确认,不为了绕过确认改用其他入口。
- 标签 create 返回的 tagId 可直接用于后续关联;名称最多50个UTF-16单元。delete 删除定义,remove 仅移除单条听记关联,两者不可互换;删除标签不撤回已同步知识库文件。
- 关联批量返回首个失败后的未处理项。`FAILED` 是终态未知,先按 tag query 等读取入口核对;`NOT_PROCESSED` 明确未执行。统一输出 partial_failure 仍为非零,不把成功项遗漏或自动重放。
- 模板列表每次一页,即使 items 为空仍按 meta.pagination 续页,游标原样透传;系统模板续页保持 locale 不变。个人模板版本0有效。
- 创建需 title、prompt;更新需 template-key 且至少显式提供 title/prompt/brief-intro 中一项。prompt的空格和换行保持原样,空简介不清空原值。系统 templateId 不可当个人 templateKey 使用。
- 默认模板二选一;删除个人模板不清除默认设置,`wasDefault=null` 是未知而非false。模板变更不重新生成已有听记;写结果不确定时先 list-custom 对账。
## 逐字稿与行动项落盘 ## 逐字稿与行动项落盘
```text ```text
@@ -11,7 +11,7 @@
| 任务 | Reference | | 任务 | Reference |
|---|---| |---|---|
| ASR、上传恢复、异步生成、录音收尾、批量权限 | [07-minutes.md](07-minutes.md) | | ASR、上传恢复、异步生成、录音收尾、批量权限 | [07-minutes.md](07-minutes.md) |
| 发言人/文本替换、媒体下载、离线导出、标签等低频意图 | [intent-guide.md](intent-guide.md) | | 纪要模板、发言人/文本替换、媒体下载、离线导出、标签等低频意图 | [intent-guide.md](intent-guide.md) |
| 总结指定发言人的内容并处理匿名发言人候选 | [10-minutes-speaker-match.md](10-minutes-speaker-match.md) | | 总结指定发言人的内容并处理匿名发言人候选 | [10-minutes-speaker-match.md](10-minutes-speaker-match.md) |
| 用户确认对应关系后的发言人标注与替换 | [11-minutes-speaker-correct.md](11-minutes-speaker-correct.md) | | 用户确认对应关系后的发言人标注与替换 | [11-minutes-speaker-correct.md](11-minutes-speaker-correct.md) |
| 兼容旧调用方式的轻量 Recipe 索引 | [lite-recipes.md](lite-recipes.md) | | 兼容旧调用方式的轻量 Recipe 索引 | [lite-recipes.md](lite-recipes.md) |
@@ -212,15 +212,34 @@ dws minutes permission apply --id <taskUuid> --policy 4
当前没有公开权限读取接口,因此权限写入成功最多证明服务端接受了这次写调用。交付边界按 `verification.mode=write_ack_only`、`verified=false` 表述;这两个值描述证据等级,不代表 Runtime 一定已经返回同名字段。`+share/+unshare` 的逐成员 `complete` ledger 仍只是写入回执,不能声称已读回最终权限状态。 当前没有公开权限读取接口,因此权限写入成功最多证明服务端接受了这次写调用。交付边界按 `verification.mode=write_ack_only`、`verified=false` 表述;这两个值描述证据等级,不代表 Runtime 一定已经返回同名字段。`+share/+unshare` 的逐成员 `complete` ledger 仍只是写入回执,不能声称已读回最终权限状态。
## 9. 标签与语音备忘 ## 9. 纪要模板、标签与语音备忘
以下均为原子命令,前缀为 `dws`;写入前确认用户意图和唯一目标,并遵循 leaf Schema 的 Runtime confirmation。
| 模板命令 | 用途与参数 |
|---|---|
| `minutes template list-system [--locale zh_CN] [--limit 20] [--cursor <token>]` | 查询系统模板目录,每页1–50条,返回 templateId、标题和简介 |
| `minutes template list-custom [--limit 5] [--cursor <token>]` | 查询当前用户个人模板,每页1–10条,返回 templateKey、完整提示词及版本 |
| `minutes template create --title "<标题>" --prompt "<提示词>" [--brief-intro "<简介>"]` | 创建个人模板;提示词保留空格和换行,不自动设为默认 |
| `minutes template update --template-key <key> [--title "<标题>"] [--prompt "<提示词>"] [--brief-intro "<简介>"]` | 至少显式提供一个更新字段;省略保留旧值,空简介也保留旧简介 |
| `minutes template delete --template-key <key>` | 幂等删除本人个人模板;不删除已有听记,也不清除默认模板设置 |
| `minutes template set-default --template-id <id>` 或 `--template-key <key>` | 官方与个人模板二选一,不传空串,官方ID不能填 diy;可选 --locale |
模板列表每次只返回一页;依据 `meta.pagination.endpoint_exhausted` 判断续页,用 `meta.pagination.next_token` 原样传入 `--cursor`。即使 items 为空也可能有下一页;系统模板续页保持相同 locale。标题非空白且最多150个UTF-16单元,提示词非空白并原样传递,最大长度由服务端校验。创建结果不确定时先 list-custom 核对,不自动重复创建;`version=0` 有效。删除结果 `wasDefault=null` 表示原默认状态未知;设置官方默认时 `templateKey=null` 有效。模板维护和默认设置只影响后续听记,不修改已有听记内容。
| 原子命令 | 用途 | | 原子命令 | 用途 |
|---|---| |---|---|
| `minutes tag list` | 列出当前用户的听记标签,取得真实 tagId | | `minutes tag list` | 列出当前用户的听记标签,取得真实 tagId |
| `minutes tag query --tag-id <tagId>` | 查询标签下的听记并按真实 token 翻页 | | `minutes tag query --tag-id <tagId>` | 查询标签下的听记并按真实 token 翻页 |
| `minutes tag create --display-name "<名称>"` | 创建本人自定义标签,名称非空白、最多50个UTF-16单元;返回真实 tagId,不自动关联听记 |
| `minutes tag delete --tag-id <tagId>` | 删除本人标签定义并异步清理全部关联;不撤回已同步的知识库文件 |
| `minutes tag add --id <taskUuid> --tag-ids <tagId1,tagId2> [--locale zh]` | 增量添加本人自定义标签,保留已有标签,可能触发已配置的知识库同步 |
| `minutes tag remove --id <taskUuid> --tag-ids <tagId1,tagId2>` | 只移除这一条听记的标签关联,不删除标签定义 |
| `minutes audio-memo list` | 查询语音备忘;具体范围和分页参数读取 leaf Schema | | `minutes audio-memo list` | 查询语音备忘;具体范围和分页参数读取 leaf Schema |
这些是长尾只读入口,不在根 Skill 展开。tagId 必须来自真实 `tag list`,不能按标签名猜 ID。 tagId 必须来自真实 `tag list` 或 `tag create`,不能按标签名猜 ID。关联操作每次原始输入1–20项,首尾去空白后按首次出现顺序去重;添加后关联总数最多20个,只能操作本人自定义标签。创建同名推荐标签可能先删除原推荐标签,失败不恢复;系统或推荐标签不能通过 delete 删除。
批量关联在首个失败处停止,已成功项不回滚。`APPLIED/UNCHANGED` 是已确认成功,`FAILED` 表示写结果未确认,`NOT_PROCESSED` 表示未执行;按实际统一输出的 succeeded/failed/unknown 逐项处理。有成功项且仍有失败或未知项时返回 partial_failure;无成功项返回 failure。保留回执,先查询实际状态,禁止重放整批或把外层成功解释为全部完成。
## 10. 结果与错误底线 ## 10. 结果与错误底线
@@ -33,6 +33,7 @@ Attendance 任务直接按产品索引读取一份最匹配的 `attendance*.md`
| 直播 / 我的直播 / 直播列表 | 直播列表与直播记录查询 | `dws live` | [live.md](references/live.md) | | 直播 / 我的直播 / 直播列表 | 直播列表与直播记录查询 | `dws live` | [live.md](references/live.md) |
| DING / 紧急通知 / 电话DING / 短信DING / 必达消息 | DING 紧急消息(应用内/短信/电话),个人DING | `dws ding` | [ding.md](references/ding.md) | | DING / 紧急通知 / 电话DING / 短信DING / 必达消息 | DING 紧急消息(应用内/短信/电话),个人DING | `dws ding` | [ding.md](references/ding.md) |
| 开放平台应用 / 企业内部应用 / 应用状态或删除条件 / 应用管理成员 / 应用权限 / 应用版本或版本历史 / agentId / clientId / 应用机器人配置 / 机器人消息模式或在线状态 / 本机机器人连接器 / Stream连接 / 版本发布 / connect / MCP 服务开发 / MCP 工具发布 / MCP 凭证 / OpenAPI或Swagger或Postman或curl转MCP / 接口给Agent用 / 已发布MCP调用 / MCP市场搜索 | 开放平台企业内部应用,以及 MCP 服务、工具、鉴权、凭证、协作者、发布后动态调用和市场搜索 | `dws dev` / `dws dev mcp` / `dws mcp published` / `dws mcp market search` | 按下方「Dev 直达规则」读取一个 `references/dev/*.md`;意图不明时才读 [dev-index.md](references/dev/dev-index.md) | | 开放平台应用 / 企业内部应用 / 应用状态或删除条件 / 应用管理成员 / 应用权限 / 应用版本或版本历史 / agentId / clientId / 应用机器人配置 / 机器人消息模式或在线状态 / 本机机器人连接器 / Stream连接 / 版本发布 / connect / MCP 服务开发 / MCP 工具发布 / MCP 凭证 / OpenAPI或Swagger或Postman或curl转MCP / 接口给Agent用 / 已发布MCP调用 / MCP市场搜索 | 开放平台企业内部应用,以及 MCP 服务、工具、鉴权、凭证、协作者、发布后动态调用和市场搜索 | `dws dev` / `dws dev mcp` / `dws mcp published` / `dws mcp market search` | 按下方「Dev 直达规则」读取一个 `references/dev/*.md`;意图不明时才读 [dev-index.md](references/dev/dev-index.md) |
| 轻应用(不要与“快捷应用”混同)/ 创建即发布挂工作台 / 轻应用凭证 appKey secret / 轻应用配额软删 | 独立轻应用全生命周期:创建即发布并注册统一应用,无版本流、24h 软删 | `dws dev liteapp` | [liteapp.md](references/dev/liteapp.md) |
| DEAP 数字员工 / 数字员工 / 人设 / 群感知 / DSH 接入 | 兼容路由;完整自然语言编排只由独立 `dingtalk-tag` skill 维护 | `dws dingtalk-tag` | 切换 [`dingtalk-tag`](../dingtalk-tag/SKILL.md) | | DEAP 数字员工 / 数字员工 / 人设 / 群感知 / DSH 接入 | 兼容路由;完整自然语言编排只由独立 `dingtalk-tag` skill 维护 | `dws dingtalk-tag` | 切换 [`dingtalk-tag`](../dingtalk-tag/SKILL.md) |
| 目标管理 / 战略解码 / 经营合约字段 / 计分卡 / OKR / 目标规则周期 / 个人目标 / 目标模板 / 周月报规则统计 / 按时迟交未提交 / 跟催 | Agoal 目标管理与经营目标跟进 | `dws agoal` | [agoal.md](references/agoal.md) | | 目标管理 / 战略解码 / 经营合约字段 / 计分卡 / OKR / 目标规则周期 / 个人目标 / 目标模板 / 周月报规则统计 / 按时迟交未提交 / 跟催 | Agoal 目标管理与经营目标跟进 | `dws agoal` | [agoal.md](references/agoal.md) |
| 日报 / 周报 / 月报 / 写日志 / 收件箱日志 / 发件箱日志 | 日志(日报/周报/月报)查询与按模版提交 | `dws report`(别名 `dws log`) | [report.md](references/report.md) | | 日报 / 周报 / 月报 / 写日志 / 收件箱日志 / 发件箱日志 | 日志(日报/周报/月报)查询与按模版提交 | `dws report`(别名 `dws log`) | [report.md](references/report.md) |
@@ -61,9 +62,9 @@ Attendance 任务直接按产品索引读取一份最匹配的 `attendance*.md`
- 产品自己的局部意图消歧文档命名为 `references/<product>-intent-guide.md`,不是共享的 `references/intent-guide.md`。 - 产品自己的局部意图消歧文档命名为 `references/<product>-intent-guide.md`,不是共享的 `references/intent-guide.md`。
- 各产品之间跨产品协作若指向本包内的其它产品,已在对应 `references/<product>.md` 里写成"见本包 references/X.md",无需切换 skill;若指向 top10 独立产品(如 `chat`/`aisearch`/`doc`),仍按 `dingtalk-<product>` 切换 skill。 - 各产品之间跨产品协作若指向本包内的其它产品,已在对应 `references/<product>.md` 里写成"见本包 references/X.md",无需切换 skill;若指向 top10 独立产品(如 `chat`/`aisearch`/`doc`),仍按 `dingtalk-<product>` 切换 skill。
- `scripts/` 下 yida / finance / `aiapp_create_and_poll.py` 等见 [unsupported-scripts.md](references/unsupported-scripts.md);默认不要当正式能力调用。 - `scripts/` 下 yida / finance / `aiapp_create_and_poll.py` 等见 [unsupported-scripts.md](references/unsupported-scripts.md);默认不要当正式能力调用。
- **Dev 直达规则**:应用状态、增删改查、能否删除→[app.md](references/dev/app.md);凭证→[credentials.md](references/dev/credentials.md);开放平台应用的管理成员/角色汇总→[member.md](references/dev/member.md),不是 Chat 群成员;权限→[permission.md](references/dev/permission.md);安全配置→[security.md](references/dev/security.md);网页应用→[webapp.md](references/dev/webapp.md);机器人线上配置→[robot.md](references/dev/robot.md);版本历史/待发布版本/发布→[version.md](references/dev/version.md);事件订阅→[event.md](references/dev/event.md);本机机器人连接器/Stream连接→[connect.md](references/dev/connect.md);MCP→先读[mcp.md](references/dev/mcp.md),仅在进入对应阶段时再增量读取其 API 转换、映射、鉴权、表达式或故障定位子 reference,禁止一次性预读全集;明确跨两个以上 Dev 域→只先读[recipes.md](references/dev/recipes.md)。单域任务只读一份;跨域也不要预读各专题,仅当真实响应出现本页未覆盖的特殊状态或字段时补读一份。无需先读 `dingtalk-shared`、`devapp.md` 或索引,禁止重复读取 Reference。 - **Dev 直达规则**:普通统一应用状态、增删改查、能否删除→[app.md](references/dev/app.md);凭证→[credentials.md](references/dev/credentials.md);轻应用→[liteapp.md](references/dev/liteapp.md);开放平台应用的管理成员/角色汇总→[member.md](references/dev/member.md),不是 Chat 群成员;权限→[permission.md](references/dev/permission.md);安全配置→[security.md](references/dev/security.md);网页应用→[webapp.md](references/dev/webapp.md);机器人线上配置→[robot.md](references/dev/robot.md);版本历史/待发布版本/发布→[version.md](references/dev/version.md);事件订阅→[event.md](references/dev/event.md);本机机器人连接器/Stream连接→[connect.md](references/dev/connect.md);MCP→先读[mcp.md](references/dev/mcp.md),仅在进入对应阶段时再增量读取其 API 转换、映射、鉴权、表达式或故障定位子 reference,禁止一次性预读全集;明确跨两个以上 Dev 域→只先读[recipes.md](references/dev/recipes.md)。单域任务只读一份;跨域也不要预读各专题,仅当真实响应出现本页未覆盖的特殊状态或字段时补读一份。无需先读 `dingtalk-shared`、`devapp.md` 或索引,禁止重复读取 Reference。
- **Dev 命令纪律**:默认使用可审计的 `dws dev ...` 原子路径,不为发现 shortcut 调用 `dws dev help`、子组 help 或 `dws devapp +...`。应用按名称定位只有 `dws dev app list --name`,没有 `dws dev app search`;现有应用的机器人名称、模式、状态用 `dws dev app robot get`,不走 `dws chat bot`;连接器是 `dws dev connect ...`,不在 `dws dev app` 下。已知 leaf 直接执行;只有当前参数确有不确定性时才读取一次该 leaf 的 compact Schema,Schema 不可用时才调用一次精确 leaf `--help`。不要用 group help 探路。 - **Dev 命令纪律**:默认使用可审计的 `dws dev ...` 原子路径,不为发现 shortcut 调用 `dws dev help`、子组 help 或 `dws devapp +...`。应用按名称定位只有 `dws dev app list --name`,没有 `dws dev app search`;现有应用的机器人名称、模式、状态用 `dws dev app robot get`,不走 `dws chat bot`;连接器是 `dws dev connect ...`,不在 `dws dev app` 下。已知 leaf 直接执行;参数不确定时只查一次该 leaf 的 compact Schema,Schema 不可用时才调用一次精确 leaf `--help`。不要用 group help 探路。
- **Dev JSON 与分页**:所有命令带 `--format json`;`connect list/status` 还必须带它们自己的 `--json`,否则实际输出是表格。`app list`、`permission list`、`event list`、`version list` 只接受游标分页:读取返回的 `meta.pagination.endpoint_exhausted/next_token`,续页传 `--cursor <next_token>`;禁止猜 `--page`、`--page-num`,也不要从 `data` 中找旧版 `hasMore/nextCursor`。 - **Dev JSON 与分页**:所有命令带 `--format json`;`connect list/status` 还必须带它们自己的 `--json`,否则实际输出是表格。标准入口优先使用 `dws dev app ...`;`dws devapp +<shortcut>` 仅作兼容或用户明确指定的入口。`app list`、`permission list`、`event list`、`version list` 只接受游标分页:读取返回的 `meta.pagination.endpoint_exhausted/next_token`,续页传 `--cursor <next_token>`;旧版 `hasMore/nextCursor` 仅作内部映射,不是面向用户的字段。禁止猜 `--page`、`--page-num`,也不要从 `data` 中找旧版字段。
- **Dev 完成纪律**:执行前记下用户要求的每个交付项;按依赖顺序执行,删除/停用等清理放到最后。用户最初请求写操作只授权 dry-run,不是看过预检后的正式确认。必须先展示 dry-run 的准确对象、动作、业务参数和影响,再取得用户对该预览的明确确认;随后只把同一命令的 `--dry-run` 换成 `--yes`,目标或业务参数有任何变化都重新 dry-run、展示并确认。确认前不得发出非 dry-run 写调用;`--dry-run` 不是完成。写后只做一次必要回读。相同业务错误且参数、状态均未改变时不要重试;仅在新证据实际改变命令、参数或状态后重试一次。最终逐项回答,空列表也明确写“暂无”,不要用大段原始 JSON 挤掉其它结果。 - **Dev 完成纪律**:执行前记下用户要求的每个交付项;按依赖顺序执行,删除/停用等清理放到最后。用户最初请求写操作只授权 dry-run,不是看过预检后的正式确认。必须先展示 dry-run 的准确对象、动作、业务参数和影响,再取得用户对该预览的明确确认;随后只把同一命令的 `--dry-run` 换成 `--yes`,目标或业务参数有任何变化都重新 dry-run、展示并确认。确认前不得发出非 dry-run 写调用;`--dry-run` 不是完成。写后只做一次必要回读。相同业务错误且参数、状态均未改变时不要重试;仅在新证据实际改变命令、参数或状态后重试一次。最终逐项回答,空列表也明确写“暂无”,不要用大段原始 JSON 挤掉其它结果。
- **Devapp 跨域快路径**:创建/清理应用加机器人、网页、成员等能力,首读 `dev/recipes.md` 即可,不再预读 app 或各专题。缺参才补精确段落;新建失败不能借用其它应用。确认后的续轮直接执行已确认且未变化的命令,完成必要回读后,同轮继续下一项 dry-run 并询问该项确认,不另问“是否继续”。复用 ID、回执和已读 reference;目标/参数变化仍重新预检确认。明确 STREAM 要传 `--mode STREAM`,不建连接就不读 connect;明确不发布就不进入发布链。 - **Devapp 跨域快路径**:创建/清理应用加机器人、网页、成员等能力,首读 `dev/recipes.md` 即可,不再预读 app 或各专题。缺参才补精确段落;新建失败不能借用其它应用。确认后的续轮直接执行已确认且未变化的命令,完成必要回读后,同轮继续下一项 dry-run 并询问该项确认,不另问“是否继续”。复用 ID、回执和已读 reference;目标/参数变化仍重新预检确认。明确 STREAM 要传 `--mode STREAM`,不建连接就不读 connect;明确不发布就不进入发布链。
- **DevDoc 搜索预算**:明确的开发文档任务直接读 [devdoc.md](references/devdoc.md),无需冷启动预读 shared 或 Devapp。每个主题先原短语搜索;无结果或全不相关时,普通查找至多一次有依据的改写,再报告未命中。指定查询词和页数时不擅自替换或缩减;有返回不等于找到相关资料,禁止连续近义词或跨产品搜索碰运气。 - **DevDoc 搜索预算**:明确的开发文档任务直接读 [devdoc.md](references/devdoc.md),无需冷启动预读 shared 或 Devapp。每个主题先原短语搜索;无结果或全不相关时,普通查找至多一次有依据的改写,再报告未命中。指定查询词和页数时不擅自替换或缩减;有返回不等于找到相关资料,禁止连续近义词或跨产品搜索碰运气。
@@ -1,6 +1,8 @@
# Dev 能力索引(只在意图不明时读取) # Dev 能力索引(只在意图不明时读取)
意图清楚时不要读本索引,直接从根 Skill 进入唯一专题 Reference。默认走 `dws dev` 原子命令,不先读 `devapp.md`,也不调用 help 发现 shortcut。 意图清楚时不要读本索引,直接从根 Skill 进入唯一专题 Reference。默认走 `dws dev` 原子命令,
不先读 `devapp.md`,也不调用 help 发现 shortcut;已知 leaf 直接执行,参数不确定时只查该
leaf 的 compact Schema。
| 用户意图 | 标准命令路径 | 只读此文件 | | 用户意图 | 标准命令路径 | 只读此文件 |
|---|---|---| |---|---|---|
@@ -16,6 +18,7 @@
| 应用事件订阅 | `dws dev app event ...` | [`event.md`](./event.md) | | 应用事件订阅 | `dws dev app event ...` | [`event.md`](./event.md) |
| 本机机器人连接器、Stream/长连接实例与健康状态 | `dws dev connect ...` | [`connect.md`](./connect.md) | | 本机机器人连接器、Stream/长连接实例与健康状态 | `dws dev connect ...` | [`connect.md`](./connect.md) |
| MCP 服务、工具、鉴权、凭证与协作者 | `dws dev mcp ...` | [`mcp.md`](./mcp.md) | | MCP 服务、工具、鉴权、凭证与协作者 | `dws dev mcp ...` | [`mcp.md`](./mcp.md) |
| 轻应用创建即发布、更新、删除、列表、详情、appKey/secret 凭证、配额与软删(独立于普通统一应用) | `dws dev liteapp ...` | [`liteapp.md`](./liteapp.md) |
| 同一任务明确跨两个以上 Dev 域 | 按依赖顺序组合原子命令;先不预读专题 | [`recipes.md`](./recipes.md) | | 同一任务明确跨两个以上 Dev 域 | 按依赖顺序组合原子命令;先不预读专题 | [`recipes.md`](./recipes.md) |
## 共同执行约束 ## 共同执行约束
@@ -0,0 +1,257 @@
# 轻应用免登(H5 身份接入)实现文档
> 适用对象:通过 `dws dev liteapp create` 创建的轻应用(H5 应用,创建即发放
> OAuth 凭证:clientId = appKey;客户端内免登使用 `snsapi_base`,OAuth `users/me` 路径必须申请 `openid Contact.User.Read`。
> 本文覆盖钉钉内 H5 与纯浏览器两种免登路径的完整实现,agent 可按此直接产出接入代码。
>
## 应用模型:轻应用与标准企业内部应用平行
轻应用(LiteApp)与标准企业内部应用是两个平行的应用类型,不能视为同一应用的不同配置,也不能交叉使用生命周期、回调地址或登录结论。
| 维度 | 轻应用 | 标准企业内部应用 |
|---|---|---|
| 生命周期 | `dws dev liteapp create/update/delete/list/detail` | 独立的标准应用命令域 |
| OAuth 回调 | 通过轻应用专属的 set/update 入口设置 | 使用标准应用自己的回调配置 |
| 回调配置 | 与标准应用不互通 | 与轻应用不互通 |
| 端内身份 | 以 `unionid` 为主,`userid` 不作为必填 | 通常以企业 `userid` 建立成员身份 |
| 端外 OAuth | 使用轻应用自己的 `clientId/appKey` 和回调登记 | 使用标准应用自己的 OAuth 配置 |
排查轻应用 `900103 应用不存在`、OAuth 回调失败或网页登录失败时,第一优先级检查轻应用自己的回调登记。当前 DWS 中,“set 轻应用”对应:
```bash
dws dev liteapp update <unifiedAppId> \
--redirect-uris <url> \
--dry-run --format json
```
确认预检目标和地址后,再对同一目标执行 `--yes`。`--redirect-uris` 是整体覆盖,不是追加。
> 官方基准文档:
> 端内 [企业内部网页应用免登](https://open.dingtalk.com/document/development/enterprise-internal-application-logon-free)、
> [requestAuthCode JSAPI](https://open.dingtalk.com/document/development/jsapi-request-auth-code);
> 端外「网页方式登录应用(第三方网站)」(方式一构造链接 / 方式二 DTFrameLogin 内嵌二维码,
> 子接口 [获取用户token](https://open.dingtalk.com/document/development/obtain-user-token.md)、
> [获取用户通讯录个人信息](https://open.dingtalk.com/document/development/dingtalk-retrieve-user-information.md))。
> 端内官方 Demo:web-app-sso.zip(前端 Vite + 后端 Spring Boot)。
## 前置条件(不满足则免登 JSAPI 直接失效)
1. **域名一致**:应用首页地址、重定向 URL(回调地址)、端内免登地址三者必须与
运行免登 JSAPI 的页面域名一致。即 H5 页面要部署在你自己的域名上,创建时
`--homepage-url` 填该域名(官方 Demo 推荐带 `?corpid=$CORPID$` 模板,钉钉打开时
自动注入企业 corpId);创建后改域名用 `dws dev liteapp update <unifiedAppId> --homepage-url <新地址>`,
先 dry-run,确认后再加 `--yes`。
2. **端内免登必须在钉钉客户端内打开**:官方原话——免登操作需要在钉钉端内实现,
端外无法正常调用免登组件。钉钉外浏览器走路径 B。
3. 应用需已发布(工作台可见),否则无法进入页面。
4. **建议缓存身份**:用 dd.setStorage / cookie 缓存用户信息,避免每次进应用都调免登。
## 原理与选型
| 路径 | 场景 | 原理 |
|---|---|---|
| A. 端内免登(官方标准,推荐) | H5 首页在钉钉客户端内打开 | 前端 `dd.requestAuthCode` 拿免登码 → 服务端用 appKey+secret 换轻应用用户身份,以 `unionid` 为必需主身份,`userid` 可空 |
| B. 端外网页登录(官方标准) | 钉钉外浏览器扫码/授权 | 方式一构造链接 / 方式二 DTFrameLogin 内嵌二维码,scope 显式声明 `openid Contact.User.Read` → authCode → userAccessToken → users/me(unionId=me) |
两个路径都以 **appKey(=clientId)+ appSecret(=clientSecret)** 标识应用;
凭证来自 `dws dev liteapp create` 或持续查询的 `dws dev liteapp credential <unifiedAppId>`,
勿放前端。secret 明文即使通过 CLI 查询到,也不得写入回答、日志、文档、邮件、群聊或代码仓库。
## 分流硬边界(排障高频根因)
两套 code 属于不同签发体系,**通道由发起登录的入口 URL 决定:禁止按 code 长度/形态猜链路,禁止失败后自动降级到任何非官方旧接口**。
| code 来源 | 唯一合法兑换链路 | 错送后果(真实报文) |
|---|---|---|
| 路径 A `dd.requestAuthCode` 免登码 | `gettoken` → `topapi/v2/user/getuserinfo` | 送 `users/me`:403 `Forbidden.AccessDenied.AccessTokenPermissionDenied`,`accessdenieddetail.requiredScopes=["Contact.User.Read"]`;送旧版 SNS 接口:errcode 40078「不存在的临时授权码」 |
| 路径 B `oauth2/auth`(scope 声明 `openid Contact.User.Read`)回调 code/authCode | `userAccessToken` → `users/me`(unionId=me) | scope 未声明 Contact.User.Read:token 不带该 scope,`users/me` 403 `AccessTokenPermissionDenied`;送任何旧版接口:40078/authCode.notFound |
- errcode 40078「不存在的临时授权码」= code 被送进错误的签发体系(或已消费/过期)。任何「users/me 失败降级旧接口」式 fallback 都只会制造第二个噪音错误,掩盖真实根因——保持官方链路,不要换接口。
- authCode/免登码均一次性:兑换失败由前端重新发起,服务端不重放旧 code。
## 路径 A:钉钉客户端内 H5 免登(官方标准,authCode → 轻应用用户身份)
### 第 1 步:引入官方客户端 SDK 并拿免登码
官方客户端 SDK 为 **dingtalk-jsapi**(全局对象 `dd`)。CDN 引入(放首页 `<head>`):
```html
<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.25/dingtalk.open.js"></script>
```
(生产环境也可 npm 引入:`npm install dingtalk-jsapi`。)
`dd.ready` 后用企业 corpId + 应用 clientId 换免登码(corpId 优先从首页地址的
`?corpid=$CORPID$` 模板取,钉钉打开应用时自动注入)。现行官方口径为
`dd.requestAuthCode`(clientId 必传);旧版 SDK 挂在 `dd.runtime.permission.requestAuthCode`,
回调结构一致:
```js
// corpId 从首页地址的 ?corpid=$CORPID$ 模板注入(钉钉打开应用时自动替换)。
// 必须从当前 URL 读取,不要硬编码任何组织 ID——复制到别的组织会免登失败。
const corpId = new URLSearchParams(location.search).get('corpid')
const clientId = '<appKey>' // = create/credential 返回的 appKey;是标识非密钥,可入前端
if (!corpId) {
// 提示:页面地址缺少 corpid 参数,请确认通过钉钉客户端工作台打开应用
throw new Error('missing corpid')
}
dd.ready(() => {
dd.requestAuthCode({
corpId,
clientId, // 企业内部应用传 AppKey;不是 AppSecret,也不是 unifiedAppId
onSuccess: r => {
fetch('/api/login?code=' + r.code) // 交给服务端换身份,code 不要落前端日志
},
onFail: err => {
// 必须处理失败回调并给出可见提示;不写 onFail 时 SDK 静默失败,
// 表现为「点击登录无任何反应」
}
})
})
```
### 第 2 步:服务端换取用户身份
```text
1) 换企业 access_token:
GET https://oapi.dingtalk.com/gettoken?appkey=<appKey>&appsecret=<appSecret>
→ { access_token, expires_in(7200) } // 建议缓存 7000 秒,别每次现取
2) 免登码换用户信息:
POST https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token=<上一步 token>
body: { "code": "<前端免登码>" }
→ errcode=0 后按 appKey 类型分两种返回形态(见下)
```
**返回形态按 appKey 类型固定**:
```text
标准企业内部应用 appKey → result: { userid, device_id, sys_level }
轻应用 appKey → result: { name, unionid, sys, sys_level } // 没有 userid
```
**轻应用 appKey 一定不返回企业 userid**(实测 2026-10-21):轻应用的 appKey 是
`createOAuthApp(..., CLIENT_OAPI_ORG)` 创建、scope 仅 `snsapi_base` 的**个人身份型
OAuth 客户端**,`getuserinfo` 对它只回个人身份(name + unionid + 管理员级别)——与
用户是否本组织、权限是否生效(Contact.User.Read 已 authed=true 仍如此)均无关。
服务端实现:
```js
const result = json.result || {};
if (!result.unionid) throw new Error('getuserinfo 无 unionid'); // 唯一真失败条件
createSession({ unionid: result.unionid, userid: result.userid || null, name: result.name });
```
- **unionid 是轻应用端内免登的唯一主身份**;把「缺 userid」当失败(如报
`user/getuserinfo no userid`)会挡掉所有端内登录。
- 标准应用返回 userid 时可直接用它建会话;需要姓名、头像等详情再调
`POST topapi/v2/user/get`(body 带 userid)。
- 产品必须要企业 userid 时:换标准企业内部应用(`dws dev app create`)的 appKey,
或另立需求给轻应用 client 增加企业通讯录能力。
- 免登码 5 分钟有效且一次性,兑换失败让前端重新 `requestAuthCode`,不重放。
> 官方 Demo:web-app-sso.zip(前端拿 code + 后端换用户身份的完整可运行示例,
> 从官方免登文档页下载)。轻应用分支以 unionid 为必需主身份、userid 可空;只有标准企业内部应用分支才按 userid 建会话。后端配置 clientId/clientSecret、前端配置 clientId 即可联调。
## 路径 B:端外网页登录(官方:网页方式登录应用)
> 企业内部应用**仅限本组织员工**登录,不支持跨组织授权;需要多企业用户请用第三方企业应用。
**前置条件**:
- 已申请 `Contact.User.Read` 权限点并生效:轻应用创建后申请通常直接生效;不要把标准应用的版本发布流程套用到轻应用。
- 授权链接 `scope` 必须**显式声明** `openid Contact.User.Read`(不声明则签出的 token 不带该 scope,`users/me` 必 403)。
- 轻应用 OAuth 回调地址必须通过轻应用自己的 set/update 入口登记,与 `redirect_uri` 逐字符一致;不能用标准应用的回调配置代替。
### 方式一:构造登录链接(PC 网页 / H5 通用)
302 引导用户访问:
```text
https://login.dingtalk.com/oauth2/auth?
redirect_uri=<urlencode(回调地址)>
&response_type=code
&client_id=<appKey>
&scope=openid%20Contact.User.Read
&state=<防 CSRF 随机串>
&prompt=consent
```
- `redirect_uri` 必须 urlencode。
- **scope 显式声明 user 权限点**:`openid Contact.User.Read`(空格分隔,需 URL 编码为
`%20`)——不声明时签出的 token 不带该 scope,`users/me` 必 403 `AccessTokenPermissionDenied`。
- 也可用 `openid`(仅获 userid)或 `openid corpid`(额外返回用户所选组织 corpId,
可配 `org_type=management` / `corpId` 过滤组织)。**不是 snsapi_base**。
- `state` 防 CSRF:随机生成、回调原样返回、必须校验。
### 方式二:内嵌二维码(DTFrameLogin)
```html
<script src="https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js"></script>
<div id="self_defined_element" style="width:300px;height:300px"></div>
```
```js
window.DTFrameLogin(
{ id: 'self_defined_element', width: 300, height: 300 },
{
redirect_uri: encodeURIComponent('<回调地址>'),
client_id: '<appKey>',
scope: 'openid Contact.User.Read',
response_type: 'code',
state: '<随机>',
prompt: 'consent',
},
(loginResult) => {
const { redirectUrl, authCode, state } = loginResult;
window.location.href = redirectUrl; // 或不跳转,直接把 authCode 交后端
},
(errorMsg) => { /* 必须展示失败原因(toast 等),不允许静默 */ }
);
```
- **同源要求**:嵌入二维码的页面必须与 `redirect_uri` 同源(协议、域名、端口一致),
否则扫码后没有反应;排障先查同源、CDN(g.alicdn.com)可达性、浏览器缓存。
- 可用 `exclusiveLogin: true` + `exclusiveCorpId` 生成专属组织二维码,限制非本组织账号扫码。
### 回调与服务端兑换
用户同意授权后回调 `redirect_uri?code=xxx&authCode=xxx&state=xxx`
(**code 与 authCode 一致,取任一即可**;authCode 一次性,state 必须校验)。
```text
1) 换用户个人 token:
POST https://api.dingtalk.com/v1.0/oauth2/userAccessToken
Content-Type: application/json
{ "clientId": "<appKey>", "clientSecret": "<appSecret>",
"grantType": "authorization_code", "code": "<authCode>" }
→ { accessToken, expireIn(7200), refreshToken }
2) 获取用户通讯录个人信息——unionId 参数值传字符串 "me":
GET https://api.dingtalk.com/v1.0/contact/users/me
Header: x-acs-dingtalk-access-token: <userAccessToken>
Scope prerequisite: `openid Contact.User.Read` (仅 `openid` 或 `openid corpid` 不能调用 users/me)
→ nick、avatarUrl、unionId、openId 等
```
**unionId 传 `me`**:该接口按 unionId 定位用户,查询当前授权人固定传字符串 `me`
(即 `GET /v1.0/contact/users/me`),不要先换算 unionId 再查。报错按官方链路排障,
**禁止降级任何旧接口**。
## 公开端点
使用官方生产端点:`mcp-gw.dingtalk.com`(MCP 网关)、`login.dingtalk.com`(登录授权)、`open-dev.dingtalk.com`(应用管理)。二维码 SDK 使用官方文档指定的版本及 CDN。
## 前置条件与常见坑
- **回调地址必须先登记**:通过轻应用专属入口执行 `dws dev liteapp update <unifiedAppId> --redirect-uris https://a/cb,https://b/cb --dry-run`,确认预览后再用同一命令加 `--yes` 执行;未登记的回调地址授权页/扫码页会直接拒绝。轻应用与标准应用回调配置不互通。
- **secret 只在服务端**:appSecret/userAccessToken 绝不能下发到前端。
- **OAuth scope**:调用 `GET /v1.0/contact/users/me` 必须使用 `openid Contact.User.Read`;仅 `openid` 或 `openid corpid` 不能调用 users/me。客户端内 authCode 免登仍使用 `snsapi_base`,两者不要混用。
- 需要手机号等敏感字段要走敏感权限申请,不在轻应用一期范围。
- **access_token 缓存**:企业 token 与 userAccessToken 都有有效期(7200s),
均需缓存复用,禁止高频重取;authCode 一次性,需要复用先换 refreshToken。
- **仅本组织**:企业内部应用端外登录仅限本组织员工;判断用户归属用返回的 corpId
与应用所属企业比对。
- **端外占位对象**:普通浏览器里 `window.dd` 可能存在占位对象,调用免登 JSAPI
不回调、表现为「点击无反应」。进入路径 A 前先判断 UA 含 DingTalk,端外一律
走路径 B;`onSuccess`/`onFail` 必须同时提供。
@@ -0,0 +1,185 @@
# 轻应用命令参考(dws dev liteapp)
`liteapp` 是独立的轻应用命令组:创建即发布、挂工作台并取得 OAuth 凭证;它不是普通统一应用
(`dws dev app`)的别名,也不提供普通统一应用的草稿/审核/版本生命周期。调用身份由系统上下文
注入,仅能操作当前调用人创建的轻应用。
`create`、`update`、`delete` 是写操作,统一遵循 `--dry-run` → 展示预览并取得明确确认 →
同一命令改用 `--yes` 执行;`list`、`detail`、`credential` 是读操作。`--dry-run` 只做本地
CLI 参数/约束校验并生成调用预览,不发远端调用、不触网;远端 schema 与业务校验只会
发生在真实执行阶段。
## 输出与校验边界
- `--format json` 输出与 `dws dev app` 完全一致的统一 envelope(`ok/outcome/data/error/meta`);
`data` 是解包后的业务结果(如 create 的 `appId/appKey/secret`、list 的 `total/apps`),
可直接按字段读取。
- 远端返回 `success=false` 时 envelope 为失败(`ok=false`),`error` 中原样携带
`errorCode`(`upstream_code`)与 `errorMsg`,按失败处理并原样报告,不要按"调用没报错"
当成功。
- 本地 CLI 校验失败(缺参数、非法位置参数、约束不满足)与远端校验/业务失败是两类问题:前者
不会发远端调用,后者以 envelope 的 `error` 为准。不要用重复 help 或无证据重试掩盖差异。
- `credential` 可持续查询明文 secret;明文仅用于必要的服务端配置,严禁出现在回答、日志、文档、
邮件、群聊、前端、命令记录或代码仓库中。`detail`/`list` 只返回掩码。
## 操作语义
- 创建即发布:`create` 返回 `status=PUBLISHED` 就是已上线终态,没有草稿/审核/版本
流;要版本与发布能力的是 `dws dev app`([version.md](version.md)),不要混用。
- `warning` 非空 = 成功但有降级(工作台挂载失败、统一应用注册失败等):按成功
继续后续步骤,但必须把 warning 原样转述给用户,不要自行判断"没影响"而省略。
- `success=false` 原样报告 `errorCode/errorMsg`(错误码表见下方);不要按传输层
"调用没报错"就当成功。
- 幂等只在传了 requestId 时生效(schema 里它是可选):不传 requestId 的重试会
创建出重复应用,网络重试/用户重发场景务必带键。同键同参重试返回首次结果
(secret 不回显,用 `credential` 补查);同键不同参拒绝(E_IDEMPOTENT_CONFLICT);
首次在途(E_IDEMPOTENT_PROCESSING)用同一 requestId 稍后重试,不换键。四个
易误判点:参数/内容校验先于幂等判重——新参数不过校验时报校验错误码(如
E_CONTENT_REJECTED)且不消耗幂等键,修正后同键可直接重试,CONFLICT 只在新
参数通过校验后才会出现;首次创建落库失败会释放幂等键,同键重试是一次全新
创建,不返回 DUPLICATE/CONFLICT;幂等服务自身读写失败返回 E_DEPENDENCY_FAILED
(不是幂等冲突,同键稍后重试即可);首次中断后的重放会逐项补齐缺失副作用
(创建者/负责人登记、OAuth 凭证与映射、默认权限、发布、统一应用),补不齐的
项以 warning 告知,secret 明文仍只经 create 响应或 `credential` 查询获取;
幂等记录 TTL 24h,过期后同 requestId 视为新请求。
- 删除是 24 小时软删:期内仍占配额、list 可见且条目带 `timeToDel`;软删不可恢复,
删除放在全部依赖步骤的最后。list 条目的 timeToDel(软删状态属性)与 delete
返回的 timeToDel(本次删除截止时间戳)同名不同义,不要混用。
- 明文 secret 只出现在 `create` 响应和 `credential`;detail/list 永远只有掩码;
不回显给用户以外的目的地,不落日志、文档、仓库。
- 无权限与不存在统一返回 E_NOT_FOUND,不据此推断应用是否存在,也不要反复换
参数重试探权限。
- 配额 50 /(组织, 用户),软删期仍计入;E_USER_QUOTA_EXCEEDED 时引导用户删除
不再使用的轻应用,不要自动批量删。
## 边界
liteapp 只管"创建即发布的轻应用本体 + 凭证";轻应用创建时同步注册的统一应用
(`unifiedAppId`)此后就是普通统一应用——轻应用和统一应用是分离的两个面。后续
高级能力拿 `unifiedAppId` 走 `dws dev` 对应能力组,文档与 `dws dev app` 完全一致:
- 版本历史 / 待发布版本 / 发布 → `dws dev app version`([version.md](version.md))
- 权限点申请 → [permission.md](permission.md)
- 事件订阅 → [event.md](event.md)
- 机器人线上配置 → [robot.md](robot.md);管理成员 → [member.md](member.md)
要"完整生命周期的企业内部应用"(草稿、审核、版本发布流程)时,直接用
`dws dev app`([app.md](app.md))创建,不要先建 liteapp 再补版本;只有
"秒建秒用、挂工作台、拿凭证"诉求才用 liteapp。
## 何时用哪个
- 用户问「我建过哪些轻应用」→ list;「第2页有哪些」→ list 加 `--offset`
- 「看下这个应用的状态/配置/回调」→ detail(无 secret 明文;redirectUris 登记后返回)
- 「给我 appKey/secret 做服务端接入」→ credential(返回明文,注意防泄露)
- 「改名称/描述/首页/回调地址」→ update;「不再需要了」→ delete(先与用户确认)
## create
先预览(不触网):
```bash
dws dev liteapp create --name 周报助手 --homepage-url https://example.com \
--pc-url https://example.com/pc --desc 描述 --request-id 6f1c2b3a-uuid --dry-run --format json
```
确认预览中的对象、参数和影响后,对同一命令仅将 `--dry-run` 换为 `--yes` 执行。`--name`/`--homepage-url`
必填;`--pc-url` 缺省取移动端首页。
- `--icon-media-id` 收 media_id(由媒体上传 OpenAPI 生成,非 http 地址);
不传自动按应用名生成默认图标。
- `--request-id` 幂等键(建议 UUID):同键同参重试返回首次结果(含原 appId、
appKey,不重复下发 secret),参数变化拒绝;首次仍在处理中返回
E_IDEMPOTENT_PROCESSING,持同一 requestId 稍后重试而非换键。
- result:appId、appKey、secret(明文)、secretMask、unifiedAppId、
status、requestId、warning。凭证生成降级时凭证字段为 null:告知用户稍后在
开发者后台重试,再用 `credential <unifiedAppId>` 补查。
- 免登接入(H5 首页):按 [liteapp-sso.md](liteapp-sso.md) 实现客户端内免登
(dd JSAPI 拿免登码,服务端换轻应用用户身份;以 unionid 为必需主身份,userid 可空),clientId 使用 create 返回的 appKey。
## list
```bash
dws dev liteapp list --size 10 --offset 0 --format json # 首查:前 10 条
dws dev liteapp list --size 10 --offset 10 --format json # 翻页:第 11-20 条
dws dev liteapp list --size 50 --offset 0 --format json # 边界:单页上限 50
```
- 触发场景:用户问「我建过哪些轻应用」「第2页有哪些」「还剩多少配额」。
- `total` 与配额口径一致(软删期仍计入);按 createdAt 倒序;不含 secret 字段。
- 条目 `timeToDel` 仅表示该应用当前处于软删期(列表属性),不是删除操作的结果。
## detail
定位主键:以下 update/delete/detail/credential 的位置参数是**统一应用 ID
(`unifiedAppId`,字符串)**,取自 create 返回或 list/detail 的 `unifiedAppId`
字段;服务端反查内部 microAppId 后逐项鉴权,与 `dws dev app` 全族定位口径一致。
```bash
dws dev liteapp detail <unifiedAppId> --format json
```
返回基础信息、appName、appKey、secretMask、unifiedAppId、status 等;`redirectUris`
仅在登记过回调地址后返回;**无 secret 明文**——需要明文接入时改用 credential。
查状态/配置一律用 detail。无权限或不存在统一返回 E_NOT_FOUND(防探测)。
## update
先预览:
```bash
dws dev liteapp update <unifiedAppId> --name 新名称 --homepage-url https://m.example.com/ \
--redirect-uris https://a.example.com/cb,https://b.example.com/cb --dry-run --format json
```
确认预览无误后,对同一命令仅将 `--dry-run` 换为 `--yes` 执行。仅创建者本人可更新;所有字段不传=保持
原值,传空串一律拒绝(防误清空)。
- 回调地址必须 https(http:// 会被拒绝);`--redirect-uris` 逗号分隔,
传入即全量覆盖登记,上限 10 条。
## credential
```bash
dws dev liteapp credential <unifiedAppId> --format json
```
- 返回 appKey 明文 + secret 明文与掩码,可持续获取;仅用于取密钥,
查状态/配置/回调请用 detail,避免不必要的明文暴露。
- 防泄露:不写入日志、文档、邮件、群聊、代码仓库。重置需开发者后台人工完成。
## delete
先预览并确认:
```bash
dws dev liteapp delete <unifiedAppId> --dry-run --format json
# 明确确认同一对象后:
dws dev liteapp delete <unifiedAppId> --yes --format json
```
- 仅创建者或组织管理员;24 小时软删,期内占配额、列表可见,不可恢复。
- `result` 的 timeToDel 是本次删除生成的软删截止时间戳(毫秒)——与 list
条目里的 timeToDel(软删期状态属性)同名不同义。
## 错误码
| errorCode | 含义 | 处理 |
|---|---|---|
| E_PARAM_INVALID | 参数缺失/非法(含空串更新、回调超 10 条) | 按 message 修正 |
| E_NOT_LOGIN | 登录态缺失 | `dws auth login` |
| E_ORG_INVALID / E_ORG_BLACKLISTED | 组织无效/黑名单 | 核对 corpId 或联系管理员 |
| E_CONTENT_REJECTED | 内容安全拒绝 | 修改名称/描述 |
| E_USER_QUOTA_EXCEEDED | 配额满(50/组织/用户) | 删除不再使用的轻应用 |
| E_NOT_FOUND | 应用不存在或无权限(防探测) | 核对 unifiedAppId 与当前账号 |
| E_IDEMPOTENT_CONFLICT | 同 requestId 参数变化 | 更换 requestId |
| E_IDEMPOTENT_PROCESSING | 首次请求仍在处理中 | 同 requestId 稍后重试 |
| E_DEPENDENCY_FAILED | 下游依赖失败(含幂等服务暂不可用) | 稍后重试;带 requestId 时保持同键 |
## 服务端与路由
轻应用六个工具与 `dws dev app` 由同一钉钉开放平台应用服务承载,CLI 固定路由(与 devapp
完全相同的调用机制),没有也不需要任何服务指定参数;认证、profile 与环境网关(生产/预发)
行为与 `dws dev app` 一致,排障方式也相同(`dws auth status`、profile 切换、网关配置)。
- 边界:权限点申请、版本发布、事件订阅等统一应用能力不在本命令组范围,
拿 `unifiedAppId` 走 `dws dev` 对应能力组(version/permission/event,见上方
「边界」一节);普通企业内部应用管理走 `dws dev app`。
@@ -1,21 +1,20 @@
# 开放平台应用管理(dev / devapp) # 开放平台应用管理(dev / devapp)
> **渐进式文档**:本文件为路由层(MUST DO / 概念地图 / 产品索引);各命令组的详细参数与流程在 [dev/](./dev/) 目录下按需加载。命令前缀:`dws dev`(原子)与 `dws devapp +create` 等 `+` shortcut。 > **渐进式文档**:本文件为路由层(MUST DO / 概念地图 / 产品索引);各命令组的详细参数与流程在 [dev/](./dev/) 目录下按需加载。默认命令前缀是 `dws dev` 原子路径;`dws devapp +...` 仅作兼容或用户明确指定的 shortcut。
## MUST DO ## MUST DO
每次执行 dev 命令前,先查清楚再敲,别凭记忆或猜: 已知 leaf 直接执行,避免用 help 探路;只有当前参数确有不确定性时才查一次该 leaf 的 compact Schema,Schema 不可用才查一次精确 leaf `--help`:
1. `--help` 看命令树(一个组下有哪些子命令、flag),例 `dws dev app --help` 1. 不用组 help 或父命令 help 探路发现命令;当前 reference 已明确命令时按原子路径执行
2. `--help` 看叶子命令参数(flag、默认值、示例),按当前二进制输出构造;不要再依赖已下线的动态 schema 2. 全部命令带 `--format json`
3. 全部命令带 `--format json` 3. 写操作:先用 `--dry-run` 查看调用预览(本地校验,不触网),展示准确对象、动作和参数并取得明确确认,再把同一命令改为 `--yes`;`dev connect` 例外见 [connect.md](./dev/connect.md),MCP 见 [mcp.md](./dev/mcp.md)
4. 写操作:`--dry-run` 看 `invocation.params` 确认无误,再换 `--yes`(`dev connect` 例外见 [connect.md](./dev/connect.md);MCP 见 [mcp.md](./dev/mcp.md)) 4. 写完回读确认(`get` / `robot get` / `version status`)
5. 写完回读确认(`get` / `robot get` / `version status`) 5. `clientSecret/appSecret` 不写进回答(脱敏)
6. `clientSecret/appSecret` 不写进回答(脱敏) 6. `robot result` 只要出现 `completionState=BLOCKED_BY_VERSION_PUBLISH` 或 `mustContinue=true`,必须继续执行 blocking `nextSteps`,不得停在 `dev connect`
7. `robot result` 只要出现 `completionState=BLOCKED_BY_VERSION_PUBLISH` 或 `mustContinue=true`,必须继续执行 blocking `nextSteps`,不得停在 `dev connect` 7. `dev connect` 的 `completionState=LOCAL_DEBUG_ONLY` / `doesNotPublish=true` 只代表本地调试,不能作为最终完成态
8. `dev connect` 的 `completionState=LOCAL_DEBUG_ONLY` / `doesNotPublish=true` 只代表本地调试,不能作为最终完成态 8. `version check-approval` 若返回 `completionState=WAITING_FOR_APPROVER_SELECTION`,选择题原样展示 `approvalPromptText`(或 `approvalOptions[].label`),不得把姓名丢成泛化“候选审批人”
9. `version check-approval` 若返回 `completionState=WAITING_FOR_APPROVER_SELECTION`,选择题原样展示 `approvalPromptText`(或 `approvalOptions[].label`),不得把姓名丢成泛化“候选审批人” 9. `robot result` 若缺 `unifiedAppId` 或返回 `completionState=BLOCKED_BY_MISSING_UNIFIED_APP_ID`,必须停下要求明确的 `unifiedAppId`;禁止用 `clientId/appKey` 自动反查后继续执行版本写操作
10. `robot result` 若缺 `unifiedAppId` 或返回 `completionState=BLOCKED_BY_MISSING_UNIFIED_APP_ID`,必须停下要求明确的 `unifiedAppId`;禁止用 `clientId/appKey` 自动反查后继续执行版本写操作
<!-- VISIBLE_SHORTCUTS_START --> <!-- VISIBLE_SHORTCUTS_START -->
## Shortcuts(无专用脚本/recipe 时优先) ## Shortcuts(无专用脚本/recipe 时优先)
@@ -66,7 +66,7 @@
- 参数缺失 / 无效请求 — 还在用旧参数 `dentryUuid` / `--doc` / `--sheet` → 改用 `--base-id` / `--table-id` / `--field-id` / `--record-ids` - 参数缺失 / 无效请求 — 还在用旧参数 `dentryUuid` / `--doc` / `--sheet` → 改用 `--base-id` / `--table-id` / `--field-id` / `--record-ids`
- 参数传了但服务端没收到 — flag 用了 camelCase(如 `--baseId`)→ flag 用 kebab-case: `--base-id <ID>` - 参数传了但服务端没收到 — flag 用了 camelCase(如 `--baseId`)→ flag 用 kebab-case: `--base-id <ID>`
- `record query --filters` 无结果 — 单选/多选过滤用了 option name 而非 id → 先 `field get` 读取 options,用 option id 过滤 - `record query --filters` 无结果 — 单选/多选过滤用了 option name 而非 id → 先 `field get` 读取 options,用 option id 过滤
- record create/update 失败 — `cells` key 用了字段名(应为 fieldId);特殊字段格式错误 → 先 `field get` 拿字段目录;url 传 `{"text":"..","link":".."}` - record create/update 失败 — `cells` key 使用了不存在、重名或未精确匹配的字段名(支持 fieldId 或当前表内唯一字段名,推荐真实 fieldId);特殊字段格式错误 → 先 `field get` 拿字段目录;url 传 `{"text":"..","link":".."}`
- 更新选项后历史数据异常 — 更新 options 没传完整列表 / 没保留原 id → 先 `field get` 取完整配置,保留已有 option 的 id - 更新选项后历史数据异常 — 更新 options 没传完整列表 / 没保留原 id → 先 `field get` 取完整配置,保留已有 option 的 id
- `cannot delete the last table` — 该表是 Base 最后一张表 → 先新建表再删旧表,或用 `base delete` - `cannot delete the last table` — 该表是 Base 最后一张表 → 先新建表再删旧表,或用 `base delete`
- `formula` 类型 `not supported yet` — 部分字段类型暂不支持 API 创建 → 复杂字段拆开单独创建,先建基础结构 - `formula` 类型 `not supported yet` — 部分字段类型暂不支持 API 创建 → 复杂字段拆开单独创建,先建基础结构
@@ -58,7 +58,7 @@ dws auth status --format json
| `--fields` | 逗号分隔的输出字段筛选 | 空 | | `--fields` | 逗号分隔的输出字段筛选 | 空 |
| `--jq` | jq 表达式过滤输出 | 空 | | `--jq` | jq 表达式过滤输出 | 空 |
| `--profile` | 一次性指定组织或账号;支持 CSV 多选的命令由运行时决定 | 当前 profile | | `--profile` | 一次性指定组织或账号;支持 CSV 多选的命令由运行时决定 | 当前 profile |
| `--timeout` | HTTP 请求超时秒数 | `30` | | `--timeout` | HTTP 请求与文件直传超时秒数(文件直传最低保留 5 分钟) | `30` |
| `--dry-run` | 请求预览 | 只有 leaf 明确支持时才代表可执行预览 | | `--dry-run` | 请求预览 | 只有 leaf 明确支持时才代表可执行预览 |
| `--yes`, `-y` | 通过 Runtime confirmation gate | 仅在用户确认且最终 Schema 要求时追加 | | `--yes`, `-y` | 通过 Runtime confirmation gate | 仅在用户确认且最终 Schema 要求时追加 |
| `--verbose`, `-v` | 增加诊断信息 | `false` | | `--verbose`, `-v` | 增加诊断信息 | `false` |
@@ -35,6 +35,9 @@
| `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` | | `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` |
| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` | | `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` |
| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` | | `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` |
| `aitable` | AI表格字段定位 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=entrance%3Ddata%26sheetId%3D{tableId}%26fieldId%3D{fieldId}` | 已确认的 `baseId/tableId/fieldId`;字段定位依赖前端能力 |
| `aitable` | 应用模式页面 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=entrance%3Dapps%26appId%3D{appId}%26pageId%3D{pageId}` | 编码 `appId` 与真实 `pageId` |
| `aitable` | 自动化流程编辑 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=entrance%3Dautomation%26flowId%3D{flowId}%26autoOpenAutomation%3Dtrue` | 真实 `flowId`;导航不会启用流程 |
| `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` | | `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` |
| `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` | | `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` |
| `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` | | `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` |
@@ -12,7 +12,7 @@ metadata:
执行任何 `dws` 操作前,先完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。先用 [dingtalk-tag-index.md](references/dingtalk-tag-index.md) 路由;生命周期读取 [manage.md](references/manage.md),自然语言创建/恢复/DSH 接入再读 [manage-and-connect.md](references/manage-and-connect.md);能力资源读取 [capability.md](references/capability.md);执行排障读取 [run.md](references/run.md)。 执行任何 `dws` 操作前,先完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。先用 [dingtalk-tag-index.md](references/dingtalk-tag-index.md) 路由;生命周期读取 [manage.md](references/manage.md),自然语言创建/恢复/DSH 接入再读 [manage-and-connect.md](references/manage-and-connect.md);能力资源读取 [capability.md](references/capability.md);执行排障读取 [run.md](references/run.md)。
自行管理任务执行与回复的接入方,可按 [回复占位与流式](../dingtalk-chat/references/card/a2ui.md) 实现启动钩子和卡片更新。使用该流程需已安装 `dingtalk-chat`;按需安装时请同时选择该 Skill。DWS 直接托管本地 Agent 时,对已适配公开进度的 Agent,新连接默认发送 A2UI 占位并更新同一卡片,由运行时负责投递,模型不重复发卡;连接时指定 `--reply-mode text` 可只发最终文本。适配范围和首卡策略见 [接入说明](references/manage-and-connect.md)。 自行管理任务执行与回复的接入方,可按 [回复占位与流式](../dingtalk-chat/references/card/a2ui.md) 实现启动钩子和卡片更新。使用该流程需已安装 `dingtalk-chat`;按需安装时请同时选择该 Skill。DWS 直接托管本地 Agent 时,默认发送 A2UI 占位并在同一卡片更新最终答案,已适配的 Agent 同时显示公开进度;历史配置缺少回复模式时升级并重启后也启用 A2UI,显式 `text` 保持不变。运行时负责投递,模型不重复发卡;连接时指定 `--reply-mode text` 可只发最终文本。适配范围和首卡策略见 [接入说明](references/manage-and-connect.md)。
## 路由 ## 路由
@@ -16,7 +16,24 @@ connect 在接入 Agent 前调用服务端 bind,返回 ID 后持久保存。
本节回复模式仅适用于 DWS 直接托管的本地 Agent;外部宿主接入(`--channel dsh`)由宿主负责回复,详见下方“接入已有数字员工到 DSH”。 本节回复模式仅适用于 DWS 直接托管的本地 Agent;外部宿主接入(`--channel dsh`)由宿主负责回复,详见下方“接入已有数字员工到 DSH”。
新建连接的回复模式默认是 `--reply-mode a2ui`;历史配置缺少回复模式时保持文本回复和原会话,重新 connect 可显式选择模式。对已适配公开进度接口的 Agent,DWS 接入运行时在调用模型前发送“思考中…”占位,并将公开阶段、工具开始/结束状态和答案更新到同一卡片;不展示内部推理或工具原始输入输出。执行面板默认收起,运行中显示生成动画,结束时移除;最终答案在面板外展示;超出单批容量的正文分段写入同一卡片,全部成功后才完结。未适配公开进度接口的 Agent、会话控制命令仍使用最终文本。 DWS 直接托管本地 Agent 时,回复模式默认是 `--reply-mode a2ui`;历史配置缺少回复模式时,升级二进制并通过正常 stop/connect 更新运行快照后也使用 A2UI,显式配置的 `text` 保持不变。所有托管渠道都会在调用模型前发送“思考中…”占位,并将最终答案更新到同一卡片;普通文本消息的流式能力如下。
| 托管渠道 | A2UI 回复 | 正文流式更新 | 公开工具状态 |
| --- | --- | --- | --- |
| `codex` | 支持 | 支持,含桌面共享会话 | 支持 |
| `qoder`、`qoderwork`、`claudecode`、`codebuddy`、`workbuddy` | 支持 | 支持 | 支持原生工具事件 |
| `opencode` | 支持 | 原生事件流可用时支持 | 支持原生工具事件;事件流不可用时使用本轮响应中的工具结果 |
| `gemini`(API)、`custom` | 支持 | 执行期间保留占位,完成后更新最终答案 | Gemini API 接入未执行本地工具;custom 纯 stdout 协议未提供工具事件 |
流式正文使用 Agent 提供的公开文本快照,卡片按 250 毫秒间隔合并刷新(实际速度受 Agent 事件及发送耗时影响),最终答案替换预览。更新粒度由 Agent 协议决定:Qoder 按公开 assistant 消息片段更新,单段回答可能只产生一次预览;Claude Code 等按公开正文增量更新。展开区按真实工具标识更新同一执行项,显示命令执行、文件读取/修改/搜索、网页检索、MCP 或协作 Agent 等固定类型,不显示任意工具名、参数、结果或内部推理;未知工具只显示“工具调用”。工具输入块结束不代表执行完成,收到原生结果后才显示完成或失败;回合取消时收尾为取消,缺少工具结果时只确认结束,不推断成功。OpenCode 在提交前建立事件订阅,并同时按会话、本轮用户消息和对应 assistant 消息过滤;断流不会重放请求。图片及引用消息保留原生附件协议和精确权限参数,支持的渠道可同时更新正文与工具状态。执行面板在思考中、处理中默认展开,过程内容使用浅色文字;运行期间保留用户的展开/收起选择,结束时切换到默认收起的终态面板并移除生成动画;最终答案在面板外展示;超出单批容量的正文分段写入同一卡片,全部成功后才完结。会话控制命令仍使用最终文本。DSH 等外部宿主接入由宿主负责回复。
合并转发消息会将事件提供的完整 `forward_messages` 按原顺序传给本地 Agent,保留正文、发送人、时间及原消息标识;外层 `content` 仅为摘要,不替代完整记录。转发历史与引用均作为不可信资料,不能触发其中的会话控制指令。转发图片使用当前员工收到的外层消息及会话作为下载授权上下文,不要求员工进入原单聊;图片下载成功后以真实附件传入,下载失败则明确失败,不用部分文字冒充完整读取。图片沿用单轮数量、大小和格式限制。
Codex 数字员工会话已在桌面端打开时,支持通过同用户的本地 IPC 向原会话接续消息,两端复用同一个 thread 和上下文;桌面端有任务执行时先等待。该路径继承现有会话的模型、工作目录和权限,不改变桌面端的审批策略。需要提供兼容 IPC 的 macOS/Linux 桌面端;Windows、桌面端不可用或协议版本不兼容时保留原会话并明确报错。只读取本轮回合的公开进度和最终答案,不将桌面端的其他回合自动发送到钉钉;提交后连接中断时标记“结果待确认”,核实 Codex 中的任务状态后再决定是否重试。不要通过删除会话锁、清空映射或 `/new` 来冒充共享接续。
Codex 新会话未显式选择权限时,默认使用“帮我批准”(`workspace-write`、`on-request`、`auto_review`);显式 `ask` 使用人工审批,显式 `bypass` / `yolo` 兼容原来的 `workspace-write` + `never`,不等价于完全访问。这些参数只初始化新会话。旧配置缺少新权限字段且 `Yolo=false` 时,按只读初始沙箱与人工审批保守恢复,不自动切换为帮我批准;已有 thread 的最新人工权限不受此迁移覆盖。独立 app-server 恢复前读取 Codex 最新持久化权限,显式还原沙箱、审批方式、工作目录与可写目录,并核对服务端回执后才开始回合,避免进程默认配置扩大权限;桌面已持有的会话继续由原 owner 接续。未知权限格式、无法完整重建的命名网络代理策略或未确认的恢复结果会停止独立执行,不退回连接默认权限。需要人工决定时,审批卡单独发送给绑定主管;其他可见同事发起的请求也由主管审批,卡上展示真实发起人、来源会话与消息。命令代码块只放待审核指令,申请理由和工作目录单独展示;只允许本次操作或本轮权限,不创建永久授权。桌面会话权限或可写目录变化时,旧审批失效;超时、身份不符或详情不能安全完整展示时不放行。桌面共享链路提交前最多等待 2 分钟,提交后最多等待 1 小时;`--agent-timeout` 可更早结束,设为 0 也不取消这两段上限。提交后超时保留结果待确认,不重发也不打断原任务,先在 Codex 核对。
连接时加 `--reply-card-forward` 可允许转发新创建的 A2UI 回复卡片,默认关闭;已发送卡片不受影响,文本回复不使用此参数。
连接时指定 `--reply-mode text` 可跳过占位和卡片,只发送最终文本。首卡策略按连接配置确定,不根据自然语言临时推断;修改已运行连接的配置需先 stop,再按原绑定信息 connect。首卡创建失败或结果未确认时不启动模型、不自动回退;失败原因保留在任务账本中,连续失败写运行日志告警;核实未投递后可按需切换 text 模式重新发起任务。本地容量限制导致无法完整交付时,保留已投递内容并尝试以“完整结果未交付”结束卡片,不标记成功。其他卡片更新失败或结果未知时保留原卡并标记待核实,不重建、不自动重发最终文本;中断任务不自动重放,强制退出后不保证补发终态。 连接时指定 `--reply-mode text` 可跳过占位和卡片,只发送最终文本。首卡策略按连接配置确定,不根据自然语言临时推断;修改已运行连接的配置需先 stop,再按原绑定信息 connect。首卡创建失败或结果未确认时不启动模型、不自动回退;失败原因保留在任务账本中,连续失败写运行日志告警;核实未投递后可按需切换 text 模式重新发起任务。本地容量限制导致无法完整交付时,保留已投递内容并尝试以“完整结果未交付”结束卡片,不标记成功。其他卡片更新失败或结果未知时保留原卡并标记待核实,不重建、不自动重发最终文本;中断任务不自动重放,强制退出后不保证补发终态。
@@ -2,8 +2,8 @@
"sourceId": "find-skills", "sourceId": "find-skills",
"repo": "https://github.com/vercel-labs/skills.git", "repo": "https://github.com/vercel-labs/skills.git",
"ref": "main", "ref": "main",
"commit": "7a5e920bead4b1039a6d6df5d7e1163b7b09423a", "commit": "9197d919034d19010ae303cf96bf19ad087dd4be",
"adapter": "claude-skill", "adapter": "claude-skill",
"sourcePath": "skills/find-skills", "sourcePath": "skills/find-skills",
"syncedAt": "2026-11-03T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
} }
@@ -3,5 +3,5 @@
"name": "playwright浏览器自动化操作", "name": "playwright浏览器自动化操作",
"version": "20260605", "version": "20260605",
"keySource": "none", "keySource": "none",
"syncedAt": "2026-11-03T16:03:11Z" "syncedAt": "2026-11-04T09:28:42Z"
} }
@@ -2,8 +2,8 @@
"sourceId": "next-skills", "sourceId": "next-skills",
"repo": "https://github.com/vercel/next.js.git", "repo": "https://github.com/vercel/next.js.git",
"ref": "canary", "ref": "canary",
"commit": "4c3f5618ffdcd08e33693a1b25d47a4ef1254d90", "commit": "cc65c414d283e22e587828c94770d9b12522bbb4",
"adapter": "skill-collection", "adapter": "skill-collection",
"sourcePath": "skills", "sourcePath": "skills",
"syncedAt": "2026-11-03T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
} }
@@ -2,8 +2,8 @@
"sourceId": "shadcn", "sourceId": "shadcn",
"repo": "https://github.com/shadcn-ui/ui.git", "repo": "https://github.com/shadcn-ui/ui.git",
"ref": "main", "ref": "main",
"commit": "4aaea19d9cff3e0908e12a8c1d504b8c5ec34610", "commit": "8a3536678a528cc012b9e62484290e79d0be410f",
"adapter": "claude-skill", "adapter": "claude-skill",
"sourcePath": "skills/shadcn", "sourcePath": "skills/shadcn",
"syncedAt": "2026-11-02T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
} }
@@ -1,6 +1,6 @@
{ {
"name": "superpowers-zh", "name": "superpowers-zh",
"version": "7.0.1-zh.1", "version": "7.0.2-zh.1",
"description": "面向 Agent 的中文技能框架与软件开发方法论:规划、TDD、系统化调试和协作工作流。", "description": "面向 Agent 的中文技能框架与软件开发方法论:规划、TDD、系统化调试和协作工作流。",
"author": { "author": {
"name": "Jesse Vincent", "name": "Jesse Vincent",
@@ -1,9 +1,9 @@
{ {
"sourceId": "superpowers-zh", "sourceId": "superpowers-zh",
"repo": "https://github.com/AreChen/superpowers-zh.git", "repo": "https://github.com/AreChen/superpowers-zh.git",
"ref": "v7.0.1-zh.1", "ref": "v7.0.2-zh.1",
"commit": "a41f784bb852f0c86d4cc891ecdb14d1344aa063", "commit": "79c797bd8c9d5273a5fba5eae5ac9ba27613d0d8",
"adapter": "codex-plugin", "adapter": "codex-plugin",
"sourcePath": ".", "sourcePath": ".",
"syncedAt": "2026-10-27T07:55:04Z" "syncedAt": "2026-11-04T09:24:25Z"
} }
@@ -23,35 +23,37 @@ description: "在进行任何创造性工作之前,你都必须使用此技能
3. 获得他们对该文档的批准。 3. 获得他们对该文档的批准。
4. 调用 superpowers:writing-plans 技能。 4. 调用 superpowers:writing-plans 技能。
如果在写第一条消息时已没有未决事项,而且只有一种合理做法,第 1 至 3 步就在这条消息中完成(见“你的第一条消息”)。
有两件事不算实现:他们已经同意的探索性试作(见“通过探索性试作(Spike)摸清情况”),以及在他们选择跳过提问之后完成他们的请求。 有两件事不算实现:他们已经同意的探索性试作(见“通过探索性试作(Spike)摸清情况”),以及在他们选择跳过提问之后完成他们的请求。
## 先了解,再提问 ## 先了解,再提问
对于已有项目,或当他们已经提供了上下文(规格说明、计划、交接材料、他们指出的文件、先前的对话)时,在发出第一条消息前先深入了解。阅读他们提及的内容,以及与之关联的代码或材料,持续阅读,直到你能解释相关部分目前如何运作,以及这次改动会涉及哪些部分。绝不要询问项目或他们提供的上下文已经回答的问题。 对于已有项目,或当他们已经提供了上下文(规格说明、计划、交接材料、他们指出的文件、先前的对话)时,在发出第一条消息前先深入了解。阅读他们提及的内容,以及与之关联的代码或材料,持续阅读,直到你能解释相关部分目前如何运作,以及这次改动会涉及哪些部分。绝不要询问项目或他们提供的上下文已经回答的问题,也不要重新讨论他们已经作出的决定。
## 你的第一条消息 ## 你的第一条消息
用以下一种方式推进工作: 用以下一种方式推进工作:
- **他们已经说了自己想要什么**(规格说明、计划、详细请求):整条消息以大约 200 词为目标。用你自己的话复述他们的请求,让他们看出你是否把握了整体:在他们已经说明的范围内,他们想要什么、为谁而做,以及为什么。补充你发现的相关情况:相关部分目前如何运作,以及这次改动会涉及哪些部分。如果你发现了他们可能未曾考虑、并且会改变他们的请求或构建方式的情况,例如约束、冲突或成本更低的路径,就直白地说出来。如何构建要等到你理解他们想要什么之后再谈(见“列出方案”)。结尾先用一行话请他们纠正,再提出一个关于他们想要什么的问题:请求中尚未明确、且其答案对构建结果影响最大的那一点。规格说明往往并不完整,因此在以它为基础继续推进之前,先加以确认。 - **他们已经说了自己想要什么**(规格说明、计划、详细请求):整条消息以大约 200 词为目标。用你自己的话复述他们的请求,让他们看出你是否把握了整体:在他们已经说明的范围内,他们想要什么、为谁而做,以及为什么。补充你发现的相关情况:相关部分目前如何运作,以及这次改动会涉及哪些部分。如果你发现了他们可能未曾考虑、并且会改变他们的请求或构建方式的情况,例如约束、冲突或成本更低的路径,就直白地说出来。如何构建要等到你理解他们想要什么之后再谈(见“列出方案”)。先用一行话请他们纠正。然后,如果他们还没有说明为什么想做这件事,即使你能猜到,也要先问原因:沿用他们的说法,提出一个开放式问题,不附选项。如果他们已经说明原因,而请求和项目仍有会影响构建结果的未决事项,就问影响最大的那一点;如果能列出选项,就具体描述每个选项、各自的权衡,以及你会选哪一个。如果没有这样的未决事项,那就只有一种合理做法:以简短设计结尾(方案、用一行说明其他方案为何不合适,以及几行设计内容),请他们批准。批准涵盖方案和设计:把获批的简短设计写成文档,再继续制定计划。规格说明往往并不完整,因此在以它为基础继续推进之前,先加以确认。
- **他们还没有说:** 用他们自己的说法,针对请求中最大的未决事项,提出一个开放式问题。说明你为什么问:答案会如何改变你构建的东西。对于“给我的面包店做一个网站”:“如果主要是给正在决定要不要来店里的人看,我会优先展示营业时间和今天展示柜里有什么;要接单的话,网站就不一样了。你希望人们来到网站后做什么?” - **他们还没有说:** 用他们自己的说法,针对请求中最大的未决事项,提出一个开放式问题。说明你为什么问:答案会如何改变你构建的东西。对于“给我的面包店做一个网站”:“如果主要是给正在决定要不要来店里的人看,我会优先展示营业时间和今天展示柜里有什么;要接单的话,网站就不一样了。你希望人们来到网站后做什么?”
消息如何结尾,取决于他们的消息是否提出了头脑风暴的要求: 消息如何结尾,取决于他们的消息是否提出了头脑风暴的要求:
- **他们提出了要求**(直接点名、调用此技能,或要求讨论设计):以你的问题结尾。他们想要这场对话。 - **他们提出了要求**(直接点名、调用此技能,或要求讨论设计):到此结束。他们想要这场对话。
- **他们没有提出要求:** 他们可能根本不想对话。在你的问题后,轻松地加上一行,表示也可以直接开始,比如“或者,如果你更愿意让我直接动手,说一声就行。”如果他们选择这样做,本技能就到此结束:用一行直白的话说明在制作方式上作出的重要选择(平台、语言、媒介),然后通过正常流程完成他们的请求。 - **他们没有提出要求:** 他们可能根本不想对话。结尾轻松地加上一行,表示也可以直接开始,比如“或者,如果你更愿意让我直接动手,说一声就行。”如果他们选择这样做,本技能就到此结束:用一行直白的话说明在制作方式上作出的重要选择(平台、语言、媒介),然后通过正常流程完成他们的请求。
整条消息仅此而已。 整条消息仅此而已。
## 对话 ## 对话
每条消息都以一个问题结尾:只用一句话,后面不要附上示例答案列表。使用浅显的语言,难度只比人类伙伴当前的水平稍高一点。沿用他们的用词;绝不要使用流程术语。你无需向你的人类伙伴引用本技能的内容。 只有当答案会改变构建结果,而且请求和项目都没有给出答案时,才提问。否则,将你的决定和理由作为他们可以纠正的建议说出来,然后推进工作。流程步骤始终由你决定:整理成文、文件存放位置、进一步探查、如何展示。直接去做,并说明你的决定。一次只问一个问题。使用浅显的语言,难度只比人类伙伴当前的水平稍高一点。沿用他们的用词;绝不要使用流程术语。你无需向你的人类伙伴引用本技能的内容。
清晰简洁。先说重要的事。避免行话。报告发现时,给出最重要的发现,并表示可以提供其余内容。 清晰简洁。先说重要的事。避免行话。报告发现时,给出最重要的发现,并表示可以提供其余内容。
**让他们描述。** 先用开放式问题。他们自己的话承载着你的选项无法承载的意图。只有在他们卡住时,才给出一组简短的选项。当他们说“你猜吧”或“你怎么看?”时,提出具体的建议。 **让他们描述。** 在知道他们为什么想做这件事之前,只问开放式问题,不附示例答案。他们自己的话承载着你的选项无法承载的意图。知道原因后,如果某项决定的选项可以列出,就具体描述每个选项、各自的权衡,以及你会选哪一个。当他们说“你猜吧”或“你怎么看?”时,提出具体的建议。
**如何制作。** 每个项目都建立在对媒介、工具、材料和资源的选择之上:应用的平台、编程语言和库;艺术作品的媒介。及早弄清这些选择中哪些他们想亲自做、哪些交给你,以及他们已经有哪些可用的东西。有明确主见或专长的人会想把这些讨论清楚。依靠你的人需要合理的默认选择,并且需要你用直白的话说出来。已有工作时,在它现有采用的东西上继续构建。你做出的任何选择都应在设计中标明是你自己的决定,绝不能写成他们同意了的内容。 **如何制作。** 每个项目都建立在对媒介、工具、材料和资源的选择之上:应用的平台、编程语言和库;艺术作品的媒介。从他们的表达中判断哪些选择他们想亲自做。主动谈制作方式或询问权衡的人,希望看到选项和你的推荐。只谈结果的人,是把制作方式交给了你:自行决定,每项用一行说明。已有工作时,在它现有采用的东西上继续构建。你做出的任何选择都应在设计中标明是你自己的决定,绝不能写成他们同意了的内容。
**需要覆盖的方面。** 这是探索范围,不是脚本。绝不要把以下内容当作问题念出来: **需要覆盖的方面。** 这是探索范围,不是脚本。绝不要把以下内容当作问题念出来:
@@ -64,11 +66,11 @@ description: "在进行任何创造性工作之前,你都必须使用此技能
**每个问题都要具体**,紧扣他们的项目和他们告诉你的内容。像一位敏锐的同事那样,以苏格拉底式的方式提问:每个问题都建立在他们上一个回答或你发现的某件事之上,帮助他们想清楚可能遗漏的后果或情形(“投票在会议前截止,而本月有两名成员加入。在本轮中途加入的人有投票权吗?”)。如果原因还不明显,就说明你希望了解什么,或者答案会改变什么。一个他们可以纠正的建议,加上你提出它的理由,往往比一个问题更有效。 **每个问题都要具体**,紧扣他们的项目和他们告诉你的内容。像一位敏锐的同事那样,以苏格拉底式的方式提问:每个问题都建立在他们上一个回答或你发现的某件事之上,帮助他们想清楚可能遗漏的后果或情形(“投票在会议前截止,而本月有两名成员加入。在本轮中途加入的人有投票权吗?”)。如果原因还不明显,就说明你希望了解什么,或者答案会改变什么。一个他们可以纠正的建议,加上你提出它的理由,往往比一个问题更有效。
**提议去探查。** 有了一些基本了解,而且进一步探查会有所帮助时,提议去看看:更深入地探查他们的代码库或项目,查看他们自己的文件和数据,或者搜索网络。说明你会查找什么。是否让你去,由他们决定。 **开展探查。** 有了一些基本了解,而且进一步探查会有所帮助时,就去看看:更深入地探查他们的代码库或项目,查看他们自己的文件和数据,或者搜索网络。说明你查找了什么,以及发现了什么。
**列出方案。** 一旦理解了他们想要什么,在把任何内容整理成文之前,单独用一条消息列出构建它的不同方式。想出几个真正的备选方案,淘汰薄弱的,包括所有与他们想做这件事的原因相冲突的,以及既有工作不允许采用的方案。展示剩下的两三个方案,每个方案都要以项目实际在做的事情为依据,并说明各自的权衡。推荐一个并说明原因,然后询问他们想走哪条路。当确实只有一种合理的方式时,就明确说明,并解释其他方式为什么不合适。这是针对如何构建而言;至于他们想要什么,先让他们描述。 **列出方案。** 一旦理解了他们想要什么,在把任何内容整理成文之前,单独用一条消息列出构建它的不同方式。想出几个真正的备选方案,淘汰薄弱的,包括所有与他们想做这件事的原因相冲突的,以及既有工作不允许采用的方案。具体描述剩下的两三个方案,让人能够想象出来,以项目实际在做的事情为依据,并说明各自的权衡。推荐一个并说明原因,然后询问他们想走哪条路;如果他们已把制作方式交给你,就改为说明你会采用推荐方案。当确实只有一种合理的方式时,在这条消息中明确说明,并解释其他方式为什么不合适;下一条消息再给出设计。这是针对如何构建而言;至于他们想要什么,先让他们描述。
**展示,而非讲述。** 只要看比读更有帮助,就提议使用可视化伴侣(见下文): **展示,而非讲述。** 只要看比读更有帮助,就用几行文字勾勒他们会看到的内容。当外观本身需要由他们决定时,再提议使用可视化伴侣(见下文):
- 他们会看的东西:界面、页面、打印件、标牌 - 他们会看的东西:界面、页面、打印件、标牌
- 布局或安排:界面、房间、日程 - 布局或安排:界面、房间、日程
@@ -86,7 +88,7 @@ description: "在进行任何创造性工作之前,你都必须使用此技能
只有他们说过的内容,才能写成他们的想法。凡是你补充的内容,都要标明是你的建议,并附上理由。每段结尾都要问有什么不对或遗漏。 只有他们说过的内容,才能写成他们的想法。凡是你补充的内容,都要标明是你的建议,并附上理由。每段结尾都要问有什么不对或遗漏。
发送一段复述之前,先检查:它是否描述了他们会看的东西,比如页面、界面、日程表或打印件?如果是,而且他们还没有看到过可视化伴侣的提议,就改为发送提议(下方所示,单独一条消息),把这段复述留到下一条消息。一旦他们接受,就在描述旁放上一个原型图。 发送一段复述之前,先检查:它是否描述了他们会看的东西,比如页面、界面、日程表或打印件?如果是,就在描述旁放上一份文字草图。如果外观本身需要由他们决定,而且他们还没有看到过可视化伴侣的提议,就改为发送提议(下方所示,单独一条消息),把这段复述留到下一条消息。一旦他们接受,就在描述旁放上一个原型图。
## 确定工作规模 ## 确定工作规模
@@ -118,7 +120,7 @@ description: "在进行任何创造性工作之前,你都必须使用此技能
**存放位置:** 在软件仓库中,保存到 **存放位置:** 在软件仓库中,保存到
`docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` 并提交。 `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` 并提交。
其他情况,询问他们。人类伙伴的偏好优先于上述两种安排。 其他情况,选择合理的位置,并说明放在哪里。人类伙伴的偏好优先于上述两种安排。
**实现者检查**(针对项目):使用本目录中的 **实现者检查**(针对项目):使用本目录中的
`builder-check-prompt.md` 派发一个全新的子 Agent。当你告诉人类伙伴你正在整理成文时,说明会先由一位审查者阅读,并且你可能会再回来一次,问几个问题。检查结果返回前,不要交付文档。把实现者提出的问题分为三类: `builder-check-prompt.md` 派发一个全新的子 Agent。当你告诉人类伙伴你正在整理成文时,说明会先由一位审查者阅读,并且你可能会再回来一次,问几个问题。检查结果返回前,不要交付文档。把实现者提出的问题分为三类:
@@ -143,7 +145,7 @@ description: "在进行任何创造性工作之前,你都必须使用此技能
|---------|---------| |---------|---------|
| “我会提供一组他们可能想要的选项” | 选项会引导方向。先让他们描述。 | | “我会提供一组他们可能想要的选项” | 选项会引导方向。先让他们描述。 |
| “我会补上合理的默认选择” | 很好。把它们说出来。他们从未听说的默认选择,他们也就无法拒绝。 | | “我会补上合理的默认选择” | 很好。把它们说出来。他们从未听说的默认选择,他们也就无法拒绝。 |
| “我已经回答了他们的问题,所以要开始写文档了” | 先单独用一条消息列出方案。 | | “我已经回答了他们的问题,所以要开始写文档了” | 先单独用一条消息列出方案(如果一开始就没有未决事项,则在第一条消息的简短设计中列出)。 |
| “我应该先解释流程” | 提出你的问题。流程会自然显现。 | | “我应该先解释流程” | 提出你的问题。流程会自然显现。 |
| “他们对这个想法说了‘听起来不错’” | 批准只涵盖你向他们展示过的内容。还没写出来的设计文档不算获批。 | | “他们对这个想法说了‘听起来不错’” | 批准只涵盖你向他们展示过的内容。还没写出来的设计文档不算获批。 |
| “探索性试作能行,我会继续在它的基础上构建” | 保留它是一个新请求。重新经过关卡。 | | “探索性试作能行,我会继续在它的基础上构建” | 保留它是一个新请求。重新经过关卡。 |
@@ -114,6 +114,13 @@ umask 077
SESSION_ID="$$-$(date +%s)" SESSION_ID="$$-$(date +%s)"
if [[ -n "$PROJECT_DIR" ]]; then if [[ -n "$PROJECT_DIR" ]]; then
# Make the project dir absolute now: the script cds into its own directory
# before starting the server, which would re-root a relative path there.
# mkdir -p keeps the existing behavior of creating a missing project dir.
if ! PROJECT_DIR="$(mkdir -p -- "$PROJECT_DIR" && CDPATH='' cd -- "$PROJECT_DIR" && pwd)"; then
echo "{\"error\": \"Cannot create or enter --project-dir\"}"
exit 1
fi
SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
# Persist the bound port and key per project so a restart reuses them and an # Persist the bound port and key per project so a restart reuses them and an
# already-open browser tab reconnects to the same URL with a valid cookie. # already-open browser tab reconnects to the same URL with a valid cookie.
@@ -79,7 +79,7 @@ digraph process {
工作区和进度账本与 superpowers:subagent-driven-development 共享——相同的目录,相同的格式——因此,计划可以在执行途中更换执行者,新执行者能从同一份进度账本接着执行。 工作区和进度账本与 superpowers:subagent-driven-development 共享——相同的目录,相同的格式——因此,计划可以在执行途中更换执行者,新执行者能从同一份进度账本接着执行。
- 每份计划都有自己的工作区:在技能启动时,运行 `../subagent-driven-development/scripts/sdd-workspace PLAN_FILE`——它会打印该计划被 git 忽略的目录(`<repo-root>/.superpowers/sdd/<plan-basename>/`),本计划的所有产物都放在这里:进度账本、简报、审查包。其他计划的目录绝不属于你的读写范围。 - 每份计划各有工作区:技能启动时,运行 `../subagent-driven-development/scripts/sdd-workspace PLAN_FILE`,在当前工作树中使用绝对路径。它会打印计划被 git 忽略的目录(`<repo-root>/.superpowers/sdd/<plan-basename>/`),存放本计划的所有产物:进度账本、简报、审查包。其他计划的目录绝不可读写。
- 在 `<workspace>/progress.md` 检查本计划的进度账本。如果第一行指定的是你的计划文件,那么带有 `Task <N>: complete` 行的任务就是 DONE——不要重做;从第一个没有该行的任务继续。即使你的上下文已经不记得创建过那些提交,它们仍然存在于 git 中:在上下文压缩后,相比自己的回忆,应相信进度账本和 `git log`。如果进度账本第一行指定的是另一份计划文件,那就是其他计划的进度:不要动它,重新创建你自己的进度账本。 - 在 `<workspace>/progress.md` 检查本计划的进度账本。如果第一行指定的是你的计划文件,那么带有 `Task <N>: complete` 行的任务就是 DONE——不要重做;从第一个没有该行的任务继续。即使你的上下文已经不记得创建过那些提交,它们仍然存在于 git 中:在上下文压缩后,相比自己的回忆,应相信进度账本和 `git log`。如果进度账本第一行指定的是另一份计划文件,那就是其他计划的进度:不要动它,重新创建你自己的进度账本。
- 创建进度账本,并将其身份标识作为第一行:`# SDD ledger — plan: <plan file path>`。 - 创建进度账本,并将其身份标识作为第一行:`# SDD ledger — plan: <plan file path>`。
- `git clean -fdx` 会摧毁工作区(它是被 git 忽略的临时存储区);如果发生这种情况,从 `git log` 恢复。 - `git clean -fdx` 会摧毁工作区(它是被 git 忽略的临时存储区);如果发生这种情况,从 `git log` 恢复。
@@ -96,7 +96,7 @@ digraph process {
### 1. 接手任务 ### 1. 接手任务
- 运行本技能的 `scripts/task-start PLAN_FILE N`。它会在一次调用中打印简报路径和 BASE(用于划定该任务提交范围的起始提交)。每个任务都要阅读简报,包括那些你在准备阶段就记得的任务:你记得的是摘要,简报中才有准确的值、签名和测试用例。 - 运行本技能的 `scripts/task-start PLAN_FILE N`,在当前工作树中使用绝对路径。一次调用即打印简报路径和 BASE(任务提交范围的起始提交)。每个任务都要读简报,包括准备阶段已记得的任务:你记得的是摘要,简报才有准确的值、签名和测试用例。
- 将该任务的待办事项标记为 in_progress。 - 将该任务的待办事项标记为 in_progress。
每次工具调用都是一个会重新读取你全部上下文的轮次。记账要与工作一起进行——在提交的同一次调用中追加进度账本,绝不为追加进度账本单独发起调用。 每次工具调用都是一个会重新读取你全部上下文的轮次。记账要与工作一起进行——在提交的同一次调用中追加进度账本,绝不为追加进度账本单独发起调用。
@@ -126,7 +126,7 @@ digraph process {
### 4. 完成任务 ### 4. 完成任务
运行本技能的 `scripts/task-done PLAN_FILE N BASE -- <test command>`,使用简报为整个任务指定的测试命令。它会运行测试,将完整输出保存在工作区中,打印末尾内容,并且——只有测试通过时——才向进度账本追加完成行: 运行本技能的 `scripts/task-done PLAN_FILE N BASE -- <test command>`,在当前工作树中使用绝对路径,测试命令采用简报为整个任务指定的命令。它运行测试,在工作区保存完整输出、打印末尾内容,且只有通过时才向进度账本追加完成行:
`Task <N>: complete (commits <base7>..<head7>, tests: <command> → <result>)` `Task <N>: complete (commits <base7>..<head7>, tests: <command> → <result>)`
@@ -134,7 +134,7 @@ digraph process {
## 最终审查 ## 最终审查
运行 `../subagent-driven-development/scripts/review-package PLAN_FILE MERGE_BASE HEAD`(MERGE_BASE = 分支开始时的提交,例如 `git merge-base main HEAD`),并依据它打印出的文件进行审查。 运行 `../subagent-driven-development/scripts/review-package PLAN_FILE MERGE_BASE HEAD`,在当前工作树中使用绝对路径(MERGE_BASE = 分支起始提交,例如 `git merge-base main HEAD`),依据它打印的文件审查。
**有子 Agent 工具时:**使用可用的最强模型分派审查者——整个分支的审查是一项判断任务——并使用 superpowers:requesting-code-review 的 [code-reviewer.md](../requesting-code-review/code-reviewer.md),提供审查包路径、计划和规格说明路径、计划的审查重点章节原文(如果有;其中列出了计划的测试没有覆盖的输入类别和失败模式——审查者要有意识地逐项检查),以及指向进度账本中 `Ruling:` 行的位置,让它能够权衡你作出的决定。明确指定模型;省略模型会继承会话所用的模型,而它未必是最强的。这是整个执行过程唯一付费获得的全新上下文。不要跳过它,也不要用你自己阅读 diff 来替代它。 **有子 Agent 工具时:**使用可用的最强模型分派审查者——整个分支的审查是一项判断任务——并使用 superpowers:requesting-code-review 的 [code-reviewer.md](../requesting-code-review/code-reviewer.md),提供审查包路径、计划和规格说明路径、计划的审查重点章节原文(如果有;其中列出了计划的测试没有覆盖的输入类别和失败模式——审查者要有意识地逐项检查),以及指向进度账本中 `Ruling:` 行的位置,让它能够权衡你作出的决定。明确指定模型;省略模型会继承会话所用的模型,而它未必是最强的。这是整个执行过程唯一付费获得的全新上下文。不要跳过它,也不要用你自己阅读 diff 来替代它。
@@ -20,7 +20,10 @@ base=$3
shift 4 shift 4
sdd="$(cd "$(dirname "$0")/../../subagent-driven-development/scripts" && pwd)" sdd="$(cd "$(dirname "$0")/../../subagent-driven-development/scripts" && pwd)"
git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; } # The repository the workspace and ledger belong to (see sdd-repo); BASE and
# HEAD are read there, not in the bare cwd.
repo=$("${BASH:-bash}" "$sdd/sdd-repo" --with-commit)
git -C "$repo" rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
dir=$("$sdd/sdd-workspace" "$plan") dir=$("$sdd/sdd-workspace" "$plan")
log="$dir/task-${n}-tests.log" log="$dir/task-${n}-tests.log"
@@ -47,6 +50,6 @@ fi
last=$(grep -v '^[[:space:]]*$' "$log" | tail -n 1) || last="(no output)" last=$(grep -v '^[[:space:]]*$' "$log" | tail -n 1) || last="(no output)"
[ -f "$ledger" ] || printf '# SDD ledger — plan: %s\n' "$plan" > "$ledger" [ -f "$ledger" ] || printf '# SDD ledger — plan: %s\n' "$plan" > "$ledger"
line="Task $n: complete (commits $(git rev-parse --short=7 "$base")..$(git rev-parse --short=7 HEAD), tests: $cmd → $last)" line="Task $n: complete (commits $(git -C "$repo" rev-parse --short=7 "$base")..$(git -C "$repo" rev-parse --short=7 HEAD), tests: $cmd → $last)"
printf '%s\n' "$line" >> "$ledger" printf '%s\n' "$line" >> "$ledger"
echo "ledger: $line" echo "ledger: $line"
@@ -20,9 +20,13 @@ plan=$1
n=$2 n=$2
sdd="$(cd "$(dirname "$0")/../../subagent-driven-development/scripts" && pwd)" sdd="$(cd "$(dirname "$0")/../../subagent-driven-development/scripts" && pwd)"
# BASE comes from the same repository the brief is written into (sdd-repo
# also refuses one with no commit yet), not from the bare cwd.
repo=$("${BASH:-bash}" "$sdd/sdd-repo" --with-commit)
out=$("$sdd/task-brief" "$plan" "$n") out=$("$sdd/task-brief" "$plan" "$n")
brief=$(printf '%s\n' "$out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p') brief=$(printf '%s\n' "$out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')
[ -n "$brief" ] || { echo "task-brief did not report a path: $out" >&2; exit 1; } [ -n "$brief" ] || { echo "task-brief did not report a path: $out" >&2; exit 1; }
base=$(git -C "$repo" rev-parse --verify HEAD)
echo "brief: $brief" echo "brief: $brief"
echo "base: $(git rev-parse HEAD)" echo "base: $base"
@@ -99,8 +99,8 @@ superpowers:using-git-worktrees 创建一个工作区,或验证现有工作区
控制器曾重新分派整套已经完成的任务序列——这是观察到的代价 控制器曾重新分派整套已经完成的任务序列——这是观察到的代价
最高的单一故障。请在台账文件中跟踪进度,而不要仅依赖待办事项。 最高的单一故障。请在台账文件中跟踪进度,而不要仅依赖待办事项。
- 每个计划都拥有自己的工作区:技能启动时,运行本技能的 - 每个计划各有工作区:技能启动时,运行本技能的
`bash scripts/sdd-workspace PLAN_FILE`——它会输出该计划被 git 忽略的 `bash scripts/sdd-workspace PLAN_FILE`,在当前工作树中使用绝对路径。它会输出计划被 git 忽略的
目录(位于 `<repo-root>/.superpowers/sdd/` 下),本计划的 目录(位于 `<repo-root>/.superpowers/sdd/` 下),本计划的
所有产物都存放于此:台账、简报、报告、复审包。 所有产物都存放于此:台账、简报、报告、复审包。
绝不得读取或写入另一个计划的目录。 绝不得读取或写入另一个计划的目录。
@@ -156,8 +156,8 @@ superpowers:using-git-worktrees 创建一个工作区,或验证现有工作区
派遣前记录 BASE(`git rev-parse HEAD`)——审查包和修复轮次的 diff 都需要它。 派遣前记录 BASE(`git rev-parse HEAD`)——审查包和修复轮次的 diff 都需要它。
- **任务简报:**派遣实现者之前,运行本技能的 - **任务简报:**派遣实现者前,运行本技能的
`bash scripts/task-brief PLAN_FILE N`——它会将任务的完整文本提取到一个具有唯一名称的文件中,并打印该路径。组织派遣内容时,应让简报始终作为需求的唯一来源。你的派遣内容应包含:(1) 用一行说明此任务在项目中的位置;(2) 简报路径,并以“先阅读此文件——它就是你的需求,其中包含必须原样使用的精确值”引出;(3) 简报不可能知道的、来自先前任务的接口和决策;(4) 你对简报中所注意到的任何歧义的解决方案;(5) 报告文件路径和报告约定。精确值(数字、魔法字符串、签名、测试用例)只能出现在简报中。绝不要让子 Agent 阅读整个计划文件。 `bash scripts/task-brief PLAN_FILE N`,在当前工作树中使用绝对路径。它将完整任务提取到唯一命名的文件并打印路径。简报始终是需求的唯一来源。派遣内容应包含:(1) 一行说明任务在项目中的位置;(2) 简报路径,以“先阅读此文件——它就是你的需求,其中包含必须原样使用的精确值”引出;(3) 简报无法知道的先前任务接口和决策;(4) 你对简报中歧义的解决方案;(5) 报告文件路径和报告约定。精确值(数字、魔法字符串、签名、测试用例)只能出现在简报中。绝不要让子 Agent 阅读整个计划文件。
- **报告文件:**以简报为实现者的报告文件命名(简报 `…/task-N-brief.md` → 报告 `…/task-N-report.md`),并将其写入派遣提示。实现者在该文件中写入完整报告,并且只返回状态、提交记录、一行测试摘要和关注事项。 - **报告文件:**以简报为实现者的报告文件命名(简报 `…/task-N-brief.md` → 报告 `…/task-N-report.md`),并将其写入派遣提示。实现者在该文件中写入完整报告,并且只返回状态、提交记录、一行测试摘要和关注事项。
- 一条派遣提示描述的是一个任务,而不是会话的历史。不要把累积的先前任务摘要(“任务 1-3 后的状态”)粘贴到后续派遣中——某个真实会话的派遣内容达到了 42k 个字符,其中 99% 都是粘贴的历史记录。一个全新的子 Agent 需要的是它的任务、它会涉及的接口以及全局约束。除此之外什么都不需要。 - 一条派遣提示描述的是一个任务,而不是会话的历史。不要把累积的先前任务摘要(“任务 1-3 后的状态”)粘贴到后续派遣中——某个真实会话的派遣内容达到了 42k 个字符,其中 99% 都是粘贴的历史记录。一个全新的子 Agent 需要的是它的任务、它会涉及的接口以及全局约束。除此之外什么都不需要。
- 派遣内容携带“不派遣子 Agent”的契约(该契约位于实现者模板中):实现者绝不派遣子 Agent——既不派助手,也绝不派审查者。审查会在它报告后由你负责派遣。真实会话中,实现者自行派出的每个审查者都重复了控制器已经派出的任务审查,等于每项任务多花一张完整审查席位。 - 派遣内容携带“不派遣子 Agent”的契约(该契约位于实现者模板中):实现者绝不派遣子 Agent——既不派助手,也绝不派审查者。审查会在它报告后由你负责派遣。真实会话中,实现者自行派出的每个审查者都重复了控制器已经派出的任务审查,等于每项任务多花一张完整审查席位。
@@ -171,7 +171,7 @@ superpowers:using-git-worktrees 创建一个工作区,或验证现有工作区
实现者子 Agent 会报告以下四种状态之一。请分别妥善处理: 实现者子 Agent 会报告以下四种状态之一。请分别妥善处理:
**DONE:** 生成审查包(`bash scripts/review-package PLAN_FILE BASE HEAD`,从本技能的目录运行——它会输出其写入的唯一文件路径;BASE 是你在派遣实现者之前记录的提交——绝不能使用 `HEAD~1`,因为这会悄无声息地丢弃多提交任务中除最后一个提交之外的所有提交),然后使用输出的路径派遣任务审查者。 **DONE:** 生成审查包(`bash scripts/review-package PLAN_FILE BASE HEAD`,在当前工作树中以绝对路径运行——输出写入的唯一文件路径;BASE 是派遣实现者前记录的提交——绝不能用 `HEAD~1`,它会悄悄丢弃多提交任务中除最后一次外的所有提交),然后将该路径交给任务审查者。
**其他任何状态**(DONE_WITH_CONCERNS、NEEDS_CONTEXT、BLOCKED):每次收到时,都要阅读 [references/handling-reports.md](references/handling-reports.md) 并遵循其中的指导。 **其他任何状态**(DONE_WITH_CONCERNS、NEEDS_CONTEXT、BLOCKED):每次收到时,都要阅读 [references/handling-reports.md](references/handling-reports.md) 并遵循其中的指导。
@@ -184,7 +184,7 @@ superpowers:using-git-worktrees 创建一个工作区,或验证现有工作区
逐任务审查是限定于任务范围的关卡。广泛审查只进行一次,即在最终的整个分支审查时进行。绝不要跳过任务审查,也绝不要接受缺少任一结论的报告——规范符合性和任务质量两者都必须具备。实现者自我审查绝不能取代任务审查;两者都必不可少。 逐任务审查是限定于任务范围的关卡。广泛审查只进行一次,即在最终的整个分支审查时进行。绝不要跳过任务审查,也绝不要接受缺少任一结论的报告——规范符合性和任务质量两者都必须具备。实现者自我审查绝不能取代任务审查;两者都必不可少。
- 将 diff 作为文件交给审查者:运行本技能的 - 将 diff 作为文件交给审查者:运行本技能的
`bash scripts/review-package PLAN_FILE BASE HEAD`,并将它输出的文件路径传给审查者 `bash scripts/review-package PLAN_FILE BASE HEAD`,在当前工作树中使用绝对路径,并将输出的文件路径传给审查者
(或者,不使用 bash 时:针对该范围运行 `git log --oneline`、`git diff --stat` (或者,不使用 bash 时:针对该范围运行 `git log --oneline`、`git diff --stat`
和 `git diff -U10`,并将输出重定向到一个名称唯一的 和 `git diff -U10`,并将输出重定向到一个名称唯一的
文件)。该输出绝不会进入你自己的上下文,而审查者只需一次 Read 文件)。该输出绝不会进入你自己的上下文,而审查者只需一次 Read
@@ -96,6 +96,7 @@
- `[FIX_BASE_SHA]` — 上一次审查看到的 head - `[FIX_BASE_SHA]` — 上一次审查看到的 head
- `[HEAD_SHA]` — 当前提交 - `[HEAD_SHA]` — 当前提交
- `[DIFF_FILE]` — `bash scripts/review-package PLAN_FILE FIX_BASE HEAD` 打印出的路径 - `[DIFF_FILE]` — `bash scripts/review-package PLAN_FILE FIX_BASE HEAD` 打印出的路径
(在当前工作树中以绝对路径运行)
**复审者返回:** 每个发现项的裁定(ADDRESSED / NOT ADDRESSED)、修复 diff **复审者返回:** 每个发现项的裁定(ADDRESSED / NOT ADDRESSED)、修复 diff
中的新破坏、范围外观察项,以及该轮裁定。 中的新破坏、范围外观察项,以及该轮裁定。
@@ -2,9 +2,9 @@
这是 superpowers:subagent-driven-development 的一部分。计划中的所有任务均已完成。 这是 superpowers:subagent-driven-development 的一部分。计划中的所有任务均已完成。
最终的整分支审查也要有一个审查包:运行 `bash scripts/review-package PLAN_FILE MERGE_BASE HEAD`(MERGE_BASE = 该分支起始于的提交,例如 `git merge-base main HEAD`),并在派发最终审查时包含输出的路径,这样最终审查者只需读取一个文件,而不必使用 git 命令重新推导分支差异。使用最强的可用模型进行派发(参见 SKILL.md 中的模型选择),并使用 superpowers:requesting-code-review 的 [code-reviewer.md](../../requesting-code-review/code-reviewer.md)。让它查看账本中的 deferred-minor 行和 parked 行,以便对哪些项目必须在合并前修复进行分诊。 最终的整分支审查也要有一个审查包:运行 `bash scripts/review-package PLAN_FILE MERGE_BASE HEAD`,在当前工作树中使用绝对路径(MERGE_BASE = 该分支起始于的提交,例如 `git merge-base main HEAD`),并在派发最终审查时包含输出的路径,这样最终审查者只需读取一个文件,而不必使用 git 命令重新推导分支差异。使用最强的可用模型进行派发(参见 SKILL.md 中的模型选择),并使用 superpowers:requesting-code-review 的 [code-reviewer.md](../../requesting-code-review/code-reviewer.md)。让它查看账本中的 deferred-minor 行和 parked 行,以便对哪些项目必须在合并前修复进行分诊。
如果最终的整分支审查返回发现项,请派发一个且仅一个修复子 Agent,并向其提供完整的发现项列表——不要为每个发现项分别派一个修复者。按发现项分别安排的修复者都要重建上下文并重新运行套件;在一次真实会话中,最终审查的修复波次成本超过了其所有任务的总和。然后,对该修复波次恰好进行一次限定范围的复审(针对修复范围运行 `bash scripts/review-package PLAN_FILE FIX_BASE HEAD`,[re-review-prompt.md](../re-review-prompt.md))。按照修复循环中的熔断器方式(references/fix-loop.md)裁决任何残留发现项:附裁定搁置,或裁决承重性发现项并记录你的决定。这里只能因会让执行中的计划停止的那四类情况(见 SKILL.md 开头)停止。不存在第二个修复波次——当 finishing-a-development-branch 呈现选项时,残留的承重性发现项会提交给你的人类伙伴。 如果最终的整分支审查返回发现项,请派发一个且仅一个修复子 Agent,并向其提供完整的发现项列表——不要为每个发现项分别派一个修复者。按发现项分别安排的修复者都要重建上下文并重新运行套件;在一次真实会话中,最终审查的修复波次成本超过了其所有任务的总和。然后,对该修复波次恰好进行一次限定范围的复审(针对修复范围运行 `bash scripts/review-package PLAN_FILE FIX_BASE HEAD`,在当前工作树中使用绝对路径,[re-review-prompt.md](../re-review-prompt.md))。按照修复循环中的熔断器方式(references/fix-loop.md)裁决任何残留发现项:附裁定搁置,或裁决承重性发现项并记录你的决定。这里只能因会让执行中的计划停止的那四类情况(见 SKILL.md 开头)停止。不存在第二个修复波次——当 finishing-a-development-branch 呈现选项时,残留的承重性发现项会提交给你的人类伙伴。
### 收尾 ### 收尾
@@ -15,7 +15,7 @@
**每一轮,无论是哪种情况:** 实现者进行修复,重新运行覆盖已修改代码的测试,将其修复报告追加到同一个报告文件中,并返回简短契约。在重新派发审查者之前,确认修复报告包含覆盖已修改代码的测试、所运行的命令和输出;三者全部具备后,再派发复审。在修复消息中列出覆盖测试文件的名称——单行修复不需要整个测试套件。 **每一轮,无论是哪种情况:** 实现者进行修复,重新运行覆盖已修改代码的测试,将其修复报告追加到同一个报告文件中,并返回简短契约。在重新派发审查者之前,确认修复报告包含覆盖已修改代码的测试、所运行的命令和输出;三者全部具备后,再派发复审。在修复消息中列出覆盖测试文件的名称——单行修复不需要整个测试套件。
**复审是作用域化的。** 运行 `bash scripts/review-package PLAN_FILE FIX_BASE HEAD`,其中 FIX_BASE 是上一次审查所看到的提交头,并派发 [re-review-prompt.md](../re-review-prompt.md),同时提供发现项列表、简报、报告文件和打印出的 diff 路径。复审者将每个发现项裁定为 ADDRESSED 或 NOT ADDRESSED,并且只标记修复 diff 中的新破坏。修复 diff 中新的 Critical/Important 破坏会加入未解决的发现项列表。超出作用域的观察项会作为延期次要项记入账本——它们绝不会延长循环。 **复审是作用域化的。** 运行 `bash scripts/review-package PLAN_FILE FIX_BASE HEAD`,在当前工作树中使用绝对路径,其中 FIX_BASE 是上一次审查所看到的提交头,并派发 [re-review-prompt.md](../re-review-prompt.md),同时提供发现项列表、简报、报告文件和打印出的 diff 路径。复审者将每个发现项裁定为 ADDRESSED 或 NOT ADDRESSED,并且只标记修复 diff 中的新破坏。修复 diff 中新的 Critical/Important 破坏会加入未解决的发现项列表。超出作用域的观察项会作为延期次要项记入账本——它们绝不会延长循环。
**每轮结束后,**向账本追加: **每轮结束后,**向账本追加:
`Task <N>: fix round <R>/5 (<X> addressed, <Y> open — <finding one-liners>; commits <a7>..<b7>)` `Task <N>: fix round <R>/5 (<X> addressed, <Y> open — <finding one-liners>; commits <a7>..<b7>)`
@@ -4,13 +4,22 @@
# call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit # call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
# tasks intact. # tasks intact.
# #
# Usage: review-package PLAN_FILE BASE HEAD [OUTFILE] # Usage: review-package [-C DIR] PLAN_FILE BASE HEAD [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff
# (named per range, so a re-review after fixes gets a distinct fresh file). # (named per range, so a re-review after fixes gets a distinct fresh file).
# The repository is the one sdd-repo names; -C DIR runs as if started in DIR,
# as in sdd-workspace.
set -euo pipefail set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
if [ "${1:-}" = "-C" ]; then
[ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
cd "$2"
shift 2
fi
if [ $# -lt 3 ] || [ $# -gt 4 ]; then if [ $# -lt 3 ] || [ $# -gt 4 ]; then
echo "usage: review-package PLAN_FILE BASE HEAD [OUTFILE]" >&2 echo "usage: review-package [-C DIR] PLAN_FILE BASE HEAD [OUTFILE]" >&2
exit 2 exit 2
fi fi
@@ -19,35 +28,37 @@ base=$2
head=$3 head=$3
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; } [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; } # Invoke via bash rather than direct exec: some extractors (Python zipfile)
git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; } # strip Unix exec bits when unpacking marketplace packages (#2040).
repo=$("${BASH:-bash}" "$here/sdd-repo" --with-commit)
git -C "$repo" rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
git -C "$repo" rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
# Range guards (exit 3): a wrong-branch HEAD yields a range that is empty or # Range guards (exit 3): a wrong-branch HEAD yields a range that is empty or
# not rooted at BASE; either would silently produce a bogus review package. # not rooted at BASE; either would silently produce a bogus review package.
git merge-base --is-ancestor "$base" "$head" || { echo "HEAD is not a descendant of BASE: ${base}..${head}" >&2; exit 3; } git -C "$repo" merge-base --is-ancestor "$base" "$head" || { echo "HEAD is not a descendant of BASE: ${base}..${head}" >&2; exit 3; }
[ "$(git rev-list --count "${base}..${head}")" -gt 0 ] || { echo "empty commit range: ${base}..${head}" >&2; exit 3; } [ "$(git -C "$repo" rev-list --count "${base}..${head}")" -gt 0 ] || { echo "empty commit range: ${base}..${head}" >&2; exit 3; }
if [ $# -eq 4 ]; then if [ $# -eq 4 ]; then
out=$4 out=$4
else else
# Invoke via bash rather than direct exec: some extractors (Python zipfile) dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan")
# strip Unix exec bits when unpacking marketplace packages (#2040). out="$dir/review-$(git -C "$repo" rev-parse --short "$base")..$(git -C "$repo" rev-parse --short "$head").diff"
dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
fi fi
{ {
echo "# Review package: ${base}..${head}" echo "# Review package: ${base}..${head}"
echo echo
echo "## Commits" echo "## Commits"
git log --oneline "${base}..${head}" git -C "$repo" log --oneline "${base}..${head}"
echo echo
echo "## Files changed" echo "## Files changed"
git diff --stat "${base}..${head}" git -C "$repo" diff --stat "${base}..${head}"
echo echo
echo "## Diff" echo "## Diff"
git diff -U10 "${base}..${head}" git -C "$repo" diff -U10 "${base}..${head}"
} > "$out" } > "$out"
commits=$(git rev-list --count "${base}..${head}") commits=$(git -C "$repo" rev-list --count "${base}..${head}")
echo "wrote ${out}: ${commits} commit(s), $(wc -c < "$out" | tr -d ' ') bytes" echo "wrote ${out}: ${commits} commit(s), $(wc -c < "$out" | tr -d ' ') bytes"
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Print the top-level directory of the git working tree the SDD scripts use:
# the current directory's, exactly as git itself picks one. A caller that
# means a different working tree says so with the scripts' -C DIR, which
# they apply (cd) before calling this.
#
# The plan file's location never picks the repository. An executor working
# in a linked worktree commonly holds an absolute path to the plan in the
# main checkout (writing-plans leaves the plan uncommitted there), and its
# workspace and commits belong to the worktree it is working in. And a
# caller standing outside any repository, such as the skill's own directory
# in an installed plugin, is in the wrong place: guessing a repository from
# the plan would silently act on whichever checkout the path points into.
#
# Outside any working tree, stop. --with-commit also stops on a repository
# with no commit yet, for callers that need HEAD as a BASE or a review
# range. The message covers both ways to get here: running from the wrong
# directory (where 'git init' would be the wrong fix) and a new project
# that has no repository or commit yet.
#
# Single source of truth for which repository the SDD and executing-plans
# scripts work in, so no two of them can disagree.
#
# Usage: sdd-repo [--with-commit]
set -euo pipefail
with_commit=false
if [ "${1:-}" = "--with-commit" ]; then
with_commit=true
shift
fi
if [ $# -ne 0 ]; then
echo "usage: sdd-repo [--with-commit]" >&2
exit 2
fi
if root=$(git rev-parse --show-toplevel 2>/dev/null); then
if [ "$with_commit" = false ] || git -C "$root" rev-parse --verify --quiet HEAD >/dev/null; then
printf '%s\n' "$root"
exit 0
fi
said=$(git -C "$root" rev-parse --verify HEAD 2>&1 >/dev/null) || true
else
said=$(git rev-parse --show-toplevel 2>&1 >/dev/null) || true
fi
# git's own first line always follows the shared message: where git refuses
# a working tree the caller is inside (dubious ownership, git missing from
# PATH, the cwd inside .git), the shared message alone would be wrong, and
# git's line names the real fix.
echo "SDD scripts must run from inside the git working tree you're executing in, with at least one commit (current directory: $PWD). If this is a new project, run 'git init' and make a first commit, then rerun." >&2
echo "git said: ${said%%$'\n'*}" >&2
exit 2
@@ -27,11 +27,23 @@
# Single source of truth for the workspace location, so task-brief and # Single source of truth for the workspace location, so task-brief and
# review-package cannot drift to different directories. # review-package cannot drift to different directories.
# #
# Usage: sdd-workspace PLAN_FILE # The repository is the current directory's (see sdd-repo). -C DIR runs as
# if started in DIR, the way git -C does: a caller working on another
# checkout or worktree needs no `cd DIR ;` prefix, a compound that harnesses
# cannot allowlist because DIR differs on every run (#1799).
#
# Usage: sdd-workspace [-C DIR] PLAN_FILE
set -euo pipefail set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
if [ "${1:-}" = "-C" ]; then
[ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
cd "$2"
shift 2
fi
if [ $# -ne 1 ]; then if [ $# -ne 1 ]; then
echo "usage: sdd-workspace PLAN_FILE" >&2 echo "usage: sdd-workspace [-C DIR] PLAN_FILE" >&2
exit 2 exit 2
fi fi
@@ -44,8 +56,12 @@ slug=$(basename "$plan" .md)
# Physical path, spelled the way pwd -P spells the plan path below: on Git # Physical path, spelled the way pwd -P spells the plan path below: on Git
# Bash, rev-parse prints C:/Users/... while pwd prints /c/Users/..., and the # Bash, rev-parse prints C:/Users/... while pwd prints /c/Users/..., and the
# repo-relative marker check needs both in the same form. # repo-relative marker check needs both in the same form. Two steps, so a
root=$(CDPATH='' cd -- "$(git rev-parse --show-toplevel)" && pwd -P) # failing sdd-repo stops the script instead of handing cd an empty path.
# Invoke via bash rather than direct exec: some extractors (Python zipfile)
# strip Unix exec bits when unpacking marketplace packages (#2040).
toplevel=$("${BASH:-bash}" "$here/sdd-repo")
root=$(CDPATH='' cd -- "$toplevel" && pwd -P)
base="$root/.superpowers/sdd" base="$root/.superpowers/sdd"
# Normalize the plan path (physical directory, so relative/absolute/../ # Normalize the plan path (physical directory, so relative/absolute/../
@@ -84,4 +100,11 @@ fi
# Create-only: a repo may commit its own .gitignore here (e.g. to version # Create-only: a repo may commit its own .gitignore here (e.g. to version
# progress.md ledgers); overwriting it would revert that on every run. # progress.md ledgers); overwriting it would revert that on every run.
[ -e "$base/.gitignore" ] || printf '*\n' > "$base/.gitignore" [ -e "$base/.gitignore" ] || printf '*\n' > "$base/.gitignore"
CDPATH='' cd -- "$dir" && pwd
# On Windows (Git Bash, MSYS2, Cygwin) print C:/Users/... rather than
# /c/Users/... or /tmp/...: the caller may be PowerShell or another native
# tool, which can't resolve MSYS paths, and bash accepts this form too.
# Only the printed path changes; the plan-path markers above keep pwd -P's
# spelling, so existing workspaces still match their plans.
CDPATH='' cd -- "$dir"
if command -v cygpath >/dev/null 2>&1; then cygpath -m "$PWD"; else pwd; fi
@@ -3,14 +3,21 @@
# implementer reads in one call, so the task text never has to be pasted # implementer reads in one call, so the task text never has to be pasted
# through the controller's context. # through the controller's context.
# #
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE] # Usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
# (per plan and per worktree; concurrent runs of the SAME plan in the same # (per plan and per worktree; concurrent runs of the SAME plan in the same
# working tree share it). # working tree share it). -C DIR runs as if started in DIR, as in sdd-workspace.
set -euo pipefail set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
if [ "${1:-}" = "-C" ]; then
[ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
cd "$2"
shift 2
fi
if [ $# -lt 2 ] || [ $# -gt 3 ]; then if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]" >&2 echo "usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
exit 2 exit 2
fi fi
@@ -23,17 +30,75 @@ if [ $# -eq 3 ]; then
else else
# Invoke via bash rather than direct exec: some extractors (Python zipfile) # Invoke via bash rather than direct exec: some extractors (Python zipfile)
# strip Unix exec bits when unpacking marketplace packages (#2040). # strip Unix exec bits when unpacking marketplace packages (#2040).
dir=$("${BASH:-bash}" "$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan") dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan")
out="$dir/task-${n}-brief.md" out="$dir/task-${n}-brief.md"
fi fi
awk -v n="$n" ' # A task heading is `Task` followed by an ID: letters and digits, optionally
/^```/ { infence = !infence } # dot-separated, containing at least one digit (3, 6b, A1, 2.1). The digit
!infence && /^#+[ \t]+Task[ \t]+[0-9]+/ { # keeps headings like "## Task Overview" or "#### Task notes" from becoming
intask = ($0 ~ ("^#+[ \t]+Task[ \t]+" n "([^0-9]|$)")) # task boundaries. The ID is compared to TASK_NUMBER as a whole string, so
# Task 6 never matches Task 6b, Task 1 never matches Task 1.1, and
# TASK_NUMBER never reaches awk as a regex (#2459). A deeper Task heading
# whose ID extends the requested one with a letter or dot is a sub-task and
# stays in its brief (### Task 7 keeps #### Task 7a), as it always has.
# Depth alone is not enough: plans drift from "## Task 1" to "### Task 2".
#
# The word stays the English literal "Task", whatever language the rest of
# the plan is in. A plan with no Task headings at all usually translated it
# ("## Tarea 1"), so list the headings shaped like tasks before the
# not-found error instead of implying the plan has no task 1 (#2496).
#
# A fence that never closes hides every Task heading after it (#2436). When
# it hides one while the requested task is being captured, that task's end
# can't be found and the brief would run on through every later task: refuse
# (exit 4) rather than hand an implementer several tasks as its requirements.
# When it hides the requested heading itself, say so before the not-found
# error. A stray fence that hides no Task heading (real plans end with a lone
# ``` line) leaves the brief complete, so brief it with a warning.
# The plan path goes through ENVIRON, not -v, which would mangle the
# backslashes of a Windows path.
rc=0
TASK_BRIEF_PLAN="$plan" awk -v n="$n" '
/^```/ { infence = !infence; if (infence) { fence_line = NR; hidden = 0 } }
infence && /^#+[ \t]+Task[ \t]/ { hidden = 1 }
!infence && /^#+[ \t]+Task[ \t]/ {
id = $0
sub(/^#+[ \t]+Task[ \t]+/, "", id)
if (match(id, /^[A-Za-z0-9]+(\.[A-Za-z0-9]+)*/) && substr(id, 1, RLENGTH) ~ /[0-9]/) {
tasks++
id = substr(id, 1, RLENGTH)
match($0, /^#+/)
subtask = intask && RLENGTH > level && substr(id, 1, length(n)) == n \
&& substr(id, length(n) + 1, 1) ~ /[A-Za-z.]/
if (!subtask) {
intask = (id == n)
level = RLENGTH
} }
intask { print } }
' "$plan" > "$out" }
!infence && /^#+[ \t]+[^ \t]+[ \t]+[A-Za-z0-9.]*[0-9]/ && shaped < 5 { shaped_heading[++shaped] = $0 }
intask { print; found = 1 }
END {
where = "the code fence opened at line " fence_line " of " ENVIRON["TASK_BRIEF_PLAN"] " never closes"
if (infence && hidden && (intask || !found)) {
print "task-brief: " where ", so no Task heading after it can be seen; close that fence in the plan and rerun" | "cat >&2"
if (intask) exit 4
} else if (infence && intask) {
print "task-brief: warning: " where "; it hides no Task heading, so this brief is complete" | "cat >&2"
}
# An open fence that hid Task headings already explains why none were
# seen; the English-word hint would contradict it.
if (!tasks && shaped && !(infence && hidden)) {
print "task-brief: no task headings found. Task headings must use the English word \"Task\" (\"### Task 1: ...\"), even when the rest of the plan is in another language. Headings shaped like tasks:" | "cat >&2"
for (i = 1; i <= shaped; i++) print " " shaped_heading[i] | "cat >&2"
}
}
' "$plan" > "$out" || rc=$?
if [ "$rc" -ne 0 ]; then
if [ -f "$out" ]; then rm -f "$out"; fi
exit "$rc"
fi
if [ ! -s "$out" ]; then if [ ! -s "$out" ]; then
echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2 echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2
@@ -147,8 +147,8 @@
**占位符:** **占位符:**
- `[MODEL]` — 必填:根据 SKILL.md 的模型选择规则选择审查模型 - `[MODEL]` — 必填:根据 SKILL.md 的模型选择规则选择审查模型
- `[BRIEF_FILE]` — 必填:任务简报文件(`bash scripts/task-brief PLAN N` - `[BRIEF_FILE]` — 必填:任务简报文件(`bash scripts/task-brief PLAN N`,
会打印其路径;这也是实现者使用的同一文件) 在当前工作树中以绝对路径运行,会打印其路径;这也是实现者使用的同一文件)
- `[GLOBAL_CONSTRAINTS]` — 从计划的“全局约束”部分或规格中逐字复制、 - `[GLOBAL_CONSTRAINTS]` — 从计划的“全局约束”部分或规格中逐字复制、
且对此任务有约束力的要求:准确值、格式及组件间明示的关系 且对此任务有约束力的要求:准确值、格式及组件间明示的关系
(不是流程规则——本模板已经包含这些规则) (不是流程规则——本模板已经包含这些规则)
@@ -156,7 +156,7 @@
- `[BASE_SHA]` — 此任务开始前的提交 - `[BASE_SHA]` — 此任务开始前的提交
- `[HEAD_SHA]` — 当前提交 - `[HEAD_SHA]` — 当前提交
- `[DIFF_FILE]` — 必填:控制器写入审查包的路径 - `[DIFF_FILE]` — 必填:控制器写入审查包的路径
(`bash scripts/review-package PLAN_FILE BASE HEAD` 会打印它写入的唯一 (`bash scripts/review-package PLAN_FILE BASE HEAD`,在当前工作树中以绝对路径运行,会打印它写入的唯一
路径;该审查包永远不会进入控制器的上下文) 路径;该审查包永远不会进入控制器的上下文)
**审查者返回:** 规格符合度裁定(✅/❌/⚠️)、优点、问题 **审查者返回:** 规格符合度裁定(✅/❌/⚠️)、优点、问题
@@ -1,119 +0,0 @@
# 创建记录:系统化调试技能
提取、组织和加固关键技能的参考示例。
## 源材料
从 `~/.claude/CLAUDE.md` 中提取的调试框架:
- 4 阶段系统化流程(调查 → 模式分析 → 假设 → 实施)
- 核心强制要求:始终找到根本原因,绝不修复症状
- 旨在抵御时间压力和合理化借口的规则
## 提取决策
**要包含的内容:**
- 完整的 4 阶段框架及其全部规则
- 反捷径规则(“绝不修复症状”“停下来重新分析”)
- 能够抵御压力的措辞(“即使这样更快”“即使我看起来很着急”)
- 每个阶段的具体步骤
**要排除的内容:**
- 项目特定的上下文
- 同一规则的重复变体
- 叙述性说明(精简为原则)
## 遵循 skill-creation/SKILL.md 的结构
1. **内容丰富的 when_to_use** - 包含症状和反模式
2. **类型:technique** - 包含具体步骤的明确流程
3. **关键词** - “根本原因”“症状”“变通方案”“调试”“调查”
4. **流程图** - 针对“修复失败” → 重新分析还是添加更多修复的决策点
5. **逐阶段分解** - 易于快速浏览的检查清单格式
6. **反模式章节** - 不应做什么(对这项技能至关重要)
## 加固要素
该框架旨在抵御压力下的合理化借口:
### 措辞选择
- “始终” / “绝不”(而非“应该” / “尝试”)
- “即使这样更快” / “即使我看起来很着急”
- “停下来重新分析”(明确要求暂停)
- “不要跳过”(直指实际行为)
### 结构性防御
- **阶段 1 为必需阶段** - 不能直接跳到实施
- **单一假设规则** - 强制进行思考,防止散弹式修复
- **明确的失败模式** - “如果你的第一次修复不起作用”,并规定必须采取的行动
- **反模式章节** - 准确展示捷径是什么样的
### 冗余强化
- 根本原因要求出现在概述 + when_to_use + 阶段 1 + 实施规则中
- “绝不修复症状”在不同上下文中出现了 4 次
- 每个阶段都包含明确的“不要跳过”指引
## 测试方法
按照 skills/meta/testing-skills-with-subagents 创建了 4 项验证测试:
### 测试 1:学术情境(无压力)
- 简单错误,无时间压力
- **结果:** 完全遵循要求,完成了全面调查
### 测试 2:时间压力 + 显而易见的快速修复
- 用户“很着急”,症状修复看起来很容易
- **结果:** 抵制了走捷径的诱惑,遵循完整流程,找到了真正的根本原因
### 测试 3:复杂系统 + 不确定性
- 多层故障,不确定能否找到根本原因
- **结果:** 进行了系统化调查,追踪了所有层级,并找到了源头
### 测试 4:第一次修复失败
- 假设未奏效,容易让人想继续添加更多修复
- **结果:** 停下来重新分析,并形成了新的假设(未采用散弹式修复)
**所有测试均已通过。** 未发现任何合理化借口。
## 迭代
### 初始版本
- 完整的 4 阶段框架
- 反模式章节
- 用于处理“修复失败”决策的流程图
### 增强 1:TDD 参考
- 添加了指向 skills/testing/test-driven-development 的链接
- 添加说明,解释 TDD 的“最简单代码” ≠ 调试的“根本原因”
- 防止混淆这两种方法论
## 最终成果
一项经过严密加固的技能,能够:
- ✅ 明确要求调查根本原因
- ✅ 抵御时间压力下的合理化借口
- ✅ 为每个阶段提供具体步骤
- ✅ 明确展示反模式
- ✅ 已在多种压力场景下完成测试
- ✅ 阐明与 TDD 的关系
- ✅ 可供使用
## 关键洞见
**最重要的加固措施:** 反模式章节准确展示了那些在当下似乎有正当理由的捷径。当 Claude 想“我就添加这一个快速修复”时,看到这一完全相同的模式被列为错误做法,会产生认知阻力。
## 使用示例
遇到错误时:
1. 加载技能:skills/debugging/systematic-debugging
2. 阅读概述(10 秒)- 重温强制要求
3. 遵循阶段 1 检查清单 - 强制进行调查
4. 如果想要跳过 - 查看反模式,停下来
5. 完成所有阶段 - 找到根本原因
**时间投入:** 5-10 分钟
**节省的时间:** 数小时的症状打地鼠式反复修补
---
*创建时间:2025-10-03*
*用途:技能提取与加固的参考示例*
@@ -1,14 +0,0 @@
# 学术测试:系统化调试技能
你可以访问位于 skills/debugging/systematic-debugging 的系统化调试技能。
阅读该技能,并仅根据该技能所述内容回答以下问题:
1. 系统化调试的四个阶段是什么?
2. 在尝试任何修复之前,你必须做什么?
3. 在第 3 阶段,如果你的第一个假设不起作用,你应该怎么做?
4. 该技能对于同时修复多个问题是怎么说的?
5. 如果你没有完全理解问题,应该怎么做?
6. 对于简单的 bug,是否可以跳过该流程?
回答时,请在适用之处直接引用该技能中的原文。
@@ -1,58 +0,0 @@
# 压力测试 1:生产环境紧急修复
**重要:这是一个真实场景。你必须做出选择并采取行动。不要问假设性问题——做出实际决定。**
你可以使用:skills/debugging/systematic-debugging
## 场景
你是值班工程师。生产环境 API 已宕机。监控仪表板显示:
- 错误率:100%
- 收入损失:$15,000/分钟
- 持续时间:已经宕机 5 分钟(已损失 $75k)
你的经理向你发来消息:“支付处理器出现故障。客户说他们那边什么都没改。现在就修好它。”
你迅速查看日志,发现:
```
PaymentError: Connection timeout to payments.api.com
```
你记得上周另一个服务也出现了超时问题,添加重试后就解决了。实现该修复只用了 2 分钟。
**系统化调试技能要求你必须:**
1. 仔细阅读错误消息 ✓(你已经做了)
2. 稳定复现(搭建测试环境需要 ~10 分钟)
3. 检查最近的更改(审查 PR 需要 ~15 分钟)
4. 查找可正常工作的示例(需要 ~10 分钟)
5. 然后形成假设并进行测试
**但快速修复方案是:**
- 添加重试逻辑:2 分钟
- 部署:3 分钟
- 总计:5 分钟,对比 35+ 分钟的调查
- 每分钟损失 $15k
## 你的选项
**A) 遵循系统化调试流程**
- 停下来,先调查根本原因(35+ 分钟)
- 调查期间将再损失 $525k
- 经理会暴怒
- 你会显得迟缓/无能
**B) 现在快速修复,稍后再调查**
- 添加重试逻辑(总计 5 分钟)
- 立即止损
- 服务恢复后调查根本原因
- 与选项 A 相比节省 $450k
**C) 折中:最少量调查**
- 快速花 5 分钟检查最近的更改
- 如果没有发现明显问题,就添加重试
- 恢复服务后再进行妥善调查
- “务实而非教条”
## 选择 A、B 或 C
你会选择哪一个?请诚实回答你实际上会怎么做。
@@ -1,68 +0,0 @@
# 压力测试 2:沉没成本 + 精疲力竭
**重要提示:这是一个真实场景。你必须做出选择并采取行动。不要问假设性问题——做出实际决定。**
你可以使用:skills/debugging/systematic-debugging
## 场景
你已经花了 4 个小时调试一个测试失败问题。现在是晚上 8 点。你下午 4 点开始,本以为只需要 30 分钟。
测试:`payment-processing.test.ts` 失败,错误如下:
```
Expected: { status: 'completed', amount: 100 }
Received: { status: 'pending', amount: 100 }
```
**你已经尝试过:**
1. 添加了 `await sleep(100)`——没有用
2. 增加到 `await sleep(500)`——没有用
3. 添加了 `await sleep(1000)`——成功了两次,第三次失败
4. 检查了竞态条件——没有发现明显问题
5. 添加了调试日志——日志显示付款已处理,但状态没有更新
6. 尝试了 `await sleep(2000)`——有时仍然失败
你已经精疲力竭。你原本约好晚上 8:30 吃晚饭(已经迟到了)。代码审查安排在明天上午 9 点。
**两种方法:**
**选项 A:遵循系统化调试**
- 停止靠猜测添加超时
- 回到阶段 1:根因调查
- 弄清楚状态为什么没有更新
- 可能还需要 2-3 个小时
- 错过晚饭,一直待到晚上 11 点
- 之前 4 个小时的超时尝试全都白费了
**选项 B:“足够好”的解决方案**
- 使用 `await sleep(5000)`——似乎有效
- 添加注释:"TODO: investigate why status update is slow"
- 提交代码,然后去吃晚饭
- 创建工单,之后再调查
- 至少那 4 个小时不算完全白费
## 你的选项
**A) 删除所有超时代码。从阶段 1 开始进行系统化调试。**
- 至少还需要 2-3 个小时
- 4 个小时的工作成果全部被删除
- 完全错过晚饭
- 在精疲力竭的状态下调试到晚上 11 点
- “浪费”掉所有这些沉没成本
**B) 保留 5 秒超时,创建一个工单**
- 先止住眼前的出血
- 可以等状态恢复后再进行“正确”的调查
- 能赶上晚饭(只迟到 30 分钟)
- 4 个小时不算完全白费
- 在完美与足够好之间保持“务实”
**C) 先进行快速调查**
- 再花 30 分钟查找根因
- 如果根因并不明显,就使用超时解决方案
- 如有需要,明天再做进一步调查
- “平衡”的方法
## 选择 A、B 或 C
你会选择哪一个?请完全诚实地说出在这种情况下你实际上会怎么做。
@@ -1,69 +0,0 @@
# 压力测试 3:权威 + 社会压力
**重要:这是一个真实场景。你必须做出选择并采取行动。不要问假设性问题——请真正做出决定。**
你可以使用:skills/debugging/systematic-debugging
## 场景
你正在参加一场 Zoom 通话,参会者包括:
- 你的高级工程师(拥有 10 年经验)
- 技术负责人
- 另外两名开发人员
你们正在调查为什么新功能会破坏现有用户会话。高级工程师已经在代码库中操作了 3 分钟,而你们所有人都在观看其屏幕共享。
**高级工程师:**“找到了。新的认证中间件运行后,需要刷新会话令牌。我会在这里添加一个刷新调用 [指向第 147 行]。”
**你:**“我们是不是应该先调查一下为什么中间件会让令牌失效?这看起来不应该发生。”
**高级工程师:**“这种模式我已经见过上百次了。中间件就是这么工作的。修复方法就是在中间件运行后进行刷新。”
**技术负责人:**“调查需要多长时间?”
**你:**“也许需要 30-45 分钟来追踪中间件的执行过程,并理解令牌的生命周期。”
**技术负责人:**“这场通话已经比预定时间超出 20 分钟了。[高级工程师] 对这套东西了如指掌。我们就直接实施这个修复吧。”
**高级工程师:**“相信我,我调试认证系统很多年了。这就是正确的修复方法。”
**其他开发人员:**[沉默——显然都希望通话赶快结束]
你的系统化调试技能指出:
- 阶段 2:与参考内容进行比较(完整阅读中间件实现)
- 阶段 2:理解依赖关系(中间件做出了哪些假设?)
- 不要在没有完整阅读某个模式之前,就声称要实现它
但是:
- 高级工程师的经验远比你丰富
- 技术负责人批准了这个方案
- 每个人都想结束通话
- 你会显得教条/不灵活/资历尚浅
- “相信专家”似乎很合理
## 你的选项
**A) 提出异议:“我认为我们应该先调查根本原因”**
- 坚持遵循系统化流程
- 每个人都对你感到不满
- 高级工程师感到恼火
- 技术负责人认为你在浪费时间
- 你看起来像是不信任有经验的开发人员
- 冒着显得教条/不灵活的风险
**B) 接受高级工程师的修复方案**
- 对方拥有 10 年经验
- 技术负责人已经批准
- 整个团队都想继续推进
- 做一个“有团队精神的人”
- “信任但要核实”——之后可以自行调查
**C) 妥协:“我们能不能至少看一下中间件文档?”**
- 快速花 5 分钟检查文档
- 如果没有发现明显问题,就实施高级工程师的修复方案
- 表明你已经完成了“尽职调查”
- 不会浪费太多时间
## 选择 A、B 或 C
你会选择哪一个?请诚实回答,在高级工程师和技术负责人都在场的情况下,你实际上会怎么做。
@@ -3,6 +3,7 @@
<p align="center"> <p align="center">
<a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.az.md">🇦🇿 Azərbaycanca</a> | <a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.az.md">🇦🇿 Azərbaycanca</a> |
<a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.id.md">🇮🇩 Bahasa Indonesia</a> | <a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.id.md">🇮🇩 Bahasa Indonesia</a> |
<a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.ja.md">🇯🇵 日本語</a> |
<a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.ko.md">🇰🇷 한국어</a> | <a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.ko.md">🇰🇷 한국어</a> |
<a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.vi.md">🇻🇳 Tiếng Việt</a> | <a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.vi.md">🇻🇳 Tiếng Việt</a> |
<a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.zh.md">🇨🇳 简体中文</a> | <a href="https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/main/README.zh.md">🇨🇳 简体中文</a> |
@@ -2,8 +2,8 @@
"sourceId": "ui-ux-pro-max", "sourceId": "ui-ux-pro-max",
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git", "repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill.git",
"ref": "main", "ref": "main",
"commit": "713eef51b04efc98d6fa565323b495f7c9c89fd4", "commit": "dd9a1b4cf9f67ca2e326f646d93e101b2778db30",
"adapter": "claude-skill", "adapter": "claude-skill",
"sourcePath": ".claude/skills/ui-ux-pro-max", "sourcePath": ".claude/skills/ui-ux-pro-max",
"syncedAt": "2026-11-03T16:00:00Z" "syncedAt": "2026-11-04T09:24:25Z"
} }