Skip to content

Feat/button workflow - #2314

Open
johnsmith65536 wants to merge 2 commits into
larksuite:mainfrom
johnsmith65536:feat/button_workflow
Open

Feat/button workflow#2314
johnsmith65536 wants to merge 2 commits into
larksuite:mainfrom
johnsmith65536:feat/button_workflow

Conversation

@johnsmith65536

@johnsmith65536 johnsmith65536 commented Aug 12, 2026

Copy link
Copy Markdown

Summary

Changes

  • Change 1
  • Change 2

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark-cli <domain> <command> flow works as expected

Related Issues

  • None

Summary by CodeRabbit

  • New Features
    • Added commands to bind, view, and unbind workflows from Base button fields.
    • Added dry-run support for button workflow operations.
    • Added configurable endpoint domains and additional request headers for development environments.
  • Documentation
    • Documented button field configuration, workflow management commands, and troubleshooting guidance.
    • Updated field reference documentation with button field support.
  • Bug Fixes
    • Improved validation for endpoint overrides and custom request headers.
    • Clarified recovery steps for unsupported button field schemas.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


zhangbinkai.zbk seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c1f4a04-dbd9-488e-be9b-0eed06f906ba

📥 Commits

Reviewing files that changed from the base of the PR and between e947c82 and 372f6e2.

📒 Files selected for processing (1)
  • .gitignore

📝 Walkthrough

Walkthrough

The PR adds Base button-rule bind, get, and unbind shortcuts. It also adds endpoint-domain overrides and validated extra request headers through environment variables, with tests and updated Base field guidance.

Changes

Base button workflow commands

