Commit Graph

1080 Commits

Author SHA1 Message Date
caichengjie.viper 2509017c28 chore(lark-doc): keep script skill unchanged 2026-08-25 19:07:35 +08:00
caichengjie.viper 5c85fb4c8f fix(docs): recover PowerShell-dequoted decision JSON 2026-08-25 17:32:28 +08:00
caichengjie.viper 84828c6db7 refactor(docs): make quote recovery error-first 2026-08-25 16:34:35 +08:00
caichengjie.viper 9fd9fff095 fix(docs): handle shell-quoted presentation JSON 2026-08-25 16:00:33 +08:00
caichengjie.viper 648e6a8beb docs(lark-doc): simplify background draft cleanup guidance 2026-08-25 15:41:23 +08:00
caichengjie.viper 1bddd0c704 docs(cli): clarify UTF-8 file guidance 2026-08-25 14:39:15 +08:00
caichengjie.viper 05f46105e5 refactor(docs): narrow draft cleanup skill guidance 2026-08-24 19:06:37 +08:00
caichengjie.viper 48964deef6 fix(docs): clean up draft workspaces without agent prompts 2026-08-24 15:38:51 +08:00
fangshuyu 0e0320380c fix(docs): avoid shell text pipelines for agent drafts 2026-08-24 15:35:33 +08:00
yballul-bytedance 33cfa46a8f feat(base): document app default page reuse (#2436)
Co-authored-by: yballul-bytedance <273011618+yballul-bytedance@users.noreply.github.com>
2026-08-24 14:53:46 +08:00
zhangjun-bytedance 56ad837c3d feat: event organizer transfer bot to user (#2448) 2026-08-23 14:44:17 +08:00
lark-cli-external-pr-digest[bot] 423e81fcc6 chore: release v1.0.89 (#2447) v1.0.89 2026-08-21 19:12:19 +08:00
wangweiming-01 52f970f23e refactor(shortcuts): remove MCP text location paths (#2439) 2026-08-21 17:27:18 +08:00
Vee fbd1aa49cd feat: add IM read status shortcuts (#2318) 2026-08-21 16:47:49 +08:00
R0bynZhu b343e67639 feat(slides): use office_slide_file parent_type for imported office presentations (#2441)
Image uploads to a presentation hard-coded parent_type=slide_file at every
entry point. Imported "office" presentations carry either a legacy synthetic
token prefix ("fake_office_" / "local_office_") or a 28-character token whose
interleaved product/region marker is "OFL0X", and for those the drive backend
requires parent_type=office_slide_file. This mirrors the office_sheet_file rule
the sheets domain already applies: the token shapes are identical, because an
imported office file is an imported office file whether it backs a spreadsheet
or a deck.

Funnel the selection through one slides-domain helper so the rule lives in a
single place and every image-upload path stays consistent with its own dry-run
preview. As in sheets, the rule stays inside the domain rather than leaking
into common.UploadDriveMediaAllTyped, which mail/doc/drive/base/calendar share.

- Replace the slidesMediaParentType const with slidesMediaParentType(token),
  backed by isOfficePresentation(token); keep the native and office values as
  named constants.
- Route both parent_type call sites through it: uploadSlidesMedia (the Execute
  path shared by +media-upload and the <img src="@path"> placeholder pipeline
  behind +create / +add-slide / +update-slide) and appendSlidesUploadDryRun.
- Known gap, documented at the helper: when --presentation is a wiki URL the
  dry-run only has a "<resolved_slides_token>" placeholder, since the real
  token needs a get_node call the preview must not make, so such a preview
  shows slide_file regardless. Execute is unaffected -- it resolves first.

The negative half of the mapping is what the tests weight most heavily. The
backend does not validate parent_node against parent_type, so a native deck
misread as office still uploads successfully and only surfaces later as an
image that will not render, far from its cause; the marker check is therefore
pinned at its exact length and offsets rather than a looser "contains OFL0X".

Tests:
- shortcuts/slides/slides_media_parent_type_test.go: 14-case pure-function
  table (off-by-one length, prefix appearing mid-string, wiki placeholder),
  a real-multipart Execute assertion across four token shapes, and the
  +add-slide / +update-slide placeholder dry-run previews.
- tests/cli_e2e/slides/slides_image_upload_dryrun_test.go: five cases through
  the built binary, covering every surface a local file can enter through.
- Verified non-vacuous: short-circuiting the office branch fails all three
  package tests plus the e2e lane.

Evidence note: office_slide_file is confirmed accepted by upload_all, and the
symmetry with office_sheet_file is exact, but this has not been exercised
against a real imported-pptx presentation to confirm slide_file fails there.
2026-08-21 15:12:35 +08:00
ethan-zhx cc015bac28 feat(skills): extend slides XML schema (#2442) 2026-08-21 15:08:46 +08:00
yballul-bytedance 79b8647196 feat(base): add template discovery and form question field reuse (#2340)
Co-authored-by: yballul-bytedance <273011618+yballul-bytedance@users.noreply.github.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
2026-08-21 13:22:24 +08:00
bytedance-zhangbinkai 42060154dd feat(base): support button workflow bindings (#2437) 2026-08-21 10:50:37 +08:00
zhaoleibd e525beb8d6 feat(skills): unify meeting related skills (#2387)
* feat(skills): unify meeting guidance

* fix(meeting): restore domain boundary guidance

* docs(meeting): remove agent rollout qualification guidance

* docs(meeting): front-load skill routing description

* docs(meeting): refine identity and command guidance

* docs(meeting): clarify identity and pagination guidance

* docs(meeting): fix minutes todo detail command

* fix(meeting): clarify artifact query routing

* fix(meeting): improve live meeting skill recall

* fix(qualitygate): generate valid minute token placeholders

* fix(meeting): address unified skill review findings

* docs(meeting): add minutes permission guidance

* docs(lark-meeting): 更新SKILL.md并新增会议问答引导脚本

1. 优化SKILL.md表格排版与快速行动章节内容,新增批量获取当日会议脚本的使用说明
2. 新增meeting_qa_bootstrap.py脚本,实现一站式采集当日进行中、已结束会议及未来日程,生成可直接执行的命令引导

* docs(calendar): clarify today's meeting lookup

* revert(meeting): remove meeting Q&A bootstrap guidance

* fix(skills): register lark-meeting suite keywords

---------

Co-authored-by: maozhixiang <maozhixiang@bytedance.com>
2026-08-21 10:49:46 +08:00
wanghm25 da371dc242 feat(base): add dashboard and form share shortcuts (#2282)
- preserve explicit false values and validate partial share updates
- add dry-run and deployment-gated live E2E coverage
- document share routing in the bundled Base skill
2026-08-20 22:23:40 +08:00
Yuxuan Zhao bbcdf65110 test(drive): retry transient async cleanup contention (#2397) 2026-08-20 20:46:07 +08:00
Neseria f28a418019 feat(base): add --position and statistics number_format to dashboard-block create/update (#2118)
* feat(base): add --position and statistics number_format to dashboard-block create/update

Add an optional top-level --position flag ({x,y,w,h} JSON, parsed but not
coordinate-validated, passed through as a sibling of name/type/data_config) and
optional statistics data_config.number_format ({formatName,precision}) with
light enum + 0-9 integer validation. Both are backward compatible. Body
assembly is unified in a shared buildDashboardBlockBody helper so DryRun and
Execute stay isomorphic. Adds toIntStrict for strict precision parsing, focused
helper/execute/dry-run tests, an E2E dry-run test, and syncs the lark-base
dashboard + data-config skill references.

Co-authored-by: TRAE CLI <noreply@bytedance.com>

* fix(base): validate number_format on update path and add symmetry tests

The dashboard-block-update command parsed data_config but never ran the
statistics number_format check, so an illegal formatName/precision slipped
through locally while create rejected it — violating the SSOT + backend-design
§4.5 promise of CLI-side interception on BOTH paths. Update has no --type flag
(block type is immutable) and intentionally skips strong type validation, so it
now reuses the shared validateNumberFormat sub-validator that
validateBlockDataConfig delegates to, keeping create/update symmetric without
demanding table_name/series on a number_format-only update.

Also: add tests for the --no-validate bypass on create+update, a combined
update carrying position + number_format + name, and extend the DryRun/Execute
body isomorphism assertion to the update path. Clarify the --position flag Desc
that coordinate bounds are advisory (not validated locally or server-side) and
sync the lark-base SKILL.md routing table for --position / number_format.

Co-authored-by: TRAE CLI <noreply@bytedance.com>

* fix(base): align dashboard block validation paths

Validate dashboard block JSON consistently across dry-run and execute paths, enforce statistics number_format boundaries, and add layout precision workflow coverage and documentation.

* fix(base): resolve dashboard layout doc contradictions and harden isomorphism test

Follow-up to the --position / number_format feature, addressing review findings.

Docs (SSOT contradictions):
- SKILL.md:135 and lark-base-dashboard.md still told agents that dashboard
  shortcuts cannot set x/y/w/h and to offer auto-layout instead, which would
  have left --position unreachable through the skill. Both statements are now
  scoped to +dashboard-arrange, which genuinely cannot take coordinates.
- number_format was documented as supporting sub-field merge on update. That
  contradicts the update Tips and lark-base-dashboard.md's own data_config
  rule ("每个传入的字段内部是全量替换"). Documented as whole-key replacement
  and made the update Tip example carry formatName back.
- Trimmed both reference sections: dropped the duplicated field table, the
  restated validation blockquote, the standalone bash example and the 4-column
  comparison table; kept the enum table and the two load-bearing gotchas.
  Reformatted the example to the file's multi-line JSON style, and generalized
  the 场景 3 --position argument to '{...}' like its neighbours.

Tests:
- The isomorphism check called buildDashboardBlockBody twice with the same
  arguments, so it could never fail. Replaced with an end-to-end comparison of
  the --dry-run preview body against the body captured from Execute; verified
  it fails under single-path fault injection.
- The live workflow now updates to values distinct from the create call and
  asserts them on read-back, instead of asserting substrings that the created
  state already satisfied. Dropped the position read-back assertion: this
  iteration does not contract get to echo coordinates.
- Filled in the two missing --no-validate cells (create data-config, update
  position).

Cleanup:
- Deleted the inline DryRun closures; both commands now point at the
  dryRunDashboardBlock* functions, matching the DryRun: dryRunX convention used
  across the package and removing the second body-assembly site.
- Rewrote the update comment that referenced review-round codenames and an
  external design doc section to be self-contained.

* fix(base): keep dashboard dry-run previews free of empty identifiers

Wiring the block create/update commands to the shared dryRunDashboardBlock*
functions routed them through dryRunDashboardBase, which Set all three
identifiers unconditionally. A create preview has no block_id yet, so it began
advertising "block_id": "" — an argument that reads as failed to resolve.

Skip empty values in the shared helper rather than special-casing create, which
also clears the same pre-existing noise from the +dashboard-arrange preview.
Pinned with a test asserting a create preview carries base_token and
dashboard_id and no block_id.

* fix(base): require complete --position objects and close the arrange/position gap

Round-2 review follow-up. Three findings, all one-liners in effect, that
compounded into a real failure mode: an agent told to "move this chart to the
right half" could send a partial position, have it accepted, and silently
resize the block to nothing — with no coordinate read-back to diagnose it.

- --position now requires all four of x/y/w/h. The server fills missing
  coordinates with zero rather than leaving them alone, so a partial object is
  a resize disguised as a move. Only the object's shape is checked; coordinate
  VALUES stay unvalidated (out-of-range, negative and overlapping still pass
  through) as documented. The check is semantic, so --no-validate skips it
  while the JSON parse still runs — the same split the rest of this command
  pair already uses. Rejected the alternative of validating ranges too: that
  would contradict the documented dws-aligned pass-through contract.
- +dashboard-arrange's Tips now point at --position. The cross-reference was
  one-directional: create/update told agents about arrange, but arrange — the
  command an agent reaches for first when asked to "fix the layout" — never
  mentioned that exact placement had become possible.
- Documented that coordinates are write-only this iteration. The reference doc
  offered "replicate an existing dashboard's layout" as a use case while the
  PR itself scopes out coordinate read-back, sending agents to look for x/y/w/h
  that get/list do not return.

Also from the same review:
- The dry-run builders no longer discard buildDashboardBlockBody's error. It is
  unreachable while Validate parses the same flags first, but returning nil
  makes the runner fail loudly instead of previewing a body with a field
  silently missing.
- Added precision cases that run through the real command. The existing
  table-driven ones decode with UseNumber and hit toIntStrict's json.Number
  branch, which production never takes — parseJSONObject uses a plain
  json.Unmarshal, so precision always arrives as float64.
- coverage.md now says which four commands rest solely on the credential-gated
  live test that has not been executed yet.
- Marked the number_format fallback claim as unverified against the backend.

* fix(base): close the position guard's null hole and the contract drift it left behind

Round-3 review follow-up. Two of these were introduced by the previous
follow-up commit, not by the original feature.

- The --position completeness guard only asked whether the key was present,
  and a JSON null key IS present. `{"x":6,"y":null,"w":null,"h":null}` sailed
  through the very check meant to stop it — the exact scenario the guard's own
  comment describes. Each coordinate must now actually decode as a number, so
  null, strings, objects and bools are rejected alongside missing keys. This is
  still a shape check: out-of-range, negative and fractional values keep
  passing through as documented. The package's neighbours (`cfg["text"].(string)`,
  `table_name`) already validate required fields with a type assertion; this
  was the one place that did not. Mutation-verified: reverting the assertion
  turns the explicit-nulls case red.
- coverage.md claimed `+dashboard-block-get` "reads back position" while the
  test it cites deliberately stopped asserting coordinates — a line the
  previous commit invalidated and did not update. It now says number_format
  only. The `+dashboard-block-update` row also claimed dry-run coverage for
  number_format that only the unexecuted live test provides.
- dashboard-block-data-config.md still said the update path does no local
  validation, which commit bb7d8fbc made false in this same PR. An agent
  reading it would not expect exit 2 and might reach for --no-validate, which
  now also disables the position guard.

Also from that review:
- --no-validate's flag Desc only mentioned data_config; it silently covers the
  --position check too. Said so, in both commands.
- Four places stated unverified backend behaviour as fact — including a claim
  that the server zeroes missing coordinates, which was the guard's entire
  premise, and a "backend defaults to digital" line 23 lines above a blockquote
  saying that very fallback was unverified. All reworded to what is actually
  known; the guard's rationale is now stated in terms of the request we send.
- E2E dry-run assertions were whole-output substring matches (`"w": 6` could
  match anywhere); switched to clie2e.DryRunGet path assertions like the
  sibling suites, which also lets them prove position is a top-level sibling
  rather than nested in data_config.
- Documented that formatName is case-sensitive, unlike rollup which is
  normalized — same object, two conventions, worth saying out loud.
- The --position canonical rewrite's comment claimed it kept Validate/DryRun/
  Execute consistent; they re-parse anyway. Its real job is folding @file input
  inline so the two paths cannot read a changed file. Comment now says that.
- Named buildDashboardBlockBody's bool at the call sites; covered all three
  branches of the identifier skip, not just block_id.

* fix(base): stop dry-run previews leaking route templates; finish the unverified-claim sweep

Round-4 review follow-up. Both findings trace back to earlier follow-up commits
rather than the original feature, and both are the same failure shape: fixing
the instance instead of the class.

- 68bdaccd made dryRunDashboardBase skip empty identifiers, but Set() doubles as
  the substitution source for :param placeholders in the URL. Skipping a
  declared-but-empty identifier therefore printed the raw route template —
  `.../blocks/:block_id` — while also removing `"block_id": ""`, the one signal
  that told the caller their argument was empty. An agent whose `$BLOCK_ID` did
  not expand would see a preview that looks like the CLI failed to substitute,
  with nothing pointing at the real cause. The condition is now whether the
  command declares the flag, which is what the comment claimed all along: create
  genuinely has no block-id, and that is the case worth omitting.
  Not fixed here: a declared-but-empty required identifier still reaches the
  wire as a request to the collection endpoint (`baseV3Path` drops empty
  segments). That predates this PR and spans the whole base package — worth its
  own change rather than guarding two commands and leaving nine inconsistent.
- The isomorphism test only compared bodies, so a preview could target a
  different endpoint than Execute and still pass. It now compares method and URL
  as well, and rejects any leftover ":" placeholder — that is the mechanism that
  would have caught the above.
- 82f72540's message claimed all four unverified backend statements had been
  reworded; five survived, three of them in `--help`, where the --position Desc
  said server-side acceptance was unverified two lines above a Tip asserting
  overlaps are not server-checked. All five now match the wording already used
  in lark-base-dashboard.md, and the PR body Summary no longer contradicts its
  own Known limitations.

The rejected-alternative for the first item: guarding empty required identifiers
in Validate would be the root-cause fix, but applying it to the two commands
this PR owns while nine sibling dashboard commands keep the old behaviour trades
one inconsistency for another.

* fix(base): stabilize dashboard block validation inputs

* docs(base): clarify precise dashboard layout workflow

* docs(base): align dashboard live coverage status

* docs(base): soften absolute dashboard layout phrasing in skill

Replace "run exactly once / stop" wording for +dashboard-arrange and
--position with intent-based guidance (prefer whole-dashboard arrange,
generally no need to re-read position) so the skill routes agents away
from per-block churn and useless retries without forbidding legitimate
user-driven follow-up adjustments.

Co-authored-by: TRAE CLI <traecli@bytedance.com>

* docs(base): clarify dashboard layout guidance

* docs(skills): move dashboard layout guidance to reference

* docs(base): verify dashboard number format defaults

---------

Co-authored-by: wanglei.75 <wanglei.75@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
2026-08-20 19:26:27 +08:00
chenxingyang1019 ca35f60616 fix(apps): make cache-clear ask first, and make apps failures classifiable (#2415)
* docs(skills): require explicit confirmation before apps +cache-clear

Asked to clear an app's online cache, an agent read `Risk: high-risk-write` from
--help and then supplied `--yes` itself on the first call, wiping production
cache without ever hitting the confirmation gate.

The CLI gate is fine: no --yes -> exit 10 confirmation_required, and --dry-run ->
exit 0 without triggering it. The wording was not. It only forbade appending
`--yes` *after* an exit-10, and said "已明确授权可直接带 --yes" without defining
authorization — so "clear my cache" read as authorization.

- `+cache-clear` gets a CAUTION block: never self-supply `--yes` on the first
  call; without confirmation, either --dry-run or ask, then stop and wait. exit
  10 is not a signal to retry with --yes.
- Add a zero-ambiguity table separating a *request* to clear ("clear the online
  cache") from a *confirmation* ("我确认清 dev"), so blocking the accidental wipe
  does not also kill the cases that were already correct: an explicit
  confirmation still goes straight to `--yes`, and a request with no environment
  named still has to ask instead of picking one.
- Note that online needs a confirmation phrase even when named explicitly.

`+cache-delete` gains the response field an agent has to read
(`deleted_key_count`): 0 means the key never existed, not "deleted
successfully", plus the get -> delete -> get chain needed to prove a delete took
effect — a single miss afterwards cannot tell the two apart.

SKILL.md: add +cache-clear to 禁止预授权判定底线, the one list a pre-authorized
run cannot skip; a reference-level rule alone would be bypassed there. The
routing table is left alone — no other row annotates risk, including
+file-delete, +role-delete and +member-remove.

* fix(apps): stop attaching request-shaped hints to precondition failures

`+db-execute` against a tenant that never activated Miaoda returns code 221800
"miaoda UAT not activated" with the hint "verify table/column names with
`+db-table-get` ... target the dev database with --environment dev". Neither step
can help: the failure is tenant-level, so a caller following the hint loops over
table lookups and env retries that fail identically.

Two causes. 221800 was unregistered, so it degraded to api/unknown — nothing in
the envelope distinguished "your tenant is not activated, stop" from "your SQL
was wrong, fix it and retry". And withAppsHint filled the caller's hint whenever
the server sent none, without looking at what failed: the hints are
command-scoped ("verify --app-id", "verify table/column names", "list releases"),
so every one of them describes the request, and the request is exactly what
failed_precondition says was fine.

Register 221800 as validation/failed_precondition (same shape as 400002465 "app
has no database yet") and gate the hint fallback on the subtype.

Blast radius is two codes, since that is all the subtype covers here:
  - 221800 — now withheld; message and code still carry the meaning.
  - 400002655 "no running container" — only when it reaches a non-observability
    command; the observability pair rewrites it first, and "verify --app-id" was
    never the fix for an undeployed app.
400002465 / 500002759 are intercepted by the isAppNoDatabaseError branch above
the gate, and 400002479 is served by withDBSyncHint, which does not delegate
here. The other 78 call sites take the original path for every input.

Gate on the one subtype, not on Category: this package asserts on purpose that an
authentication failure on +role-list (99991663) keeps the app-access hint and a
503 on credential issuance keeps the developer-access hint. Those hints are broad
enough to survive a caller-standing failure; only the precondition class is
misdescribed by construction. A test pins that, so widening the gate to Category
fails loudly instead of silently dropping those hints.

The gate is asserted on the real classification path (BuildAPIError -> the code
table -> withAppsHint), not only on a hand-built Problem. Constructing
SubtypeFailedPrecondition directly feeds the gate the input it wants and passes
whether or not 221800 is registered, so the registration itself has to be part of
what the test covers.

No recovery hint for 221800 — the activation path is a product procedure, and
guessing one is what made this failure misleading in the first place.

* fix(apps): classify file-storage and app-level failures

Five Spark business codes reached the CLI unregistered, so every one of them came
out as api/unknown with exit 1: a caller could not tell "your app id is wrong"
from "you lack permission" from "the upstream is having a bad minute", and the
exit code offered no way to branch either.

  400002484  app not found            -> validation/invalid_argument     exit 2
  400002467  no admin/developer perm  -> authorization/permission_denied exit 3
  500002761  ditto, pre-4xx renumber  -> same
  400000034  file not found/no access -> api/not_found                   exit 1
  500000034  ditto, pre-4xx renumber  -> same

400002467 is not file-specific: db commands (+db-table-list, +db-table-get,
+db-quota-get, +db-changelog-list) return it for an app the caller cannot access,
so registering it fixes both domains at once.

400002484 covers a well-formed id that does not exist AND a malformed one
("notanappid", "app_1" return it too), so the argument itself is the failure ->
invalid_argument, whose exit 2 separates "you passed the wrong id" from an
upstream fault. Environments that have not picked it up answer with 400002465
instead, conflating it with "app has no database yet"; the CLI cannot tell those
apart on the old code, so nothing here keys on that.

Both the current and the pre-4xx number are registered for each file failure.
The domain is moving its client-class errors from the 5xxxxxxxx band into
4xxxxxxxx, rolled out per environment, so both are live at once and dropping the
old one would silently return the un-migrated half to api/unknown — the same trap
that made the no-database recovery flow disappear when the server renumbered it
(see appNoDatabaseCode). The new number is not derivable from the old either:
500002761 became 400002467, tail digits included.

No hints added: permission_denied already has framework recovery wording, and a
domain-specific one would have to invent a remedy.
2026-08-20 17:36:08 +08:00
zhangjun-bytedance 755daa4de3 feat: add minutes transcript degradation logic (#2404) 2026-08-19 17:25:04 +08:00
guokexin.02 de45ec61e6 fix: honor recovered E2E retries (#2400) 2026-08-19 15:31:05 +08:00
lark-cli-external-pr-digest[bot] 2829ecd188 chore: release v1.0.88 (#2394) v1.0.88 2026-08-18 21:44:31 +08:00
guokexin.02 e0867e6ebb feat: support separate and suite skill layouts (#2211) 2026-08-18 20:59:31 +08:00
陈家名 679ebd5289 fix(api): reject query strings and fragments in paths (#2375) 2026-08-18 16:49:00 +08:00
huarenmin13 9b231d9825 fix(base): improve record history output and validation (#2298)
* fix(base): make record history queries explicit and readable

1. Require an explicit confirmed record ID and document deterministic record resolution
2. Add shared-format pretty history output and projection aliases without changing default JSON
3. Reject explicitly non-positive history cursors and cover request, help, formatting,
    and dry-run behavior

```ai-signature
改动范围: 将 Codebase MR 1438 的最终十文件差异移植到 GitHub PR 2298,覆盖 record history 命令、record list 投影别名、Base 引导文档与对应单元及 dry-run 回归测试
思考过程: 以 GitHub 最新 main 为基线执行三方内容合并,保留 GitHub 独立演进;删除原 PR 针对 base_history_003 的 Skill 改动,并仅按 GitHub 当前分母重算 coverage 指标
改动原因: GitHub PR 原先承载的是已确认应撤回的单题文档方案,需要由已评审的 MR 1438 最终通用实现完整替换,同时避免复制 Codebase 的多轮提交与 revert 历史
Break Change: 行为 breaking | record history 要求显式确认的 record ID,且显式非正 --max-version 现在返回 typed validation error
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: 5bf66267670f435c7d577d2444b61b209322eb5a73f15e47e671a5b8dd2603bf

* fix(base): validate history cursors and NDJSON dry runs

1. Reject missing or invalid next_max_version values before emitting pagination guidance
2. Use the execution page size in NDJSON dry runs and assert the requested output path
3. Cover valid and invalid history cursors and prove a 2000-row request is capped to a 500-row first
    page

```ai-signature
改动范围: shortcuts/base/record_history_list.go、record_ops.go 及邻近单元和 dry-run E2E 测试,仅处理 PR 2298 中有效 CodeRabbit 评论,不新增 live E2E 或修改主 Skill
思考过程: 分页提示只接受解码后的正整数游标;NDJSON dry-run 复用执行阶段的五百行页大小,并以两千行请求验证真实截断而非相等值偶然通过,同时直接断言导出路径契约
改动原因: 原实现可能把不可用的服务端游标打印成命令,同时 dry-run 对 201 到 500 条请求报告的首屏大小与真实执行不一致,初版测试也未真正证明五百行上限或 search 输出路径
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: a362c47d739d6de5676b87431fbc015d1eaa3d47d46d711e2535baa4dad7ffce

* docs(base): simplify record history prerequisites

1. Condense the record history prerequisites into generic record selection rules.
2. Remove the positive and negative examples from the reference.

```ai-signature
改动范围: 仅修改 skills/lark-base/references/lark-base-record-history-list.md,精简使用前置并删除正反例章节。
思考过程: 保留调用前确认同表 record_id、不得自行选择记录或扩展整表扫描的核心约束,移除具体链接、视图位置和命令示例以提升通用性。
改动原因: 用户要求使用前置更精简、表述更通用,并删除正反例;本次不涉及命令行为或测试。
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: 7e69951d47c1535cd5fe0fa9ebd7018af2e17373e5851c229dac46d68eaefa84

* fix(base): align history guidance with affordance

* fix(base): keep history guidance in the existing reference

1. Remove the new Base affordance file and its Markdown-only tests
2. Keep selection and field quoting guidance in the existing record history reference
3. Restore the pre-existing command tips and retain only behavior-focused regressions

```ai-signature
改动范围: 删除新增 affordance/base.md 与对应 source/help 文案测试,调整 record-history 现有 reference、覆盖说明和原命令 Tips
思考过程: 用户要求不新增 Base affordance 文件且 Markdown 不需要专门测试,因此保留运行时代码测试,把跨命令选行和字段引号说明收敛到已有 reference
改动原因: 新增 affordance 与文案测试扩大了 PR 改动面,并重复承载已有 history reference 的 agent 工作流说明
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: 4c7e3db10025f412d4b2cb4443d94724b5997b72b7e8f97fafdae2f08633b5a2

* docs(base): clarify record history target selection

1. Describe the record ID as uniquely resolving the user-selected target
2. Avoid implying that users must confirm an opaque internal record ID directly

```ai-signature
改动范围: 仅调整现有 lark-base record-history reference 的一处目标记录选择表述
思考过程: CLI 运行时只要求有效 record_id,agent 工作流要求用户确认目标而不是直接确认内部 ID,因此需要区分产品事实和操作约束
改动原因: 原表述可能被误解为 base-cli 能验证 record_id 的用户确认来源,与实际产品边界不一致
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: bd6c9584cc38689e1c3d9f26514425e3e38ffe2f2cff7b2bb0c65ff316aaf8dc

* docs(base): preserve serial history queries

---------

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
2026-08-18 12:06:02 +08:00
ethan-zhx 27ed082520 feat(slides): add kickoff reminder for empty presentations (#2367) 2026-08-18 11:58:50 +08:00
zhaojunlin0405 b6d04738e5 test(docs): align fetch help comment expectations (#2363)
* fix(docs): restore fetch comment help guidance

* test(docs): align fetch help comment expectations
2026-08-17 17:07:25 +08:00
leave330 327874c8f4 docs(lark-shared): split SKILL.md into a slim core plus references (#2226)
* docs(lark-shared): split SKILL.md into a slim core plus references

lark-shared is force-loaded by nearly every lark-* skill (26 skills, 190
references), so its body is paid on almost every task. Split the 211-line
monolith into a slim always-loaded core plus references/ that are read only
when relevant.

This is deliberately a move, not a rewrite. Existing wording is carried over
as-is wherever possible so the change stays easy to regression-test. The core
keeps the rules whose violation is silent or has no self-announcing trigger:
identity semantics, the ok == true success contract, non-blocking auth
split-flow, QR forwarding, write/delete intent confirmation, the exit-10
approval gate, path handling and the no-secrets rule. Mechanics, field paths
and step-by-step flows move to references/.

New references, all carrying over existing text:
- lark-shared-identity-and-permissions.md
- lark-shared-auth-split-flow.md
- lark-shared-high-risk-approval.md
- lark-shared-output-contract.md
- lark-shared-update-notice.md
- lark-shared-config-init.md

Wording changes are confined to lark-shared-high-risk-approval.md, where the
exit-10 guidance was generalized to match the implementation: the recovery
flag is read from hint rather than assumed to be --yes, and --dry-run is
suggested only where the target command supports it.

* docs(lark-shared): tighten the resident rule set

Regroup the always-loaded body so the safety rules read as one block: the
no-secrets rule leads, write/delete intent confirmation and --dry-run preview
follow, and the exit-10 approval gate and path handling close it out.

Trim wording that restated consequences already covered by the linked
reference, and align a few reference passages with the command surface
(`config init` without the flag, envelope phrasing).

* docs(lark-shared): make reference loading trigger-driven

Replace the loose "other scenarios" link list with an explicit trigger index:
each row names the conditions that oblige the agent to open that reference
before taking the next step, and the table covers all six references rather
than the three the list happened to mention.

Trigger conditions beat prose pointers here because the failure mode is an
agent that never opens the reference at all — a link it reads as optional is
a link it skips. Emphasis markers on individual rules are dropped in favour of
plain arrows, since the obligation now lives in the index.

* docs(lark-shared): describe the notice trigger by effect

Name what the three `_notice` keys mean for the agent rather than listing the
keys themselves, so the trigger reads as a condition to recognize instead of a
field spec to memorize.

* docs(lark-shared): phrase triggers the way tasks arrive

Restate the identity trigger in the terms an agent actually encounters ("who am
I", "get the fields of the current identity") rather than in diagnostic
vocabulary, and gloss high-risk-write so the row is readable without already
knowing the term.

Use MUST for the read obligation, matching how the rest of the skill set marks
non-negotiable steps.

* docs(lark-shared): scope the credential-reuse ban to across flows

The split-flow steps require carrying device_code from the first turn into the
second, so a flat ban on caching it contradicted the procedure it accompanied.
Scope the ban to reuse across flows, which is the case that actually matters.

Also match the trigger table to the field name the error envelope carries
(missing_scopes), keeping it consistent with the reference it routes to.

* docs(lark-shared): restore the silent-empty warning and complete the scope flags

Bring back the note that a bot reading user resources returns an empty success
rather than an error. That failure is silent, so it belongs in the body: an
agent that never learns it reads the empty result as "the user has no data".

Also list --recommend alongside --scope and --domain as a way to specify the
authorization range, matching what auth login actually accepts.

* docs(lark-shared): fold the split-flow steps into identity-and-permissions

Auth split-flow and identity/permission recovery are consulted in the same
situation — an agent about to obtain or repair authorization — so a separate
file bought a second hop without buying separation. Merge the split-flow steps
into identity-and-permissions as its agent-initiated-auth section, and route
its trigger row there.

The non-blocking rule leaves the resident body with this merge; the trigger
index already forces the read before any auth login is initiated, which is the
moment the rule matters.

Also reword the _notice.skills gloss to "out of sync" — the mismatch is
bidirectional, not only the skills lagging the CLI.

* docs(lark-shared): make the identity-continuity kernel resident

Omitting --as does not keep the current identity — it hands the choice back
to the profile default, and nothing errors when that happens. That silent
switch is exactly the class of rule the resident body exists for, so state
the kernel in rule 2; the mechanics and examples stay in the
identity-and-permissions reference brought in from main.

* docs(lark-shared): show full reference filenames in link text

Weaker models transcribe the visible link text when deciding what to read,
and short aliases made them reconstruct the real filename — a step that
misspells. Display the exact filename everywhere so copying the visible text
yields a resolvable path.

* docs(lark-shared): route --as selection questions to the identity reference

The resident body states that identity is workflow state but keeps the
selection mechanics in the reference, so an agent asking "which --as here"
had no trigger word to match. Name it in the identity row.

* docs(lark-shared): distill identity continuity, drop the ACL table

State identity continuity as its decision rule — omitting --as hands the
choice to the CLI (whoami shows the outcome and why), so spell --as out
whenever one identity must persist — instead of restating the resolution
chain, whose details belong to the implementation. Route the new trigger
word to the reference.

Drop the missing-scope vs resource-ACL recovery table: resource-ACL
recovery is domain-specific, not a cross-cutting rule, so it does not
belong in the shared skill. Update the lark-minutes pointers that named
the removed table.

* docs(lark-vc): update the last pointer to the removed recovery table

Same companion fix as the lark-minutes references: the recording error
table pointed readers at the recovery table that left lark-shared, so name
the surviving permission-management section instead.
2026-08-17 11:36:31 +08:00
SunPeiYang996 525a98270f feat(docs): support comments and block mutation ranges (#2341)
* feat(docs): support comments and block mutation ranges

* fix(docs): address CI and review feedback
2026-08-15 00:58:51 +08:00
tianyouskrrr 0c5530dc63 feat(slides): auto-upload @path images in +update-slide (#2346)
Bring +update-slide in line with +create and +add-slide: <img src="@local">
placeholders in --content are now extracted, validated, uploaded to the target
presentation, and rewritten to file_token before the page is replaced. This
removes the manual +media-upload round-trip that was tripping agents into
passing unresolved local paths to the backend.

- Validate rejects missing files and directory placeholders locally, before any
  API call, and gates docs:document.media:upload as a conditional scope.
- Execute uploads once per unique path (deduped) and, on partial failure,
  appends a progress hint so a retry does not silently re-upload every image.
- DryRun plans the upload steps ahead of the replace and reports
  images_to_upload so the irreversible half is visible up front.
- Skill reference documents the placeholder pipeline, CWD resolution, the
  docs:document.media:upload scope on 1061004/403, and images_uploaded output.

The command Description and skill intro stay scoped to WHAT the command does;
the @path capability is surfaced through the flag help, Tips, and the reference
section rather than restated in the one-line Description or a cross-command note.
2026-08-14 17:59:54 +08:00
chendaxin-tk 5a72b989c1 feat(sheets): accept the --range / --cells / border shapes callers actually send (#2338)
* feat(sheets): read a sheet prefix in --range as the sheet selector

Eval traces: 707 calls to +cells-get / +csv-get / +csv-put / +cells-set /
+cells-clear died on "specify at least one of --sheet-id or --sheet-name",
and 53% of them had already named the sheet inside --range
("Sheet1!A1:D20"). The sheet was known, only the flag was missing — so the
prefix now fills the selector and the bare A1 range goes to the tool.
Wired on both paths: a PreRunE stage in the sheets ergonomics layer for
standalone commands, and the sub-op translator for +batch-update.

The grammar follows the front-end ref lexer (byted-sheet TractorLexer):
the full-width ! is an equal separator, an unquoted name can contain
neither width (so splitting on the first one is safe), and a quoted name
keeps its doubled-quote escape and may itself contain a "!". Unquoted
names with spaces are accepted here though the lexer rejects them — a
--range flag has none of a formula's tokenizing ambiguity.
sheetNameFromA1 delegates to the same splitter instead of carrying a
second, looser grammar.

Scope guards: an explicit --sheet-id / --sheet-name stays authoritative
and --range passes through untouched, so a disagreeing prefix cannot
silently retarget a write; only --range carries the rewrite, since
+range-copy / +range-move / +range-fill name their destination sheet with
--target-sheet-id.

* fix(sheets): 边框粗细词兜底补齐 hair 与数字线宽

07-28 只修了 border_styles.<side>.style 里的 thin/medium/thick,同族的另外两种
写法仍在报错。对 596 条 trace 做频次统计,边框取值的错法就这几种:

  weight 槽 "hair"   476 次 / 19 个用例   ← 本次新增
  style  槽 "thin"  1795 次 / 39 个用例   (07-28 已修)
  style  槽 "hair"    76 次 /  2 个用例   ← 本次新增
  weight 槽 数字        10 次 /  2 个用例   ← 本次新增(07-28 报告 Case 2)
  width  键(GSheets) 35 次 /  3 个用例   ← 本次新增

根因是契约把一个视觉概念拆成 style(线型)× weight(粗细)两个字段,而 openpyxl
把两者塞进一个词 Side(border_style="thin"),于是同几个粗细词在两个槽位都会出现。
borderWeightWord 一个函数同时服务两个槽位,挂在 expandBorderAllShorthand 这个唯一
漏斗上,四条载体路径(--border-styles / --cells 内联 / --styles 载荷 /
+workbook-create)一起生效。

weight 先于 style 归一是有意的:{"style":"thin","weight":"1"} 只有等 "1" 先变成
"thin",style 那步才看得出显式 weight 与词义一致而非冲突。显式冲突
(thin + thick)保持报错,不替用户选。

刻意不收:openpyxl 完整线型表(dashDot / mediumDashed / slantDashDot)、VBA
xlContinuous、CSS hidden、Google Sheets SOLID_THICK、line_style / thickness 等
键别名、style 与 weight 装反、px/pt 后缀 —— trace 里全是 0 次;solid_thin、
border_width、border_color 各只有 1 个用例。它们继续走 enum 报错(报错带允许值
和 did-you-mean,一轮能改对),符合本文件顶部的静默别名准入门槛:真实词汇 **且**
跨批次/≥3 任务复现。新增用例里有一条反向断言把这条线钉住。

TestCellsSetStyle_BorderWeightNumberNamesEnum 的探针从数字换成布尔——数字现在会被
归一化,不再走报错路径,enum-over-skeleton 那条文案规则改用布尔来钉。

* feat(sheets): accept the openpyxl-habit --cells shapes and prescribe the rest

The --cells shape family is the single largest client-side rejection cluster
for +cells-set in the eval corpus. Traced against 14,024 real calls it splits
into two habits, and each gets the treatment its ambiguity allows.

Accepted outright, both unambiguous, both on the existing jsonFlagNormalizers
seam (so --writes items and +batch-update sub-ops get them too):

  - {"cells": […]} envelope — an agent generating the payload in a script
    writes json.dump({"cells": cells}, f), mistaking the flag name for a JSON
    key. 11 of 21 traced `expected type "array", got "object"` rejections are
    this exact shape. Only a lone "cells" key unwraps; siblings mean the
    object is the whole tool input and dropping them would write elsewhere.
  - bare scalars in cell slots — the openpyxl / gspread habit of passing a
    plain values matrix, which real rows mix with cell objects as soon as a
    formula appears (["1","电动大门",10331.00,{"formula":"=D2*E2"}]).

  null is deliberately left failing: {} (leave the cell alone) and
  {"value":""} (write an empty string) are both plausible readings, and the
  normalizer only rewrites what is beyond doubt.

Renamed silently on the same grounds: --values is what gspread calls the
payload, and what this CLI's own +workbook-create calls its untyped 2D data.
Because bare scalars now lift into {"value":…}, the plain matrix a --values
caller passes ('[["工作内容"]]') is already accepted verbatim under --cells —
the name was the only thing wrong, which puts it in commandFlagAliases rather
than the prescription table. That drops the round trip a prescription costs
(eval F8: 170 hits, 1.9% of failures) and covers the +batch-update sub-op
path, which reads the same alias table and would otherwise get no hint at all
(a prescription only rides on cobra's unknown-flag branch).

Inferred, matching the libraries these callers arrive from: a bare
single-cell --range is now an anchor, sized from the payload — the same
inference +csv-put already does for --start-cell. The range resolves locally
and ships in full, so the server still gets the strict match it enforces. An
explicit extent ("A1:A1", "A1:C10") is never inferred over.

Prescribed, because it cannot be guessed safely: the cells-vs-range mismatch
(132 rejections across 93 case-runs) now reports both axes at once and hands
back the range that fits the payload, plus the inclusive-end note that
explains its biggest sub-bucket — A1:C10 being 10 rows. Growing the range
would overwrite rows the caller never mentioned and shrinking it would drop
data, so the choice stays with the caller. Ragged rows get their own message
instead of being reported as a range mismatch.

Supporting refactor: parseCellRange replaces the prefix-strip / split-on-":"
/ splitCellRef triplication (rangeDimensions becomes a thin wrapper, its
error wording kept byte-for-byte since +styles-put surfaces it verbatim), and
cellsExtent is the one authority on whether a payload is rectangular, so the
anchor expansion and the dimension check cannot disagree. Two bugs fell out
of the new tests: a leading space before the sheet name survived into every
rendered range, and a payload of empty rows would have rendered a malformed
suggestion.

* fix(sheets): parse the sheet part of a range with the ref lexer's grammar

parseCellRange cut the sheet off with strings.Index(range, "!"), which
disagrees with the grammar splitRangeSheetPrefix already implements from the
front-end ref lexer (byted-sheet TractorLexer.ts). Two spellings the lexer
treats as ordinary therefore failed to parse at all:

  --range '甘特图!B3'        full-width separator (ExclamationMark accepts it)
  --range "'Q1!Actual'!B3"   quoted name owning a "!" (quotes delimit, so it may)

An unparsable range is deliberately deferred ("the range validator's job"),
so the failure was silent in both directions: the anchor never expanded and
the dimension mismatch never got its prescription. Reachable whenever the
prefix survives to the shortcut — an explicit --sheet-id/--sheet-name keeps
it (the selector rewrite only fires when the pair is empty), as do
--source-range / --target-range, which that rewrite deliberately skips.

The grammar now lives in one place. scanSheetQualifier reports the parsed
sheet name AND the byte offset just past the separator; splitRangeSheetPrefix
is rewritten on top of it (all 20 of its grammar cases unchanged), and
parseCellRange slices the qualifier off at that offset. The offset is the
point: a range rendered from a parse is both shipped to the server and
printed for the caller to paste back, so the qualifier has to survive
verbatim — quotes, full-width separator and all — which a name parsed and
re-quoted could not promise.

Naming, while here: cellRange.prefix said where the field sits, not what it
holds. It is now sheetQualifier (verbatim, separator included) alongside
sheetName (parsed, unquoted) — the sheet a range names is what the type is
about, and the next caller that needs it should not reach for the raw string.

* fix(sheets): close the four gaps the PR review found

Anchor expansion no longer sizes a sheet-qualified range. Such a range only
reaches expandAnchorRange beside an explicit --sheet-id / --sheet-name, since
all three entry points fold the prefix into the selector when none was given —
so the prefix is one that disagrees with the selector, and sizing it shipped
{"range":"Sheet1!A1:B2","sheet_name":"Other"} where the pre-anchor CLI had
failed locally with the cells-vs-range mismatch. Trading a local prescription
for a wire payload whose two halves name different sheets is the wrong
direction; a qualified anchor stays a mismatch.

--writes items now really do get the payload rewrites. cellsSetWritesOps gives
each item the standalone pipeline through a per-item flag view, but that runs
after requireJSONArray has validated the array, so an item spelling its payload
"values" or wrapping it in a {"cells": …} envelope died on the array schema
while the identical +batch-update sub-op was accepted. The rewrites move onto
the jsonFlagNormalizers seam for --writes, one step ahead of the schema, so the
two spellings of the same write agree. values → cells only when "cells" is
absent: two spellings with different payloads stays normalizeSubOpInputKeys'
conflict to report.

The derived selector is left as the only spelling of itself.
normalizeSubOpInputKeys keeps a duplicate key whose two values agree rather
than erroring, and two empty strings agree — so an input carrying both
"sheet-name":"" and "sheet_name":"" kept the hyphen form, which lookupRaw finds
first and which then shadowed the sheet_name just derived from the range
prefix, failing as "specify at least one of --sheet-id or --sheet-name".

Test coverage the review asked for: a +batch-update dry-run case for the prefix
rewrite (the sub-op path had unit coverage but no E2E), and the two tests that
grepped a rendered envelope now decode the dry-run body and assert the fields
that reach the wire.

* test(sheets): cover the accepted input shapes against a real spreadsheet

The dry-run E2E pins what the CLI builds; nothing pinned that the backend
takes it. That gap matters more for rewrites than for ordinary flags: each one
turns a caller spelling into a wire payload the caller never sees, so a payload
the server rejects would be a worse outcome than the client-side error it
replaced.

TestSheets_CallCompatWorkflow writes through a sheet-qualified --range with no
selector flag at all, with bare scalars in the cell slots and a bare A1 acting
as an anchor — three rewrites composed in one call — then reads back through
the same prefix and stamps an openpyxl "hair" border over the result. The sheet
is named with a space in it so the prefix takes its quoted form, the spelling
the ref-lexer grammar exists for and the one a first-ASCII-"!" split would cut
in half.

The read-back compares values collected out of the decoded payload rather than
a fixed path: get_cell_ranges' response nesting is the backend's to change and
is pinned nowhere in this repo, while the values having survived the round trip
is the actual claim. The number is compared numerically for the same reason.

Self-contained: it builds its own workbook, and createSpreadsheet's cleanup
tears it down. Skips without tenant credentials, so local runs are unaffected
and CI's e2e-live job is what exercises it.

* feat(sheets): answer +sheet-list instead of failing the guess

Callers reach for +sheet-list on their own: the sheets surface has a whole
+sheet-* family (+sheet-create / +sheet-copy / +sheet-delete / +sheet-info),
so "list the sheets" spells itself that way. The miss does not self-correct
either, because internal/suggest ranks shared prefixes first: the "did you
mean" hint points at +sheet-create and its siblings, never at +workbook-info.

Add it as a read-only projection over get_workbook_structure emitting the bare
sheets array, entry-for-entry identical to what +workbook-info nests under
sheets. Hidden from `sheets --help` here, and from the lark-sheets skill docs
via sheet-skill-spec's doc_hidden_shortcuts, so neither surface offers a second
name for what +workbook-info already does; the command only ever answers a
caller who typed it anyway.

data/flag-defs.json and flag_defs_gen.go carry the new shortcut's flag entry,
sourced from sheet-skill-spec's spec-tables.

* feat(sheets): prescribe the real command for invented subcommand names

Callers reach for subcommand names this CLI does not have, borrowed from
neighbouring ecosystems. The framework answers an unknown name by edit distance
over the group's children; that ranking is prefix-weighted, so it cannot settle
a name whose answer shares no prefix with it, or one whose same-prefix siblings
crowd the answer out. Those names now get a curated prescription instead: the
command they meant plus its exact retry form, so the next attempt needs no
--help round trip.

Prescribed, never rewritten. Unlike a flag, silently resolving a subcommand
would run a write the caller never named, and the same information fits in the
error the failed call already returns. Every entry is a naming miss rather than
a missing capability — each intent already has a command — and a rare spelling
stays with the ranker rather than growing the table.

The hook is the group's Args validator, which cobra runs before the group's
RunE. That ordering is what keeps this inside sheets: the framework's
unknown-subcommand guard installs on RunE and never touches Args, so the two
compose and every unclaimed name still reaches the ranked "did you mean one
of: …" unchanged. The message stays byte-identical to the guard's, since the
name genuinely does not exist; only the hint and the machine-readable
suggestion change.

Targets resolve against the live tree rather than the table. All of them are
write commands, so a concealed distribution or a user policy of max_risk: read
replaces one with a hidden deny stub; prescribing it then would name a command
that can only answer command_unavailable, and that the ranker has already
stopped suggesting. The check mirrors the ranker's filter, and doubles as a
runtime backstop when a target vanishes in a rename.

Known gap: +batch-update validates sub-op shortcut names against its own
allow-list, so an invented name inside --operations still gets the generic
"not allowed" dump instead of the prescription.

Tests pin the two invariants that make the table safe to extend — a target must
exist, and a key must not shadow a real command (checked against backward's
aliases too, which mount on the same group) — plus the registration itself, so
deleting the wiring fails the suite instead of silently reverting the CLI to
generic suggestions.

* fix(sheets): keep a non-finite line width off the thickness mapping

strconv.ParseFloat answers yes to "Inf" / "Infinity" / "NaN", so a quoted
non-finite weight entered the numeric-width branch and came back out as
"thick" with exit 0 — the CLI guessing at input that means nothing. NaN
only escaped that by accident (every comparison against it is false).
borderLineWidth now reports a non-finite result as "not a width", which
puts both back on the enum error path that names thin / medium / thick.

Also closes the review's test-coverage gaps: hair in the style slot pins
the canonical style ("solid") next to the weight in both the corpus and
the dry-run e2e, the numeric-width table gains its two ends (3 is where
thick starts, 0 keeps its own type error), and splitRangeSheetPrefix
covers the backslash-escaped separator after a quoted name.

* fix(sheets): parse --ranges prefixes with the shared grammar, budget every cells shape

Three gaps the review found, each reproduced against a built binary first.

--ranges kept its own strings.Index("!") splitter, so the four separator
spellings the rest of the PR unified on stopped at the flag boundary:
"工作表1!A1:B2" was rejected as carrying no sheet prefix at all, and
"'My Sheet'!A1:B2" shipped sheet_name "'My Sheet'" — quotes included — for
the backend to fail on as sheet-not-found. Both the up-front prefix check
and splitSheetPrefixedRange now go through scanSheetQualifier /
splitRangeSheetPrefix, which keeps the two error messages' division of
labour: no qualifier at all is "must include a sheet prefix", an empty
side is "must use sheet!range form".

estimatedBatchOpCells ran before the translator's normalizers but read the
wire shape only, so a {"cells": …} envelope, a lone cell object, and a
payload spelled "values" each scored zero cells and materialized outside
the batch-wide safety budget. It unwraps the shape now — no mutation, the
per-cell rewrites stay the translator's and change no count.

sheetNameFromA1 lost "Sheet1!" when it moved onto splitRangeSheetPrefix,
which requires a non-empty range; a prefix with no range still names a
sheet, and pivotPlacementWarn is more use naming it than falling back to
the generic wording. It reads the qualifier directly instead.

* fix(sheets): make the +cells-put prescription validate, and type the range assertions

The +cells-put hint replaces the ranked candidate list, so it is the whole
of what a caller gets back — and it prescribed a 1×2 matrix against A1:B2,
which fails the cells-vs-range check the same call would hit, plus prose
forbidding the bare scalars this branch now accepts. It spells a matching
2×2 scalar matrix and both accepted cell forms instead.

TestPrescribedExamplesActuallyValidate pulls the flags back out of the hint
and runs them through +cells-set, so the prose cannot drift from what the
validator takes; restoring the old hint fails it with the very error the
caller would have seen.

splitSheetPrefixedRange's rejection cases asserted only that an error came
back, which an untyped one would satisfy. They now go through
requireValidation and pin the --range attribution and the offending input
in the message.
2026-08-14 17:43:30 +08:00
zgz2048 0a2037233e feat(base): clarify skill routing and references (#2347) 2026-08-14 15:30:47 +08:00
zgz2048 cb1bb1d004 docs(base): restructure skill routing and analysis guidance (#2320)
* docs(base): add common filter condition examples

* docs(base): restructure skill routing and guidance

* docs(base): simplify identity selection guidance

* docs(base): restore concise recovery contracts

* docs(base): condense recovery guidance

* docs(base): retain only high-value recovery guidance

* docs(base): clarify full field update semantics

* docs(base): prioritize common text filter examples

* docs(base): deduplicate auto number update guidance

* fix(base): align ndjson dry-run page size

* docs(base): align permission identity guidance

* test(base): align ndjson dry-run page size

* docs(base): recommend dynamic select option reuse

* docs(base): require SOP for record reads

* docs(base): streamline record read guidance

* docs(base): clarify record format guidance

* docs(base): reduce hidden record flag exposure

* docs(base): merge record read gate

* docs(base): reduce table discovery guidance

* docs(base): centralize block discovery guidance

* docs(base): restore table analysis chain

* docs(base): restore table resource guidance

* docs(base): use resource-specific list commands

* docs(base): restore folder listing command

* docs(base): minimize probe stdout
2026-08-14 13:35:44 +08:00
zhangjun-bytedance a8552d7f44 feat: fix doc skill about identity coutinue (#2334) 2026-08-13 21:01:21 +08:00
lark-cli-external-pr-digest[bot] d3a96f537c chore: release v1.0.87 (#2337) v1.0.87 2026-08-13 19:37:40 +08:00
calendar-assistant b75632e78a docs(calendar): clarify +create-first strategy for advanced fields (#2336)
Reframe the calendar +create reference's "advanced usage" section around a
+create-first strategy: prefer +create, then patch unsupported advanced
fields (location, visibility, custom reminders/attendee_ability/
free_busy_status, optional attendance, all-day) via the raw API update
instead of hand-building the whole event from scratch.

Drop the two raw-create default-value notes (auto-filled attendee_ability/
free_busy_status/vchat.vc_type/reminders and self-attendee injection) since
the +create-first strategy makes building create payloads by hand a
non-goal.
2026-08-13 18:55:35 +08:00
xiaomi-bytedance 723f884e9b feat(base): support BaseApp application mode (#2231)
* feat(base): add BaseApp workspace, page and block shortcuts

Implement the CLI layer of the BaseApp CLI/OpenAPI protocol design: 17 new
shortcuts covering workspace entities, blank app creation, page CRUD and page
block CRUD, plus skill references and dry-run E2E for each.

The data_config validator moves to a neutral block_data_config.go with chart
logic unchanged; list and richText dispatch are new branches, so dashboard
behaviour is untouched. Command spaces stay separate — dashboard commands never
take --app-token and app block commands never take --dashboard-id. The one
exception is +app-block-get-data, which shares the dashboard endpoint, execute
and dry-run hooks and therefore takes --base-token instead of --app-token.

This phase ships no +app-block-delete and no page arrange command; both the
help text and the skill docs spell out that a block type cannot be changed
after creation.

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

* feat(base): implement app mode shortcuts and list components

* feat: support deleting BaseApp via drive delete

* fix: 修复 workspace scope

* fix: correct BaseApp permission scopes

* feat: 新增 moveIn workspace 逻辑

* feat(base): support multi-datasource data_config for BaseApp charts

BaseApp page charts follow section 8 图表协议 of the App CLI RPC 协议,
which differs from dashboard charts by supporting multiple data sources:
base_token is a single top-level value shared by every source, while
table_name/series/count_all/group_by/filter move into each data_sources[]
element (plus top-level data_source_mode and sort). The per-source value
semantics are identical to dashboard charts, so each data_sources[] element
reuses normalizeDataConfig / validateChartDataConfig; the wrapper only adds
the top-level structure. Dashboard charts keep the flat shape; the list
protocol is untouched.

- block_data_config.go: add normalizeAppChartDataConfig /
  validateAppChartDataConfig / validateAppBlockDataConfig
- app_block_create/update: route chart blocks to the multi-datasource
  normalize/validate; refresh tips and examples
- reference doc: rewrite the chart section for the multi-datasource shape
- unit + e2e tests: migrate chart cases to data_sources; add a
  multi-datasource combo case

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

* fix(base): map BaseApp richText block type to the wire type "text"

The rich-text widget's API type is "text" (App CLI RPC 协议 §10), but the
CLI exposes the friendlier "richText" alias and was sending it verbatim, so
the backend rejected +app-block-create --type richText with "type is
invalid". Map richText -> text when building the request body; the
user-facing --type richText is unchanged. Add TestAppRichTextTypeMapsToText.

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

* docs(base): remove internal protocol doc link/reference from skills

The BaseApp skill references pointed at an internal Lark doc (deep link with
a private token) as the source of truth, which must not ship in this repo.
Drop the link and the doc name entirely from the reference markdown and from
code comments; describe behavior in neutral terms ("服务端协议 / 服务端返回和校验")
instead. No functional change.

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

* feat: create workspace for BaseApp when omitted

* fix(base): align BaseApp block protocol

* fix: 删掉废弃的 workspace-entity-remove

* fix(base): align app get reference response

* fix(base): align app shortcuts with API contract

* fix(base): refine app mode shortcut contracts

* fix: use entity_type for workspace entity filtering

* fix: return workspace URLs for base workspace ops

* fix(base): enforce unique app block names

* fix(base): use chart token for app block data

* 明确baseapp边界,不导向到dashboard-arrange

* docs(base): clarify app copy is unsupported

* docs(base): define unsupported app page operations

* fix(base): align app block text type with dashboard

AppMode 的文本组件此前对外叫 richText,发送时再映射成 wire 上的 text,
而读取方向没有反向映射:写进去用 richText、读回来是 text,同一个 CLI
表面自相矛盾,回填或幂等复建时会被枚举校验拒掉。

统一成 text,与 Dashboard 文本组件同名同义:
- appBlockTypes/isAppBlockType/textBlockTypes 去掉 richText
- 删除 appBlockBody 里的 richText → text 发送期映射
- help、枚举、示例、tips 与 baseapp block data_config reference 同步
- 新增回归测试,确保 richText 不再被接受也不再出现在枚举里

richText 不保留别名:+app-* 尚未随已发布版本对外,无存量调用方。

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

* feat(base): resolve BaseApp URLs

Migrate the net changes from bitable/base_cli!1308 onto the current BaseApp development branch.

* fix(base): remove app block list type filter

* docs: preserve explicit intent when reusing BaseApp blocks

* fix(base): route +app-block-get-data to base_apps endpoint

Move the shortcut off the dashboard route and onto the dedicated
BaseApp block-data endpoint:

- URL: /open-apis/base/v3/base_apps/:app_token/blocks/:block_id/data
- base_token is passed as a required query parameter per the new IDL
- Refresh --block-id description and tips to list all producers of the
  chart_token (create/list/get) and note the cht… prefix
- Update the dryrun test to expect the new URL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: clarify unsupported BaseApp copy paths

* fix: front-load BaseApp copy stop rule

* fix: surface unsupported PageGroup operations

* fix: preserve PageGroup support boundary

* docs(base): clarify unsupported app block handling

* docs(base): clarify how to read text block content

Text blocks have no /data endpoint; calling +app-block-get-data on
one returns a generic server 500. Point readers at +app-block-get,
whose data_config.text carries the Markdown source.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: clarify immutable BaseApp block types

* docs(base): correct chart date filter format

* docs(base): explain inaccessible app pages

* docs: require workspace removal lookup

* fix(base): return workspace move-in result faithfully

* chore(base): adapt BaseApp changes to upstream main

* fix(base): align app mode changes with upstream scope

* fix(base): address app mode review feedback

* test(base): assert app data transport error contract

* fix(base): satisfy app mode merge requirements

* refactor(base): align app mode filenames

* docs: fix BaseApp rename guidance

* fix(base): remove unsupported workspace icon

* docs(base): clarify app mode concepts and config reuse

---------

Co-authored-by: weibiao.x <weibiao.x@bytedance.com>
Co-authored-by: zhangbinkai.zbk <zhangbinkai.zbk@bytedance.com>
Co-authored-by: yurunjie <yurunjie.xx@bytedance.com>
Co-authored-by: Codex <codex@example.com>
2026-08-13 16:20:34 +08:00
calendar-assistant 9b845a5b8b feat(calendar): support setting VC meeting owner in +create (#2328)
Add --meeting-owner-id to the +create shortcut so bot (tenant-access-token)
callers can set the VC meeting owner via vchat.meeting_settings.owner_id
without dropping to the raw API. buildEventData now builds vchat as a nested
map, keeping the default vc_type: vc and injecting meeting_settings.owner_id
only when the flag is set; dry-run reflects it automatically.

Validate the flag with typed errs errors: reject dangerous chars, require a
non-bare ou_ user open_id, and enforce bot-only effectiveness (--as bot).

Cover the behavior with buildEventData unit tests plus command-boundary tests
(dry-run owner_id assertion and typed Validate errors for bare ou_, non-ou_
ids, and non-bot identity), and document the flag and the raw-API
default-value gap in the calendar create reference.
2026-08-13 15:28:57 +08:00
kiraWangRuilong ac6d7ce4f8 feat: propagate credential metadata (#2305) 2026-08-13 14:42:44 +08:00
liujinkun2025 6e2cad7221 fix(wiki): bound node retries (#2292) 2026-08-13 14:12:56 +08:00
chenxingtong-bytedance f98db395d8 docs(lark-im): support user and bot identities for resource uploads (#2319)
The lark-im SKILL.md API table marked images.create as bot-only, but user identity with im:resource scope can upload and send media fine. Corrected the identity annotation to support user and bot, added a missing files.create entry with the same annotation, added a media-upload identity note to the Identity and Token Mapping section, and added files.create to the scope table.
2026-08-12 20:28:14 +08:00
liujinkun2025 5190cfd60f fix(wiki): guide recovery from node structural limits (#2290) 2026-08-12 18:22:15 +08:00
ViperCai 52ab2ea1c3 fix(drive): guide wiki permission recovery (#2311) 2026-08-12 16:33:36 +08:00
木杉 1e87f67244 fix(apps): friendly-ize "Container not exists" for observability commands (#2302)
* fix(apps): friendly-ize "Container not exists" for observability commands

+metric-list and +analytics-list passed the upstream business code 400002655
("Container not exists") through verbatim. The message reads like an
infrastructure fault and misleads callers (including AI agents) into retrying a
non-retryable, expected business state: an app with no running container simply
has no metrics/analytics to query yet.

Rewrite it at a scoped observability helper (withObservabilityHint) into a
user-facing explanation plus a deploy-then-retry next step, mirroring the
existing isAppNoDatabaseError override. Detection is code-OR-message so a server
renumber alone does not silently drop the rewrite. Classification, code, and the
wrapped cause are preserved; unrelated failures still fall through to the shared
app-id recovery hint (and its own no-database override).

* test(apps): add execution-path regression tests for observability container hint

common_test.go proves withObservabilityHint in isolation but stays green if a
call site reverts to withAppsHint. Drive +metric-list and +analytics-list
Execute with a mocked 400002655 "Container not exists" envelope and assert the
container-specific message/hint/code, so a revert fails the build. Also closes
the two uncovered call-site lines flagged by coverage.

* fix(errclass): classify no-container code as validation/failed_precondition

Register 400002655 in sparkCodeMeta mirroring its no-database twin
(400002465) so both "expected precondition not met" business states expose
the same validation/failed_precondition classification to machine consumers,
instead of falling back to api/unknown. The shortcut-layer message rewrite
already keyed off the raw code, so this only aligns the typed envelope's
category/subtype; update the execution-path tests to pin the new
classification.

* fix(apps): gate the no-container hint's release behind user authorization

The no-container hint told a harness to deploy via +release-create, a "write"
that takes the whole app live and can affect existing production traffic —
without the user-confirmation gate its no-database twin deliberately carries.
Since the hint's audience is an AI agent that acts on it, a failed metrics read
could trigger an unconfirmed go-live. Lead with a read-only +release-list
status check and gate +release-create behind an explicit user confirmation,
mirroring appNoDatabaseHint.
2026-08-12 15:01:10 +08:00
木杉 56dba0fc08 fix(apps): reject non-HTML apps in +html-publish with actionable error (#2300)
* fix(apps): reject non-HTML apps in +html-publish with actionable error

+html-publish only supports html/modern_html apps, but the backend
rejected frontend/full_stack apps with an opaque business code
(400000059) partway through the publish, and a wrong --app-id surfaced
a bare "app not exist" code (400002577). Add an app_type whitelist
precheck at the top of runHTMLPublishTOS: it resolves the type via
queryAppType, rejects unsupported types with a failed-precondition error
that names the type and redirects to +release-create, and annotates a
lookup failure with the +list recovery hint. A defense-in-depth
translation covers the case where the type changes between precheck and
release-create.

* test(apps): cover release-create app_type fallback translation

Pin the defense-in-depth layer in runHTMLPublishTOS: when the app_type
precheck passes but release-create still returns 400000059 (type changed
mid-flight), the opaque business code is translated into a
+release-create hint instead of bubbling raw.

* test(apps): pin app_type precheck runs before packaging

Use a nonexistent --path in the full_stack/frontend rejection tests so
the assertion fails if the precheck is ever moved below the tarball
packaging step. A valid site only proved the precheck runs before the
network call, not before walkHTMLPublishCandidates.

* docs(apps): fix registerAppTypeStub comment on httpmock match direction

httpmock matches when the request URL contains stub.URL. The bare
/apps/{id} query cannot contain the longer pre_release/releases stub
URLs, so it only matches the app_type stub — the previous comment stated
the substring relationship backwards. Comment-only; no behavior change.
2026-08-12 14:46:13 +08:00
zgz2048 fd9703987b docs(base): add common filter condition examples (#2310) 2026-08-12 11:43:12 +08:00