Layer / File(s) Summary
Button-rule command implementation
shortcuts/base/button_rule.go, shortcuts/base/shortcuts.go
Adds bind, get, and unbind commands with validation, dry-run support, API requests, and shortcut registration.
Button-rule validation and execution coverage
shortcuts/base/*test.go, tests/cli_e2e/base/*
Tests command registration, request methods, payloads, responses, dry runs, and workflow ID validation.
Button-field creation and recovery guidance
skills/lark-base/SKILL.md, skills/lark-base/references/lark-base-field-json.md
Documents button field JSON, separate workflow operations, discriminator errors, and unsupported field removal.

Environment routing and request headers

Layer / File(s) Summary
Environment variable contracts and parsing
internal/envvars/*, .gitignore
Defines endpoint and header environment variables. Parses and validates semicolon-separated extra headers.
Endpoint domain override
internal/core/types.go, internal/core/types_test.go
Applies validated hostname overrides to all resolved Feishu endpoints and rejects URL-form values.
Extra security headers
internal/cmdutil/secheader.go, internal/cmdutil/secheader_test.go
Adds configured extra headers to base security headers and verifies the configured value.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant ButtonRuleShortcut
  participant BaseAPI
  CLI->>ButtonRuleShortcut: invoke button-rule command
  ButtonRuleShortcut->>ButtonRuleShortcut: validate arguments
  ButtonRuleShortcut->>BaseAPI: send GET or PUT request
  BaseAPI-->>ButtonRuleShortcut: return workflow rule
  ButtonRuleShortcut-->>CLI: print response
Loading

Possibly related PRs

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description contains only template placeholders and does not explain the motivation, changes, or completed verification. Replace the placeholders with a summary of the button workflow and environment changes, list the actual changes, and mark completed tests or provide their results.
Docstring Coverage ⚠️ Warning Docstring coverage is 5.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding button workflow functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added domain/base PR touches the base domain size/XL Architecture-level or global-impact change size/L Large or sensitive change across domains or core paths and removed size/XL Architecture-level or global-impact change labels Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (8)
env/codex-dev-lark.sh (1)

233-238: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the lane override behavior with env/claude-dev-lark.sh.

Line 235 pins LARK_LANE="$lane" at generation time. The sibling shim in env/claude-dev-lark.sh line 224 uses LARK_LANE="\${LARK_LANE:-$lane}" and lets the caller override the lane per invocation. Both launchers document the same --lane flag, so the two shims should resolve the lane the same way.

♻️ Proposed fix
 exec env \\
   LARK_CLI_ENV_BIN="$bin_dir" \\
-  LARK_LANE="$lane" \\
+  LARK_LANE="\${LARK_LANE:-$lane}" \\
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@env/codex-dev-lark.sh` around lines 233 - 238, Update the LARK_LANE
assignment in the launcher’s exec environment to use the caller-provided
LARK_LANE when set, falling back to the generated lane value otherwise, matching
the behavior of env/claude-dev-lark.sh. Keep the existing --lane handling and
other environment assignments unchanged.
internal/envvars/read.go (1)

34-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not reuse agentNameMaxLen as the header value limit.

Line 34 bounds arbitrary header values with agentNameMaxLen. That constant defines the limit for the agent-name value. The two limits are unrelated. If the agent-name limit changes later, header values are silently accepted or rejected at a different length.

Declare a dedicated constant, for example extraHeaderValueMaxLen, and use it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/envvars/read.go` at line 34, In the header-value sanitization path,
replace the agentNameMaxLen argument used by sanitizeSingleLine with a dedicated
extraHeaderValueMaxLen constant. Define the new limit alongside the existing
environment-value limits, keeping the agent-name limit exclusively for
agent-name validation.
env/larkenv (2)

101-117: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Disable globbing during the unquoted split.

Line 107 iterates $raw unquoted so that IFS=';' splits the entries. Bash also applies pathname expansion to that unquoted expansion. If a header value contains * or ?, the loop can replace the value with matching filenames from the current directory.

♻️ Proposed fix
 	local item result=""
 	local IFS=';'
+	local reset_glob=0
+	case "$-" in *f*) ;; *) reset_glob=1; set -f ;; esac
 
 	for item in $raw; do
 		item="${item#"${item%%[![:space:]]*}"}"
 		item="${item%"${item##*[![:space:]]}"}"
 		[ -n "$item" ] || continue
 		[ "$item" = "$target" ] && continue
 		if [ -n "$result" ]; then
 			result="$result; $item"
 		else
 			result="$item"
 		fi
 	done
+	[ "$reset_glob" -eq 0 ] || set +f
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@env/larkenv` around lines 101 - 117, Update remove_extra_header so pathname
expansion is disabled while iterating over the unquoted raw value used for
semicolon splitting, preventing header characters such as * and ? from expanding
to filenames. Preserve the existing trimming, target removal, and result
reconstruction behavior.

270-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Restore terminal echo if the prompt is interrupted.

Line 272 disables echo with stty -echo. If the user presses Ctrl-C during read, line 274 never runs and the terminal stays without echo. Bash read -rs handles the restore itself, including on interrupt.

♻️ Proposed fix
 		printf 'App Secret (输入不回显): ' >&2
-		stty -echo 2>/dev/null || true
-		read -r secret
-		stty echo 2>/dev/null || true
+		read -rs secret
 		printf '\n' >&2
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@env/larkenv` around lines 270 - 278, Update the secret prompt in the
three-argument branch to use Bash’s `read -rs` instead of manually toggling
terminal echo with `stty -echo` and `stty echo`. Remove the explicit stty calls
while preserving hidden input, interruption-safe echo restoration, newline
output, and subsequent `config init --app-secret-stdin` handling.
internal/cmdutil/secheader_test.go (1)

265-272: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add an assertion that extra headers cannot replace CLI-owned headers.

The test proves that a new key is added. It does not constrain the case where the extra header collides with a header the CLI sets itself. Add that case together with the fix proposed on internal/cmdutil/secheader.go lines 62-66.

💚 Proposed test
func TestBaseSecurityHeaders_ExtraHeadersDoNotOverrideCLIHeaders(t *testing.T) {
	t.Setenv(envvars.CliExtraHeaders, "X-Cli-Source: spoofed; X-TT-ENV: boe_bitable_bk")

	h := BaseSecurityHeaders()
	if got := h.Get(HeaderSource); got != SourceValue {
		t.Fatalf("%s = %q, want %q", HeaderSource, got, SourceValue)
	}
	if got := h.Get("X-TT-ENV"); got != "boe_bitable_bk" {
		t.Fatalf("X-TT-ENV = %q, want boe_bitable_bk", got)
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cmdutil/secheader_test.go` around lines 265 - 272, Extend the
security-header tests around BaseSecurityHeaders to cover collisions with
CLI-owned headers: configure CliExtraHeaders with both a spoofed HeaderSource
and a new X-TT-ENV header, then assert the CLI’s SourceValue remains
authoritative while the non-conflicting extra header is preserved. Apply the
corresponding protection in BaseSecurityHeaders so extra headers cannot override
CLI-set headers.
internal/core/types_test.go (1)

76-83: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Extend the rejection coverage to a table.

The test covers only the https:// prefix. The override guard also rejects /?#@, and it currently accepts values that should be rejected, such as a value with a port. Table-driven negative cases document the intended contract and fail if the guard is loosened.

💚 Proposed test
func TestResolveEndpoints_RejectsInvalidEndpointDomainOverride(t *testing.T) {
	for _, raw := range []string{
		"https://open.feishu-boe.cn",
		"open.feishu-boe.cn/path",
		"open.feishu-boe.cn?a=b",
		"user@open.feishu-boe.cn",
		"open.feishu-boe.cn:8080",
		"   ",
	} {
		t.Run(raw, func(t *testing.T) {
			t.Setenv(envvars.CliEndpointDomain, raw)
			ep := ResolveEndpoints(BrandFeishu)
			if ep.Open != "https://open.feishu.cn" {
				t.Errorf("Open = %q, want default endpoint for override %q", ep.Open, raw)
			}
			if ep.Accounts != "https://accounts.feishu.cn" {
				t.Errorf("Accounts = %q, want default endpoint for override %q", ep.Accounts, raw)
			}
		})
	}
}

The :8080 case fails against the current implementation. It documents the gap raised on internal/core/types.go lines 115-123.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/core/types_test.go` around lines 76 - 83, Expand
TestResolveEndpoints_RejectsURLAsEndpointDomainOverride into a table-driven
negative test covering URL schemes, path, query, userinfo, ports, and
whitespace-only overrides; rename it to reflect invalid endpoint domain
overrides. For each case, assert ResolveEndpoints(BrandFeishu) falls back to
both the default Open and Accounts endpoints, including the port case currently
accepted by the guard.
env/claude-dev-lark.sh (1)

137-139: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Make the permission bypass opt-in.

Line 138 always passes --allow-dangerously-skip-permissions. The script provides no way to launch Claude Code with permission prompts enabled. The sibling launcher env/codex-dev-lark.sh gates the equivalent bypass behind an explicit --cx flag plus a required environment variable (lines 79-82 and 128-131).

Add a flag such as --safe or invert the default, so the bypass requires an explicit opt-in.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@env/claude-dev-lark.sh` around lines 137 - 139, Update launch_claude so
Claude starts with permission prompts enabled by default; only add
--allow-dangerously-skip-permissions when the user explicitly opts in via a
dedicated flag and the required environment-variable guard, following the
existing opt-in pattern in the sibling launcher.
internal/envvars/read_test.go (1)

147-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the empty result contract.

ExtraHeaders returns nil when no valid header remains. No test asserts that branch. A revert to returning an empty non-nil http.Header would pass the current tests.

Add a case for an unset variable and a case where every entry is invalid.

💚 Proposed test
func TestExtraHeaders_ReturnsNilWhenNoValidHeaders(t *testing.T) {
	t.Setenv(CliExtraHeaders, "")
	if h := ExtraHeaders(); h != nil {
		t.Fatalf("ExtraHeaders() = %v, want nil for empty value", h)
	}

	t.Setenv(CliExtraHeaders, "no-colon; Bad Header: nope; : empty-name")
	if h := ExtraHeaders(); h != nil {
		t.Fatalf("ExtraHeaders() = %v, want nil when all entries are invalid", h)
	}
}

Based on learnings, every behavior change requires a nearby regression test that fails when the implementation is reverted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/envvars/read_test.go` around lines 147 - 160, Add a regression test
near TestExtraHeaders_RejectsHeaderInjection covering ExtraHeaders with an empty
or unset CliExtraHeaders value and with a value containing only invalid entries;
assert the result is nil in both cases, preserving the contract that no valid
headers returns nil.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@A2A_WEB_PARITY_DISCUSSION.md`:
- Around line 210-217: Update the unknown-event fallback described in the stable
core type/provider extension proposal to avoid forwarding arbitrary raw
structures. Represent unrecognized data with a redacted or opaque extension by
default, and only preserve raw fields after explicit visibility validation and
allowlist checks; apply the same rule to the corresponding section around the
additional referenced content.
- Around line 421-429: Update section “7.2 推荐方案” to distinguish standard A2A/MCP
capabilities from Lark-specific recovery semantics: label
`SubscribeTask(after_cursor=...)` and cursor-expiry recovery as Lark-specific
extensions, explicitly pin the relevant A2A and MCP protocol versions, and
remove any implication that A2A `SubscribeToTask` or MCP Tasks define standard
event replay.

In `@cmd/agents/lark-cli-a2a-web-experience-parity-discussion.md`:
- Around line 419-438: Establish one versioned, normative public event
vocabulary before implementation, centered on the event model in section 5.2.1
or an explicitly selected alternative. Add a compatibility mapping covering
output.*, task.error, task.snapshot, and task.stream_end to the canonical
equivalents, and update the referenced design documents, reducers, and
contract-test expectations to use that mapping consistently.

In `@cmd/agents/task-stream-phase1-design.md`:
- Around line 156-190: 更新“结束事件”及对应错误表,明确 task.stream_end 中 ok
表示传输成功而非任务成功,并增加独立的任务结果字段以区分 completed、failed、rejected、canceled 等状态。为 API
错误、内容安全拦截和写入失败补充终止行为:stdout
仍可写时发送不含敏感信息的错误结束事件;写入失败时说明只能依赖退出码,但将退出码保留为辅助信号而非唯一任务状态来源。
- Around line 242-269: Extend the task-stream design around snapshot
digest/output handling to define explicit maximum serialized snapshot size,
cumulative transfer, and polling/output frequency limits, including oversized
single-message or artifact cases. Ensure the implementation checks these limits
before emitting each snapshot and returns a typed truncation or size error
rather than emitting incomplete state; apply the same safeguards to the later
output path referenced by the comment.
- Around line 114-115: Update the task polling flow described in the design so
--timeout is enforced as a hard observation deadline, using a deadline-aware
context for polling and GetTask plus an interruptible timer during backoff.
Explicitly define whether the initial GetTask may finish after the deadline, and
add coverage where the timeout is shorter than the next backoff interval to
verify prompt termination without cancelling the remote task.

In `@env/codex-dev-lark.sh`:
- Around line 95-99: The `--` handling replaces previously collected forwarding
arguments instead of preserving them. In env/codex-dev-lark.sh lines 95-99,
update the `codex_args` assignment to append the remaining positional arguments;
in env/claude-dev-lark.sh lines 81-86, append to `claude_args` and increment
`claude_arg_count` by the remaining argument count rather than resetting it.
- Around line 136-138: Add *.bak.* to the repository’s .gitignore so timestamped
skill backup files created by link_skill under the existing skill backup paths
are ignored, while preserving the current bin ignore rules.
- Line 263: Update the launcher’s final exec command to safely handle an empty
codex_args array while set -u is enabled, including plain invocation and -- with
no arguments on Bash 3.2. Preserve all existing argument passing behavior when
codex_args contains values, and keep codex_launch_args expansion unchanged.

In `@env/larkenv`:
- Around line 157-176: Confirm whether feishu-boe.cn and feishu-pre.cn are
publicly accessible and approved for inclusion in the public CLI distribution;
if not, remove the hardcoded domains and internal lane values from the
environment switch and source them from an untracked developer-provided
configuration or environment variables, while preserving the boe, pre, ppe, and
online behavior.

In `@internal/cmdutil/secheader.go`:
- Around line 62-66: Update the extra-header application in BaseSecurityHeaders
in internal/cmdutil/secheader.go:62-66 to skip keys already present in h using
canonical header names, and use h.Add for each value so multi-value headers are
preserved. Add a test case in internal/cmdutil/secheader_test.go:265-272
covering a colliding X-Cli-Source header whose CLI value remains intact and a
non-colliding header that is still added.

In `@internal/core/types.go`:
- Line 8: Remove the direct os.Getenv usage from ResolveEndpoints in
internal/core and add an envvars.EndpointDomain() string accessor that reads and
validates envvars.CliEndpointDomain alongside ExtraHeaders and the other
environment accessors. Update ResolveEndpoints to use EndpointDomain(), reusing
the existing validation machinery and keeping environment ownership within
internal/envvars.
- Around line 115-123: Strengthen endpointDomainOverride validation by rejecting
any value containing “:” and requiring a dotted domain whose labels each match
[a-z0-9]([a-z0-9-]*[a-z0-9])?. Preserve the existing trimming, lowercasing,
URL-delimiter rejection, and trailing-dot normalization while ensuring invalid
values such as ports, spaces, and empty labels return an empty override.
- Around line 106-111: Harden endpoint domain overrides across
internal/core/types.go: at lines 106-111, prevent endpointDomainOverride from
redirecting Accounts and ensure override-derived hosts are excluded from
platformEndpointHosts; at lines 115-123, replace the denylist validation with
positive domain-shape validation or an explicit development-domain allowlist; at
internal/core/types_test.go lines 76-83, convert the rejection test into a table
covering ports, inner spaces, and single-label values.

In `@shortcuts/base/workflow_execute_test.go`:
- Around line 190-196: Update
TestBaseButtonRuleValidateRejectsInternalWorkflowID to assert that the returned
error is an *errs.ValidationError, then verify its invalid-argument subtype and
associated parameter is --workflow-id. Replace the message-only validation while
preserving the existing rejection scenario and ensure the validation cause
metadata is checked rather than relying on matching error text.
- Around line 136-151: The tests in shortcuts/base/workflow_execute_test.go at
lines 136-151 and 172-188 only validate response output; update both
TestBaseButtonRuleExecuteBind and the corresponding unbind test to assert the
outbound PUT payload, requiring workflow_id "wkf_1" for bind and workflow_id ""
for unbind.

In `@shortcuts/base/workspace.go`:
- Around line 12-112: Add self-contained live E2E coverage for
BaseWorkspaceCreate, BaseWorkspaceEntityList, BaseWorkspaceEntityAdd, and
BaseWorkspaceEntityRemove, covering the complete create, list, add, and remove
workflow. Introduce an exposed cleanup shortcut or equivalent cleanup workflow
for the workspace created by the test, and ensure cleanup runs even when earlier
assertions or operations fail so no persistent workspace state is leaked.

In `@skills/lark-base/references/baseapp-protocol-design.md`:
- Line 731: Remove the developer-specific local path and internal code.byted.org
host from the backend-core evidence description in the referenced documentation
section. Replace it with repository-neutral wording such as “backend-core source
review,” without changing the surrounding evidence or contract content.

In `@skills/lark-base/SKILL.md`:
- Line 153: Update the routing section in SKILL.md to add entries for
+button-rule-bind, +button-rule-get, and +button-rule-unbind, directing these
requests to lark-base-field-json.md and its field-creation sequence. Keep the
existing button-field recovery guidance unchanged and ensure the new entries
provide domain routing and cross-command workflow coverage.

In `@tests/cli_e2e/base/base_button_rule_dryrun_test.go`:
- Around line 15-79: Extend TestBaseButtonRuleDryRun with self-contained live
E2E coverage for the bind, get, and unbind shortcuts, following the HTTP-mock
setup used by workflow_execute_test.go. Create the required base, table, field,
and workflow fixtures, verify get returns the bound workflow and then confirms
the field is unbound, and register cleanup for every created resource so
teardown runs even when assertions fail.

---

Nitpick comments:
In `@env/claude-dev-lark.sh`:
- Around line 137-139: Update launch_claude so Claude starts with permission
prompts enabled by default; only add --allow-dangerously-skip-permissions when
the user explicitly opts in via a dedicated flag and the required
environment-variable guard, following the existing opt-in pattern in the sibling
launcher.

In `@env/codex-dev-lark.sh`:
- Around line 233-238: Update the LARK_LANE assignment in the launcher’s exec
environment to use the caller-provided LARK_LANE when set, falling back to the
generated lane value otherwise, matching the behavior of env/claude-dev-lark.sh.
Keep the existing --lane handling and other environment assignments unchanged.

In `@env/larkenv`:
- Around line 101-117: Update remove_extra_header so pathname expansion is
disabled while iterating over the unquoted raw value used for semicolon
splitting, preventing header characters such as * and ? from expanding to
filenames. Preserve the existing trimming, target removal, and result
reconstruction behavior.
- Around line 270-278: Update the secret prompt in the three-argument branch to
use Bash’s `read -rs` instead of manually toggling terminal echo with `stty
-echo` and `stty echo`. Remove the explicit stty calls while preserving hidden
input, interruption-safe echo restoration, newline output, and subsequent
`config init --app-secret-stdin` handling.

In `@internal/cmdutil/secheader_test.go`:
- Around line 265-272: Extend the security-header tests around
BaseSecurityHeaders to cover collisions with CLI-owned headers: configure
CliExtraHeaders with both a spoofed HeaderSource and a new X-TT-ENV header, then
assert the CLI’s SourceValue remains authoritative while the non-conflicting
extra header is preserved. Apply the corresponding protection in
BaseSecurityHeaders so extra headers cannot override CLI-set headers.

In `@internal/core/types_test.go`:
- Around line 76-83: Expand
TestResolveEndpoints_RejectsURLAsEndpointDomainOverride into a table-driven
negative test covering URL schemes, path, query, userinfo, ports, and
whitespace-only overrides; rename it to reflect invalid endpoint domain
overrides. For each case, assert ResolveEndpoints(BrandFeishu) falls back to
both the default Open and Accounts endpoints, including the port case currently
accepted by the guard.

In `@internal/envvars/read_test.go`:
- Around line 147-160: Add a regression test near
TestExtraHeaders_RejectsHeaderInjection covering ExtraHeaders with an empty or
unset CliExtraHeaders value and with a value containing only invalid entries;
assert the result is nil in both cases, preserving the contract that no valid
headers returns nil.

In `@internal/envvars/read.go`:
- Line 34: In the header-value sanitization path, replace the agentNameMaxLen
argument used by sanitizeSingleLine with a dedicated extraHeaderValueMaxLen
constant. Define the new limit alongside the existing environment-value limits,
keeping the agent-name limit exclusively for agent-name validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f04d9270-6d77-4fcd-a728-05350d30cbfa

📥 Commits

Reviewing files that changed from the base of the PR and between 56dba0f and e947c82.

⛔ Files ignored due to path filters (2)
  • .codex-dev/tmp/boe-base-button-auth-20260812-2.png is excluded by !**/*.png
  • .codex-dev/tmp/boe-base-button-auth.png is excluded by !**/*.png
📒 Files selected for processing (53)
  • .agents/skills/lark-approval
  • .agents/skills/lark-apps
  • .agents/skills/lark-attendance
  • .agents/skills/lark-base
  • .agents/skills/lark-calendar
  • .agents/skills/lark-contact
  • .agents/skills/lark-doc
  • .agents/skills/lark-drive
  • .agents/skills/lark-event
  • .agents/skills/lark-im
  • .agents/skills/lark-mail
  • .agents/skills/lark-markdown
  • .agents/skills/lark-minutes
  • .agents/skills/lark-note
  • .agents/skills/lark-okr
  • .agents/skills/lark-openapi-explorer
  • .agents/skills/lark-shared
  • .agents/skills/lark-sheets
  • .agents/skills/lark-skill-maker
  • .agents/skills/lark-slides
  • .agents/skills/lark-task
  • .agents/skills/lark-vc
  • .agents/skills/lark-vc-agent
  • .agents/skills/lark-whiteboard
  • .agents/skills/lark-wiki
  • .agents/skills/lark-workflow-meeting-summary
  • .agents/skills/lark-workflow-standup-report
  • A2A_WEB_PARITY_DISCUSSION.md
  • cmd/agents/lark-cli-a2a-web-experience-parity-discussion.md
  • cmd/agents/task-stream-phase1-design.md
  • env/claude-dev-lark.sh
  • env/codex-dev-lark.sh
  • env/larkenv
  • internal/cmdutil/secheader.go
  • internal/cmdutil/secheader_test.go
  • internal/core/types.go
  • internal/core/types_test.go
  • internal/envvars/envvars.go
  • internal/envvars/read.go
  • internal/envvars/read_test.go
  • shortcuts/base/base_dryrun_ops_test.go
  • shortcuts/base/base_shortcuts_test.go
  • shortcuts/base/button_rule.go
  • shortcuts/base/shortcuts.go
  • shortcuts/base/workflow_execute_test.go
  • shortcuts/base/workspace.go
  • shortcuts/base/workspace_ops.go
  • shortcuts/base/workspace_test.go
  • skills/lark-base/SKILL.md
  • skills/lark-base/references/baseapp-protocol-design.md
  • skills/lark-base/references/lark-base-field-json.md
  • tests/cli_e2e/base/base_button_rule_dryrun_test.go
  • tests/cli_e2e/base/base_workspace_dryrun_test.go

Comment thread A2A_WEB_PARITY_DISCUSSION.md Outdated
Comment on lines +210 to +217
#### 方案二:稳定核心类型 + provider extension

这是推荐方案。公共协议只定义少量稳定的生命周期和内容类型:

- 生命周期由 `output.created/delta/updated/completed` 表达;
- 内容使用 `text`、`markdown`、`table`、`chart`、`file`、`form`、`action`、`data` 等通用类型;
- Base 或其他 provider 的特殊信息放在带命名空间的 `extensions` 中;
- 对暂未认识的类型,保留原始结构并生成可读 fallback,不能静默丢弃。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Do not forward arbitrary raw data for unknown public events.

The proposed fallback preserves the original structure for unknown types. This can expose internal tool parameters, prompts, resource identifiers, or other sensitive fields.

Use a redacted or opaque extension for unknown data. Permit raw data only after an explicit visibility and allowlist check.

Also applies to: 254-270

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@A2A_WEB_PARITY_DISCUSSION.md` around lines 210 - 217, Update the
unknown-event fallback described in the stable core type/provider extension
proposal to avoid forwarding arbitrary raw structures. Represent unrecognized
data with a redacted or opaque extension by default, and only preserve raw
fields after explicit visibility validation and allowlist checks; apply the same
rule to the corresponding section around the additional referenced content.

Comment thread A2A_WEB_PARITY_DISCUSSION.md Outdated
Comment on lines +421 to +429
### 7.2 推荐方案

- Task 快照补充 `status_message`、`progress`、`error`、`last_event_cursor`;
- error 至少包含稳定 `code`、`category`、`retryable`、`hint` 和必要的服务端 ground truth;
- `GetTask` 永远提供权威当前状态,事件流用于低延迟更新;
- `SubscribeTask(after_cursor=...)` 用于恢复;游标过期时返回明确错误和最新快照;
- context list 直接提供 `active_task_id`、`awaiting_input`、`last_state` 等一致的 rollup;
- 对回答、确认、取消等有副作用的动作使用 idempotency key;
- CodeAgent 在重试前先读回当前任务状态,避免重复执行。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- referenced document sections ---'
sed -n '400,530p' A2A_WEB_PARITY_DISCUSSION.md
printf '%s\n' '--- all protocol references in the document ---'
rg -n -C 2 'A2A|MCP|SubscribeToTask|after_cursor|cursor|2025-11-25|whats-new' A2A_WEB_PARITY_DISCUSSION.md

Repository: larksuite/cli

Length of output: 260


🌐 Web query:

A2A Protocol v1 whats new SubscribeToTask cursor pagination task listing after_cursor

💡 Result:

The A2A Protocol v1.0, an open standard for communication between AI agents, introduced several key changes to task management, specifically regarding task subscriptions and pagination [1][2][3]. SubscribeToTask In v1.0, the task subscription operation was officially renamed from tasks/resubscribe to SubscribeToTask [1]. This method establishes a streaming connection to receive updates for a specific task that is not in a terminal state [4][5]. Key updates to this method include: - Formal specification of the streaming subscription lifecycle [1]. - Clarified behavior for stream closure when a task reaches a terminal state (completed, failed, canceled, or rejected) [4][1]. - Support for multiple concurrent subscriptions per task [1]. - The operation is required to return the current state of the task as the first event in the stream, which avoids potential information loss between a standard GetTask call and the start of the subscription [4][5]. Task Listing and Cursor Pagination A major change in v1.0 is the mandate to use cursor-based pagination for the ListTasks operation to ensure scalability and performance [1][5]. This replaces the legacy page-based (offset) pagination used in previous versions (e.g., v0.3.0) [1]. - Mechanism: The API uses a pageToken (request) and next_page_token (response) to navigate results [4][6]. - Sorting: Implementations must return tasks sorted by their status timestamp in descending order (most recently updated tasks first) [6][7]. - Implementation Note: Clients are expected to store the next_page_token from the response to retrieve subsequent pages in a loop, rather than relying on page numbers [1]. These changes were part of a broader effort in v1.0 to improve protocol maturity, clarify specifications, and address enterprise deployment requirements [1][3].

Citations:


🌐 Web query:

MCP Tasks 2025-11-25 experimental task subscription replay cursor after_cursor

💡 Result:

In the Model Context Protocol (MCP) specification version 2025-11-25, tasks were introduced as an experimental feature that utilized cursor-based pagination for the tasks/list method [1][2]. For the tasks/list request, the pagination mechanism functions as follows: - Requestors can include an optional cursor field in the request parameters to specify their position in a paginated list [1][2]. - The server responds with a list of tasks and a nextCursor field if additional tasks are available [1][2]. - Requestors must treat these cursors as opaque tokens and are not to attempt to parse or modify them [1][2]. While the term "after_cursor" is common in general API design for pagination (often used as a parameter key or path) [3][4], the official 2025-11-25 MCP specification uses cursor as the input parameter and nextCursor as the response field for task pagination [1][2]. References to "after_cursor" in MCP-related codebases or discussions [5][6] generally refer to the broader implementation pattern rather than a mandatory field name defined in the 2025-11-25 protocol specification itself. As of August 2026, the experimental task feature has evolved significantly, having been moved out of the core protocol and into an official, versioned extension (io.modelcontextprotocol/tasks) [7][8][9]. New implementations are expected to follow this current extension specification rather than the legacy 2025-11-25 design [10][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
file="$(git ls-files | awk 'tolower($0) ~ /(^|\/)a2a_web_parity_discussion\.md$/ {print; exit}')"
if [ -z "$file" ]; then
  echo "A2A_WEB_PARITY_DISCUSSION.md not found in tracked files" >&2
  exit 1
fi
printf 'FILE=%s\n' "$file"
printf '%s\n' '--- referenced document sections ---'
sed -n '400,530p' "$file"
printf '%s\n' '--- all protocol references in the document ---'
rg -n -C 2 'A2A|MCP|SubscribeToTask|after_cursor|cursor|2025-11-25|whats-new' "$file"

Repository: larksuite/cli

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -e
file="$(git ls-files | awk 'tolower($0) ~ /(^|\/)a2a_web_parity_discussion\.md$/ {print; exit}')"
if [ -z "$file" ]; then
  echo "A2A_WEB_PARITY_DISCUSSION.md not found in tracked files" >&2
  exit 1
fi
printf 'FILE=%s\n' "$file"
sed -n '400,530p' "$file"
rg -n -C 2 'A2A|MCP|SubscribeToTask|after_cursor|cursor|2025-11-25|whats-new' "$file"

Repository: larksuite/cli

Length of output: 207


🌐 Web query:

site:a2a-protocol.org/v1.0.0/specification SubscribeToTask pageToken next_page_token replay cursor

💡 Result:

The terms SubscribeToTask, pageToken, next_page_token, and cursor are associated with the A2A Protocol, which utilizes cursor-based pagination for listing and managing task data [1]. In this specification, pageToken and next_page_token are used to implement cursor-based pagination for retrieving lists of tasks [1]. This approach is preferred over offset-based pagination because it provides better performance and consistency for large datasets, avoiding the performance degradation associated with deep pagination [1]. The SubscribeToTask operation is a streaming method designed to provide real-time updates on a specific task [1]. To ensure no information is lost during the transition between requesting current state and subscribing to future updates, the protocol requires that the operation return the current state of the task as the first event in the stream [1]. While pagination tokens are standard for List-style methods (like ListTasks), the subscription mechanism focuses on maintaining a live connection to the task state rather than paginating results [1].

Citations:


🌐 Web query:

site:modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks experimental tasks/list cursor nextCursor SubscribeTask

💡 Result:

The tasks/list operation is an experimental feature of the Model Context Protocol (MCP) introduced in version 2025-11-25 [1]. It uses cursor-based pagination to manage task retrieval [1]. When a requestor calls tasks/list, they can provide an optional cursor parameter in the request params [1]. The server's response includes a nextCursor field, which the server must provide if additional tasks are available [1]. Requestors are required to treat these cursors as opaque tokens and must not attempt to parse or modify them [1]. Regarding SubscribeTask, there is no evidence in the provided documentation of a feature or method by that name within the MCP experimental tasks specification [1]. Example of the tasks/list request and response structure: { "jsonrpc": "2.0", "id": 5, "method": "tasks/list", "params": { "cursor": "optional-cursor-value" } } { "jsonrpc": "2.0", "id": 5, "result": { "tasks": [...], "nextCursor": "next-page-cursor" } }

Citations:


Separate standard capabilities from Lark-specific recovery semantics.

Mark after_cursor as a Lark-specific extension. Pin the A2A and MCP protocol versions. Do not cite A2A SubscribeToTask or MCP Tasks as evidence that standard event replay is defined.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@A2A_WEB_PARITY_DISCUSSION.md` around lines 421 - 429, Update section “7.2
推荐方案” to distinguish standard A2A/MCP capabilities from Lark-specific recovery
semantics: label `SubscribeTask(after_cursor=...)` and cursor-expiry recovery as
Lark-specific extensions, explicitly pin the relevant A2A and MCP protocol
versions, and remove any implication that A2A `SubscribeToTask` or MCP Tasks
define standard event replay.

Source: MCP tools

Comment on lines +419 to +438
#### 5.2.1 定义 provider-neutral 的公开事件模型

建议至少支持以下事件类别:

| 事件 | 语义 |
|---|---|
| `task.started` | 任务已受理,返回 task/context 标识 |
| `task.status` | submitted/working/阶段摘要变化 |
| `message.created` | 创建一个公开消息对象 |
| `message.delta` | 向已有消息追加公开文本 |
| `message.updated` | 替换或完成已有消息对象 |
| `tool.started` | 可公开的工具类别开始执行 |
| `tool.completed` | 工具完成及脱敏摘要 |
| `artifact.updated` | 产物 processing/ready/failed 变化 |
| `input.required` | 结构化问题组,任务暂停 |
| `auth.required` | 需要用户授权,任务暂停 |
| `task.completed` | 成功终态及最终结果定位 |
| `task.failed` | 失败终态及稳定错误分类 |
| `stream.heartbeat` | 连接存活,不代表业务进度 |
| `stream.end` | 本次观察结束及 reason |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Choose one normative public event vocabulary.

This document defines message.*, tool.*, task.completed, task.failed, and stream.end. A2A_WEB_PARITY_DISCUSSION.md defines output.* and task.error, while cmd/agents/task-stream-phase1-design.md defines task.snapshot and task.stream_end.

Add a versioned mapping from phase-one compatibility events to the target event model, or select one canonical vocabulary before implementation. Otherwise, reducers and contract tests can implement incompatible protocols.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/agents/lark-cli-a2a-web-experience-parity-discussion.md` around lines 419
- 438, Establish one versioned, normative public event vocabulary before
implementation, centered on the event model in section 5.2.1 or an explicitly
selected alternative. Add a compatibility mapping covering output.*, task.error,
task.snapshot, and task.stream_end to the canonical equivalents, and update the
referenced design documents, reducers, and contract-test expectations to use
that mapping consistently.

Comment thread cmd/agents/task-stream-phase1-design.md Outdated
Comment on lines +114 to +115
| `--timeout` | `5m` | 最长观察时间;`0` 表示无界,超时不取消远端任务 |
| `--format` | `ndjson` | 一期只接受 `ndjson`;其它值返回 typed validation error |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file=$(fd -t f '^task-stream-phase1-design\.md$' . | head -n 1)
printf 'FILE=%s\n' "$file"
wc -l "$file"
sed -n '190,235p' "$file"
sed -n '100,125p' "$file"
rg -n -C 3 -- '--timeout|deadline|backoff|GetTask|sleep|timer|poll' "$file"

Repository: larksuite/cli

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -eu
git ls-files | rg '(^|/)task-stream-phase1-design\.md$|cmd/agents'

Repository: larksuite/cli

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- root ---'
pwd
ls -la
printf '%s\n' '--- candidate paths ---'
find . -type f -name 'task-stream-phase1-design.md' -print
printf '%s\n' '--- repository status ---'
git status --short 2>&1 || true

Repository: larksuite/cli

Length of output: 2907


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- cmd/agents files ---'
find cmd/agents -maxdepth 3 -type f -print 2>/dev/null | sort
printf '%s\n' '--- matching design text ---'
rg -n -i -C 3 '最长观察时间|一期只接受|GetTask|backoff|--timeout|ndjson' cmd .github README* 2>/dev/null || true

Repository: larksuite/cli

Length of output: 19133


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- branches and HEAD ---'
git branch --all --no-color
git rev-parse HEAD
printf '%s\n' '--- tracked cmd/agents entries ---'
git ls-tree -r --name-only HEAD -- cmd/agents
printf '%s\n' '--- recent commits mentioning task stream ---'
git log --oneline --all -- cmd/agents | head -20

Repository: larksuite/cli

Length of output: 446


🏁 Script executed:

#!/bin/bash
set -eu
path=cmd/agents/task-stream-phase1-design.md
printf '%s\n' '--- current commit change ---'
git show --stat --oneline HEAD
git show --format= --name-status HEAD -- "$path"
printf '%s\n' '--- file in parent commit ---'
if git cat-file -e "HEAD^:$path" 2>/dev/null; then
  git show "HEAD^:$path" | nl -ba | sed -n '100,125p'
  git show "HEAD^:$path" | nl -ba | sed -n '198,235p'
else
  printf '%s\n' 'not present in parent'
fi

Repository: larksuite/cli

Length of output: 1087


🏁 Script executed:

#!/bin/bash
set -eu
path=cmd/agents/task-stream-phase1-design.md
printf '%s\n' '--- CLI contract ---'
git show "HEAD^:$path" | sed -n '105,122p'
printf '%s\n' '--- polling state machine ---'
git show "HEAD^:$path" | sed -n '198,235p'
printf '%s\n' '--- timeout and error rules ---'
git show "HEAD^:$path" | sed -n '275,325p'
printf '%s\n' '--- tests related to timeout ---'
git show "HEAD^:$path" | sed -n '455,510p'

Repository: larksuite/cli

Length of output: 4044


🏁 Script executed:

#!/bin/bash
set -eu
path=cmd/agents/task-stream-phase1-design.md
git show "HEAD^:$path" | sed -n '315,365p'

Repository: larksuite/cli

Length of output: 1467


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Poll:
    request_duration: float
    sleep_after: float

def documented_elapsed(polls, timeout):
    elapsed = 0.0
    for poll in polls:
        elapsed += poll.request_duration
        # The design checks timeout only after a completed poll and before
        # starting the next request; its sleep is not deadline-aware.
        if elapsed >= timeout:
            return elapsed, "timeout"
        elapsed += poll.sleep_after
    return elapsed, "still polling"

cases = {
    "sleep exceeds timeout": [Poll(0.1, 5.0)],
    "request exceeds timeout": [Poll(6.0, 0.0)],
}
for name, polls in cases.items():
    elapsed, result = documented_elapsed(polls, 1.0)
    print(f"{name}: result={result}, elapsed={elapsed:.1f}s, deadline=1.0s")
PY

Repository: larksuite/cli

Length of output: 293


Enforce --timeout as a hard observation deadline.

The polling flow can exceed the configured timeout during backoff or GetTask. Use a deadline-aware context and interruptible timer. Define whether the initial GetTask must complete before the deadline. Add a test with a timeout shorter than the next backoff interval.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/agents/task-stream-phase1-design.md` around lines 114 - 115, Update the
task polling flow described in the design so --timeout is enforced as a hard
observation deadline, using a deadline-aware context for polling and GetTask
plus an interruptible timer during backoff. Explicitly define whether the
initial GetTask may finish after the deadline, and add coverage where the
timeout is shorter than the next backoff interval to verify prompt termination
without cancelling the remote task.

Comment thread cmd/agents/task-stream-phase1-design.md Outdated
Comment on lines +156 to +190
### 6.3 结束事件

当观察窗口结束时输出一条控制事件:

```json
{
"ok": true,
"identity": "user",
"event": {
"type": "task.stream_end",
"sequence": 4,
"observed_at": "2026-08-03T10:26:24Z",
"reason": "completed"
},
"data": {
"task_id": "task_123",
"state": "completed",
"is_terminal": true
}
}
```

`reason` 枚举:

| reason | 含义 | 远端任务是否停止 |
|---|---|---|
| `completed` | 成功终态 | 是 |
| `failed` | 失败终态 | 是 |
| `rejected` | 拒绝终态 | 是 |
| `canceled` | 取消终态 | 是 |
| `input_required` | 等待用户回答 | 暂停 |
| `auth_required` | 等待授权 | 暂停 |
| `timeout` | CLI 观察窗口到期 | 否 |

结束事件不重复完整 messages/artifacts,只携带任务标识和状态。完整内容已经由结束前最后一条 `task.snapshot` 输出。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make stream termination self-describing on failures.

The task.stream_end example uses "ok": true even when reason is failed, rejected, or canceled. The error table also omits a stream_end event for API errors, content-safety blocks, and write failures.

Define whether ok means transport success or task success. Prefer a separate task outcome field. Emit a non-sensitive error end event when stdout is still writable. If writing fails, document that the exit code is the only terminal signal. Keep the exit code as a secondary signal, not the sole task-status indicator.

Also applies to: 328-346

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/agents/task-stream-phase1-design.md` around lines 156 - 190,
更新“结束事件”及对应错误表,明确 task.stream_end 中 ok 表示传输成功而非任务成功,并增加独立的任务结果字段以区分
completed、failed、rejected、canceled 等状态。为 API 错误、内容安全拦截和写入失败补充终止行为:stdout
仍可写时发送不含敏感信息的错误结束事件;写入失败时说明只能依赖退出码,但将退出码保留为辅助信号而非唯一任务状态来源。

Comment on lines +190 to +196
func TestBaseButtonRuleValidateRejectsInternalWorkflowID(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
err := runShortcut(t, BaseButtonRuleBind, []string{"+button-rule-bind", "--base-token", "app_x", "--table-id", "tbl_1", "--field-id", "fld_1", "--workflow-id", "123456"}, factory, stdout)
if err == nil || !strings.Contains(err.Error(), "public wkf workflow ID") {
t.Fatalf("err=%v", err)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert typed validation metadata.

This test only matches the error message. An untyped error with the same message passes.

Assert *errs.ValidationError metadata, including the invalid-argument subtype and --workflow-id parameter.

As per coding guidelines, “Error tests must assert typed metadata and cause preservation rather than message text alone.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/base/workflow_execute_test.go` around lines 190 - 196, Update
TestBaseButtonRuleValidateRejectsInternalWorkflowID to assert that the returned
error is an *errs.ValidationError, then verify its invalid-argument subtype and
associated parameter is --workflow-id. Replace the message-only validation while
preserving the existing rejection scenario and ensure the validation cause
metadata is checked rather than relying on matching error text.

Source: Coding guidelines

Comment thread shortcuts/base/workspace.go Outdated
Comment on lines +12 to +112
var BaseWorkspaceCreate = common.Shortcut{
Service: "base",
Command: "+workspace-create",
Description: "Create a Base workspace",
Risk: "write",
Scopes: []string{"base:workspace:create"},
AuthTypes: authTypes(),
Flags: []common.Flag{
{Name: "name", Desc: "workspace name", Required: true},
{Name: "icon", Desc: "workspace icon"},
},
Tips: []string{
`Example: lark-cli base +workspace-create --name "Sales Workspace"`,
"Record the returned workspace_token; workspace entity commands need it.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return validateWorkspaceCreate(runtime)
},
DryRun: dryRunWorkspaceCreate,
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
return executeWorkspaceCreate(runtime)
},
}

var BaseWorkspaceEntityList = common.Shortcut{
Service: "base",
Command: "+workspace-entity-list",
Description: "List Base or BaseApp entities in a workspace",
Risk: "read",
Scopes: []string{"base:workspace:read"},
AuthTypes: authTypes(),
Flags: []common.Flag{
workspaceTokenFlag(true),
workspaceEntityTypeFlag(false),
{Name: "page-size", Type: "int", Default: "100", Desc: "page size per request, range 1-100"},
{Name: "page-token", Desc: "pagination token from the previous response"},
},
Tips: []string{
`Example: lark-cli base +workspace-entity-list --workspace-token <workspace_token> --type base --page-size 100`,
"Use entity_id from this output when removing an entity from the workspace tree.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return validateWorkspaceEntityList(runtime)
},
DryRun: dryRunWorkspaceEntityList,
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
return executeWorkspaceEntityList(runtime)
},
}

var BaseWorkspaceEntityAdd = common.Shortcut{
Service: "base",
Command: "+workspace-entity-add",
Description: "Add a Base or BaseApp entity to a workspace",
Risk: "write",
Scopes: []string{"base:workspace:write"},
AuthTypes: authTypes(),
Flags: []common.Flag{
workspaceTokenFlag(true),
workspaceEntityTypeFlag(true),
{Name: "token", Desc: "base_token or app_token of the entity to add", Required: true},
{Name: "prev-entity-id", Desc: "insert after this workspace entity ID"},
{Name: "to-last", Type: "bool", Desc: "append the entity to the end of the workspace"},
},
Tips: []string{
`Example: lark-cli base +workspace-entity-add --workspace-token <workspace_token> --type base --token <base_token> --to-last`,
"`--token` is the underlying Base/BaseApp token, not workspace entity_id.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return validateWorkspaceEntityAdd(runtime)
},
DryRun: dryRunWorkspaceEntityAdd,
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
return executeWorkspaceEntityAdd(runtime)
},
}

var BaseWorkspaceEntityRemove = common.Shortcut{
Service: "base",
Command: "+workspace-entity-remove",
Description: "Remove an entity relation from a workspace tree",
Risk: "high-risk-write",
Scopes: []string{"base:workspace:write"},
AuthTypes: authTypes(),
Flags: []common.Flag{
workspaceTokenFlag(true),
{Name: "entity-id", Desc: "workspace entity ID to remove", Required: true},
},
Tips: []string{
`Example: lark-cli base +workspace-entity-remove --workspace-token <workspace_token> --entity-id <entity_id> --yes`,
"This only removes the entity from the workspace tree; it does not delete the underlying Base or BaseApp resource.",
baseHighRiskYesTip,
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return validateWorkspaceEntityRemove(runtime)
},
DryRun: dryRunWorkspaceEntityRemove,
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
return executeWorkspaceEntityRemove(runtime)
},
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add self-contained live E2E coverage for the new workspace shortcuts.

The supplied tests cover dry-run behavior only. These shortcuts include persistent write operations. +workspace-create has no exposed cleanup command, so a simple live test would leak Workspace state.

Add a cleanup workflow and live coverage for create, list, add, remove, and cleanup. Do not merge a persistent live test that cannot clean up after failure.

As per coding guidelines: “new shortcuts require live E2E coverage” and “Live E2E tests must be self-contained: create, use, and clean up their resources even after failure.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/base/workspace.go` around lines 12 - 112, Add self-contained live
E2E coverage for BaseWorkspaceCreate, BaseWorkspaceEntityList,
BaseWorkspaceEntityAdd, and BaseWorkspaceEntityRemove, covering the complete
create, list, add, and remove workflow. Introduce an exposed cleanup shortcut or
equivalent cleanup workflow for the workspace created by the test, and ensure
cleanup runs even when earlier assertions or operations fail so no persistent
workspace state is leaked.

Source: Coding guidelines

Comment thread skills/lark-base/references/baseapp-protocol-design.md Outdated
Comment thread skills/lark-base/SKILL.md
| `1254045` 字段名不存在 | 重新 `+field-list`,使用真实字段名或字段 ID;注意空格、大小写和跨表字段 |
| `1254015` 字段值类型不匹配 | 先 `+field-list`,再按 [lark-base-cell-value.md](references/lark-base-cell-value.md) 构造 CellValue |
| `Invalid discriminator value`(字段写入缺 `type`) | 按完整提交规则读取当前字段,只改目标内容后提交;不要只补 `type` 重试 |
| `Invalid discriminator value`(字段写入) | 先确认字段 JSON 有合法 `type`。若请求已是 `{"type":"button",...}`,通常表示当前调用的 Base OpenAPI 后端或 `bitable.node.mcp` 仍未部署按钮字段 schema;不要往字段 JSON 里追加 `workflow_id`、内部 Workflow ID 或底层 `property.trigger`,应等待对应环境升级后用同一最小 JSON 重试 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a button-rule routing entry to SKILL.md.

The recovery rule names button fields but does not route direct binding requests. Add a quick-routing entry for +button-rule-bind, +button-rule-get, and +button-rule-unbind. Link it to lark-base-field-json.md for the field-creation sequence.

As per coding guidelines: skills/<name>/SKILL.md must contain domain routing and cross-command workflows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/lark-base/SKILL.md` at line 153, Update the routing section in
SKILL.md to add entries for +button-rule-bind, +button-rule-get, and
+button-rule-unbind, directing these requests to lark-base-field-json.md and its
field-creation sequence. Keep the existing button-field recovery guidance
unchanged and ensure the new entries provide domain routing and cross-command
workflow coverage.

Source: Coding guidelines

Comment on lines +15 to +79
func TestBaseButtonRuleDryRun(t *testing.T) {
setBaseDryRunConfigEnv(t)

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

tests := []struct {
name string
args []string
wantMethod string
wantBody string
}{
{
name: "bind",
args: []string{
"base", "+button-rule-bind",
"--base-token", "app_x",
"--table-id", "tbl_x",
"--field-id", "fld_x",
"--workflow-id", "wkf_x",
"--dry-run",
},
wantMethod: "PUT",
wantBody: "wkf_x",
},
{
name: "get",
args: []string{
"base", "+button-rule-get",
"--base-token", "app_x",
"--table-id", "tbl_x",
"--field-id", "fld_x",
"--dry-run",
},
wantMethod: "GET",
},
{
name: "unbind",
args: []string{
"base", "+button-rule-unbind",
"--base-token", "app_x",
"--table-id", "tbl_x",
"--field-id", "fld_x",
"--dry-run",
},
wantMethod: "PUT",
wantBody: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{Args: tt.args, DefaultAs: "bot"})
require.NoError(t, err)
result.AssertExitCode(t, 0)

out := result.Stdout
require.Equal(t, "/open-apis/base/v3/bases/app_x/tables/tbl_x/fields/fld_x/button_rule", clie2e.DryRunGet(out, "api.0.url").String(), out)
require.Equal(t, tt.wantMethod, clie2e.DryRunGet(out, "api.0.method").String(), out)
if tt.wantMethod == "PUT" {
require.Equal(t, tt.wantBody, clie2e.DryRunGet(out, "api.0.body.workflow_id").String(), out)
}
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add self-contained live E2E coverage for the new shortcuts.

This file verifies dry-run output only. shortcuts/base/workflow_execute_test.go uses HTTP mocks.

Add live bind, get, and unbind cases. Create fixtures, verify bound and unbound readback, and clean up all resources after failure.

As per coding guidelines, “new shortcuts require live E2E coverage.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cli_e2e/base/base_button_rule_dryrun_test.go` around lines 15 - 79,
Extend TestBaseButtonRuleDryRun with self-contained live E2E coverage for the
bind, get, and unbind shortcuts, following the HTTP-mock setup used by
workflow_execute_test.go. Create the required base, table, field, and workflow
fixtures, verify get returns the bound workflow and then confirms the field is
unbound, and register cleanup for every created resource so teardown runs even
when assertions fail.

Sources: Coding guidelines, Learnings

Change-Id: I3f05245da74f75e06414a50c60019cb81863e489
Change-Id: I910b31e171fa63a7e700189f0b6c7eecf320c9fb
@github-actions github-actions Bot added size/M Single-domain feat or fix with limited business impact and removed size/L Large or sensitive change across domains or core paths labels Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/base PR touches the base domain size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants