Commit Graph

1111 Commits

Author SHA1 Message Date
lark-cli-external-pr-digest[bot] 177ef1c7cf chore: release v1.0.92 (#2553) 2026-08-31 10:57:31 +08:00
xuzhigang 3109a56671 feat(im): support rich-text message attachment zone in send/reply/mge… (#2515)
* feat(im): support rich-text message attachment zone in send/reply/mget/edit

Support the post message attachment zone (top-level files array) end to end:
- +messages-send / +messages-reply: repeatable --attachment file_key flags
  merged into the post content's files array (deduplicated).
- +messages-mget: render attachment-zone files/folders as <file>/<folder>
  tags in content, extract file keys for --download-resources.
- +messages-edit: new shortcut (PUT /open-apis/im/v1/messages/:id) with
  --set-attachments / --clear-attachments; body-only edits preserve the
  attachment zone by default.
- Attachment flags are mutually exclusive with --content carrying a files
  array (declare the zone via one or the other, not both).
- bot-only identity, matching server behavior (user token rejected).
- Fixes from review: attachments no longer bypass content mutual-exclusion
  validation (P1); merge dedups by key.
- Docs (SKILL.md, references, affordance) and unit tests updated.

* fix(im): address design-review findings (auto-infer post, dedup set, doc routing)

- --attachment/--set-attachments/--clear-attachments now infer msg_type=post
  automatically; only an explicit incompatible --msg-type conflicts.
  --text is rejected with attachments (text is a standalone message, not a
  post body) with a hint to use --markdown or --content.
- --set-attachments deduplicates repeated keys (docs promised this; the
  replace helper now enforces it).
- Shortcut Description no longer leaks the HTTP path or the raw server
  error phrase; it describes the command semantically.
- affordance/im.md +messages-edit now routes WHEN: interactive cards go to
  messages.patch, corrected messages go to +messages-send, and attachment
  tri-state tips are listed.
- mget doc no longer claims --format json exposes raw wire fields (the
  output is the rendered content); download eligibility clarified.
2026-08-31 10:57:31 +08:00
xiongyuanwen-byted ea088f4283 fix(sheets): suppress multipart stderr noise and tighten e2e boundary test (#2550)
Add a Quiet flag to DriveMediaMultipartUploadConfig so callers whose
success contract forbids non-empty stderr (the sheets shortcuts) can
suppress the chunk-plan and per-block progress lines the multipart
path writes unconditionally on success. Both uploadSheetImage and the
deprecated uploadSheetMediaFile pass Quiet: true.

Also simplify isLocallyOpenedOfficeToken to two HasPrefix calls
instead of a loop over an inline slice, so the two-prefix invariant
stays in one expression rather than drifting from common.

Finally, fix the e2e "at the ceiling stays single-part" test case:
the small.png fixture was 9 bytes, not the 20 MB the name implied.
Truncate it to singlePartCeiling so the boundary is actually pinned.
2026-08-31 10:57:31 +08:00
zhengzhijiej-tech cb02853f15 feat(sheets): add +cond-format-result-get and --include conditional_format (#2502)
* feat(sheets): add +cond-format-result-get shortcut and --conditional-format flag

- lark_sheet_read_data.go: add CondFormatResultGet shortcut with include_conditional_format_style hardcoded to true
- cellsGetInput(): add --conditional-format flag mapping
- shortcuts.go: register CondFormatResultGet alongside existing cond-format shortcuts
- lark_sheet_read_data_test.go: add dry-run test cases covering new shortcut and --conditional-format
- flag-defs.json / flag_defs_gen.go: sync from sheet-skill-spec

* fix(sheets): fold conditional format into include flag

* refactor(sheets): isolate conditional format result output

* fix(sheets): satisfy nested slice lint
2026-08-31 10:57:31 +08:00
xiongyuanwen-byted 3fcc4e337d fix(sheets): make image-upload previews match what Execute sends (#2537)
* fix(sheets): repair the build after the local-office detection move

main does not compile: shortcuts/sheets/lark_sheet_workbook.go references
isOfficeSpreadsheet and officePrefixes, which no longer exist in the package.

Neither PR was wrong on its own. #2531 (merged 10:39) moved local-office token
detection into common.IsLocalOfficeToken and deleted the sheets-local copies;
#2533 (merged 12:48) added errLocalOfficeExportUnsupported, which calls them.
#2533's branch predated the move, so its CI was green against a base that still
had the symbols, and merging it left main broken.

Repoints both references at the moved API. Behaviour is unchanged:
common.IsLocalOfficeToken is the same predicate #2531 moved, and the prefix pair
is the exported form of the same two constants. #2533's own coverage —
TestWorkbookExport_LocalOfficeTokenRejected across the local_office_ prefix, the
fake_office_ prefix, and an interleaved OFL0X token, plus the wiki-node and
dry-run cases — passes unchanged, which is what pins the equivalence.

* fix(sheets): derive dry-run image parent_type from the ref kind, not the token

A `/wiki/` URL reaches a DryRun hook as the wiki node_token: resolving it to the
backing spreadsheet needs the get_node call a preview must not make. Both
image-upload previews fed that node_token straight to sheetMediaParentType, so
the parent_type they showed was derived from a token that is not the one Execute
uploads against. A node_token shaped like an imported office token previewed
office_sheet_file for a spreadsheet that will upload as sheet_image.

sheetsDryRunParentType decides from the ref's kind instead. A wiki ref is native
by construction, not by default: resolveWikiNodeToSpreadsheetToken rejects any
node whose obj_type is not "sheet", and a spreadsheet backed by an imported
office file sits in drive as a "file" node, so it never survives that gate to
reach an upload. Execute is unaffected either way — it derives from the resolved
token. This mirrors slidesDryRunParentType, which the slides domain already
applies for the same reason.

The hooks now hold the parsed ref rather than re-deriving the token from it, so
the kind is visible where the preview is built.

Also records, at uploadSheetMediaFile, that office_sheet_file survives the
multipart path. Slides caps image uploads at 20 MB because upload_prepare
rejects its parent types outright, which raised the question for sheets, whose
deprecated +media-upload has no such cap. Verified against the live API:
upload_prepare accepts both sheet_image and office_sheet_file, and a 20.6 MB
file uploaded with office_sheet_file completes prepare -> 6 x upload_part ->
upload_finish and returns a file_token a float image then accepts.

Tests: sheetsDryRunParentType over both ref kinds, including wiki refs carrying
office-shaped and office-prefixed node tokens; dry-run coverage through the
public flags for +cells-set-image and +float-image-create, each checked against
the identical token as a /wiki/ URL and as a raw spreadsheet token so the two
rows differ only in kind; the same pair added to the e2e dry-run lane. All four
fail against the previous behaviour.

* fix(sheets): send oversized images through the chunked upload, not upload_all

uploadSheetImage always used the single-part endpoint, so an image past the
20 MB ceiling failed with a bare 1061002 "upload media failed: params error"
naming neither the size nor the limit. The capability was already in the domain:
the deprecated sheets +media-upload has dispatched by size since it was written
(backward.uploadSheetMediaFile), which left the same image succeeding through
the old shortcut and failing through +cells-set-image and +float-image-create,
the ones meant to replace it.

uploadSheetImage now picks the endpoint by size the way doc and the deprecated
shortcut already do. The parent_type is unchanged and still comes from
sheetMediaParentType, so the office/native split rides along either branch.

The preview follows the same branch. appendSheetImageUploadDryRun renders one
upload_all under the ceiling and the upload_prepare / upload_part /
upload_finish trio above it, and both image-write hooks now build their upload
step through it rather than each spelling out an upload_all. A preview that
promised a single-part upload for a file the CLI will send in chunks is a
preview of a different request.

Verified against the live API with a 20.6 MB PNG: +cells-set-image and
+float-image-create both complete, the cell reads back holding the uploaded
image_token at the file's real 3000x2400 dimensions, and the dry-run shows the
three chunked steps Execute hits. The same file failed with 1061002 before.

Tests: the chunked branch at exactly one byte past the ceiling, asserted through
upload_prepare's parent_type with upload_all deliberately left unstubbed so a
regression fails loudly; the preview's step list on both sides of the boundary,
as a unit test and in the e2e dry-run lane. All fail against the previous
behaviour.
2026-08-31 10:57:30 +08:00
xiongyuanwen-byted da24f05064 refactor(sheets): keep the success path off stderr (#2533)
* feat(sheets): reject local-office tokens in +workbook-export

A locally opened Office file (a local_office_ / fake_office_ token, or an
interleaved OFL0X one) names a file the Lark client is showing, not a cloud
document, so the drive export task can only fail on the backend -- and it
fails late, after the create and poll round trips, with an opaque message.

Refuse it up front with a typed failed_precondition that says the workbook is
already a file on disk, and points at +workbook-import for callers who want a
cloud spreadsheet they can export later. The check runs in Validate (so
--dry-run is covered too) and again after the wiki hop in Execute, where the
real spreadsheet token is first known.

* refactor(sheets): report success-path advisories in the result, not on stderr

Every sheets shortcut that had something to say on a successful run said it on
stderr: ignored sub-op locators, emulated dimension semantics, the deprecated
--dimension/--count and +cells-batch-set-style spellings, the dropdown
option-error steer, and the upload/export stage lines in the compatibility
layer. PowerShell's native-command handling and most agent harnesses read
non-empty stderr as failure, so a working call reported itself as an error --
and the facts a caller actually needed sat outside the JSON they parse.

Pure stage text ("Writing image", "Waiting for export task") is deleted: it
duplicates what the result already proves. Everything decision-relevant moves
into the payload:

  - data.warnings           ignored locators, colliding freezes, the dropdown
                            option-error steer (also shown in --dry-run now)
  - data.effective_operation +dim-insert's anchor shift under --inherit-style
                            before, and the whole (rows, cols) state a freeze
                            leaves behind
  - data.deprecation        +cells-batch-set-style and +dim-freeze's legacy
                            flag pair, under a key of its own rather than
                            mixed into warnings
  - data.upload             how +media-upload sent the file

Clean calls keep their exact previous payload shape: every field above is
added only when it has something to report.

Scope is shortcuts/sheets/** on purpose. The remaining success-path stderr in
this domain comes from shared code (the drive export/import core behind
+workbook-export / +workbook-import, the multipart media helper, the auto-grant
helper), which other domains share; cleaning those up belongs to their own
change. The one sheets-owned exception is +workbook-import's extension
correction, which has no slot in the import core's output envelope -- it is
documented at the call site and allowlisted in the guard test.

Tests pin the contract (a successful run leaves stderr empty) and each new
field, plus a source scan that stops new direct ErrOut writes from appearing.

* docs(sheets): point the dropdown option-error warning at data.warnings

The --source-range flag help still told callers the option-error steer arrives
on stderr; it now rides in the result. Mirrors the same edit in the upstream
spec (canonical-spec/spec-tables/flags.json), so the next sync is a no-op.

* fix(sheets): keep export identifiers in +export output, tighten the stderr guard

Review follow-ups on the success-path stderr change:

- +export --output-path lost file_token: on the download branch the token
  reached the caller only through the deleted "Export complete: file_token=…"
  stderr line, and the payload carried just saved_path and size_bytes. Both
  file_token and ticket now ride in the download result, so a caller can
  re-download or resume without re-running the export.

- The stderr guard allowlisted a whole file, hiding any future write in it.
  It now matches one exact statement in one file and asserts that write still
  exists, so both a new write and a stale exception fail the test.

- The contract comments claimed more than the tests prove. They now state
  that only sheets-OWNED code is silent, name the three commands whose noise
  comes from shared implementations (+workbook-export, +workbook-import,
  +media-upload over 20MB), and a new test pins that the shared export core
  does still write -- failing, by design, once that core is cleaned up.

* fix(drive): keep the export and import cores off stderr on success

+workbook-export and +workbook-import delegate to drive.RunExport /
drive.RunImport, so the sheets success-path contract could not hold while
those cores narrated every step: task creation, each poll attempt, completion,
"still in progress", and the import's media upload. Callers that read
non-empty stderr as failure saw a finished export report itself as an error.

The stage text is deleted -- ticket, ready, status, file_token, token and
next_command are all already in the payload. What the narration alone carried
moves into the result:

  - poll               attempts / transient_failures / last_error, added only
                       when a poll actually had to be retried, so a caller can
                       tell a clean run from one that limped to the finish
  - warnings           markdown export falling back to the token as file name
                       after a failed title lookup
  - input_corrections  a caller-supplied record of inputs the CLI rewrote
                       before the request ran; sheets +workbook-import uses it
                       for a mislabeled .xls that is really an .xlsx, which was
                       its last stderr write

drive +export / +import get the same treatment, since they share these cores.
Clean runs keep their exact previous payload shape.

With this, the sheets stderr guard needs no allowlist, and the contract test
covers both workbook commands end to end. Two shared paths a sheets caller can
still reach stay noisy and are named in the contract comment: multipart media
upload over 20MB, and the bot-identity auto-grant warning.

* test(sheets): cover the annotation shapes and both guard call sites

Review follow-ups, all test-side except one comment:

- +dim-insert's effective_operation had no test: a regression could drop the
  emulated-anchor block and still keep stderr empty. Now asserted field by
  field, plus the negative case (--inherit-style after rewrites nothing, so it
  must not gain the block).

- The local-office guard's second call site had no test. A /wiki/ URL only
  reveals its backing token after get_node runs in Execute, so that branch is
  now covered, asserting both the typed rejection and that no export task was
  created.

- annotateSheetsResult's three payload shapes are pinned: object annotated in
  place, array/scalar preserved under `result`, and an empty tool result left
  without an invented `result: null`. The doc comment now spells out that last
  case instead of lumping it in with non-object output.

- The export poll summary test asserted transient_failures but not attempts,
  so a wrong or missing count would have passed.

* fix: preserve recovery state on failure paths and TTY liveness during polls

Review round 2. Removing the success-path narration also removed information
from paths that fail after remote work has started, and removed the only
liveness signal an interactive user had:

- drive +import / sheets +workbook-import: once the import task exists, the
  ticket is the only handle back to it. A poll failure returned bare, so the
  ticket -- previously visible through the polling line -- was lost. It now
  rides on the typed error together with the +task_result command.

- sheets +export --output-path: a download or save failure happens after the
  artifact is ready, so the error now carries ticket, file_token and the
  +export-download command; re-running the whole export is not the recovery.
  A poll timeout carries the ticket for the same reason.

- sheets +batch-update / +batch-chart-*: batch_update is fail-fast without
  rollback, so the ignored-locator and colliding-freeze advisories matter most
  exactly when the call fails part-way -- they decide the safe retry set. They
  are now attached to the typed error's hint as well as the success payload.

- Bounded polls and the import upload are wrapped in RuntimeContext.StartSpinner,
  which is gated on StderrIsTerminal and is a strict no-op for pipes, CI and
  captured output. A human terminal gets liveness back; a machine caller's
  stderr stays empty (the contract tests, which capture stderr, still pass).

+workbook-export's rejection of Office tokens also stopped assuming the caller
holds the file: a local_office_ / fake_office_ prefix means the workbook is
already on their disk, but an interleaved OFL0X token is a file stored in Lark
that may never have been downloaded, so that class is now pointed at
drive +download (then +workbook-import if they want a Lark spreadsheet).

Each behaviour above has a regression test; httpmock's CapturedBodies doc
comment is corrected, since it is appended on every match, not only for
Reusable stubs.
2026-08-31 10:57:30 +08:00
R0bynZhu 96a975e1de fix(slides): relax office token length check from 28 to >=25 (#2531)
* fix(slides): relax office token length check from 28 to >=25

The interleaved "OFL0X" product/region marker is read at fixed positions
(1-based 5/10/15/20/25), so a token only has to be long enough to hold
it. Pinning the total length to exactly 28 silently reclassified every
other length as native.

28 is already stale: per #2509 the local-office format is "OFL0X + 21
random + 1 office type enum" = 27 characters, and sheets relaxed the
identical guard to >= 25 in that PR. Slides was missed, so an imported
office deck at the current length uploaded with parent_type "slide_file"
instead of "office_slide_file".

The marker positions are unchanged. Relaxing the length is only safe
because of them: a false positive is the dangerous direction, since the
drive backend does not validate that parent_node actually names an office
file, so a misclassified native deck uploads successfully and only
surfaces later as an image that will not render. The interleaved native
token cases (same length, different marker) are what keep the floor
honest.

Tests: replaced two mislabelled rows with five verified ones covering 27,
29, 25, 24 characters and a 28-character token carrying the ppt office
type enum. #2509's own labels were off by one or two characters and it
never covered the 25/24 boundary; this does.

* refactor(common): extract local-office token detection into common

The office token shape existed in three identical copies:
shortcuts/sheets/helpers.go, shortcuts/sheets/backward, and
shortcuts/slides. Every copy is somewhere a format change has to be found
again, and that is not hypothetical — #2509 had to apply the same
28-to->=25 relaxation twice inside sheets, and missed slides entirely.

Moved the shape to common.IsLocalOfficeToken. It belongs there because
recognising a local-office document is a drive-level property, not a
per-domain one: an imported office file is an imported office file whether
it backs a spreadsheet or a deck. What genuinely differs per domain is the
parent_type the answer selects — office_sheet_file vs office_slide_file —
so those mappings stay with each domain.

The name deliberately matches the vocabulary #2509 already used
("local-office format"). Its doc comment calls out that "local office" is
the whole category and not the LocalOfficeTokenPrefix case, since the two
now share a word stem while the predicate also accepts
FakeOfficeTokenPrefix and the interleaved marker.

Only slides is rewired here. The two sheets copies are left alone on
purpose to keep this reviewable as a pure no-op for them; they can follow
separately.

The marker offsets are now an array whose length is tied to the marker
string, so adding a character to one without the other stops compiling,
and TestOfficeTokenMinLenMatchesMarkerOffsets pins the length floor to one
past the last offset rather than letting the two merely agree by
coincidence.

Behaviour is unchanged, verified by diffing dry-run parent_type between
the pre-refactor and post-refactor binaries across 13 tokens covering both
prefixes, the 24/25 boundary, 27/28/29 characters, interleaved native
pptcn/shtcn markers, a leading-but-misaligned OFL0X, and an off-by-one
offset: 13/13 identical.

* refactor(sheets): route local-office detection through common

Deletes the last two copies of the token shape, both byte-identical to the
one now in common: shortcuts/sheets/helpers.go and
shortcuts/sheets/backward/lark_sheets_float_images.go. sheetMediaParentType
keeps owning the sheets half of the decision — which parent_type the answer
selects — and only the shape moves.

Equivalence was not assumed from reading. A throwaway fuzz test compared
isOfficeSpreadsheet against common.IsLocalOfficeToken in both packages over
an alphabet biased toward the characters that can actually disagree
(OFL0X plus the native product markers), every single-byte mutation of a
known office token at all 28 positions, and 400k fixed-seed random tokens
of length 0-33. Zero disagreements in either package. Dry-run parent_type
was then diffed binary-to-binary against origin/main across 11 tokens
covering the 24/25 boundary, 27/28/29 characters, interleaved native
shtcn/pptcn markers, a leading-but-misaligned OFL0X and an off-by-one
offset: sheets identical on all 11.

Two comment fixes that the extraction made unavoidable:

The const-block doc in both files still described "a 28-character token".
That was already wrong on main — #2509 relaxed the guard to >= 25 and left
the comment behind — and the shape is no longer described here at all now,
so both defer to common.IsLocalOfficeToken.

Four rows in TestSheetMediaParentType were mislabelled: "25 char, at
boundary" held a 27-character token and the three "new 27-char" rows held
28-character ones, so the floor those labels claimed to cover was never
tested. Relabelled by measured length, and the real 25/24 boundary added.
2026-08-31 10:57:30 +08:00
lark-cli-external-pr-digest[bot] c9adda7b37 chore: release v1.0.91 (#2542)
Co-authored-by: lark-cli-external-pr-digest[bot] <305837809+lark-cli-external-pr-digest[bot]@users.noreply.github.com>
2026-08-31 10:57:30 +08:00
douran-dev 6bbb929c1e docs(im): document chat.join_requests in the lark-im skill (#2395)
Add the chat.join_requests resource (list / handle) and its two scope
rows, matching the registry-side whitelist. Both methods are user
identity only and require the caller to be the chat owner or an admin.

The list entry records a pagination trap verified against the live API:
page_token is returned even when has_more is false, so an agent that
pages while page_token is present never terminates. The handle entry
records that results[] mirrors items[] in count and order and that exit
0 does not mean every item succeeded.

Written by hand rather than via gen-skills. skill-template/domains/im.md
last changed in 7675185f, while this file has five later commits that
edited it directly (#2319, #2223, #2194, #2146, #1906), so a full
regeneration silently reverts them. Reconciling the template with this
file is left as separate work.
2026-08-31 10:57:30 +08:00
yanghao.1600 b540fc747d docs(drive): address knowledge_base_bootstrap review comments
- outputs: load in OUTLINE_PROPOSE as well (align progressive loading)
- entry: narrow pre-write gate to docs +update; allow node-create only
  after separate outline confirmation
- entry: resolve root nodes for bare space targets via wiki +node-list
  (space root = top-level nodes, empty parent)
- entry: triage every non-docx origin node (incl. file); exhaustive
  classification so no node slips through
- outputs/entry: WRITE_CONFIRM shows node_token, command family and exact
  content/diff before the R2 write
- outputs: tag plain-text fences as text (markdownlint MD040)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 20:57:12 +08:00
yanghao.1600 ac3efc0bb2 docs(drive): add knowledge_base_bootstrap workflow
Register a new lark-drive workflow that authors maintenance standards
into an existing Wiki knowledge base: read node tree and drafts,
optionally propose and create an outline when the structure is too
sparse, then write general standards to the root node and per-node
requirements to each sub-node after confirmation.

- entry + outputs reference docs (R2/S2), no phase files
- node type triage: docx writable, non-docx/shortcut skipped or new_docx
- default append (keep drafts), overwrite only placeholders
- OUTLINE_PROPOSE state supports root-only knowledge bases
- register in workflow registry; route from lark-drive and lark-wiki

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 20:33:28 +08:00
taojieyeta-design 0d5334a0cd docs(approval): document keyword search and add-sign flow (#2388) 2026-08-27 17:38:22 +08:00
91-enjoy b45d4cbb1c feat: add im message patch meta api (#2407) 2026-08-27 17:19:48 +08:00
yballul-bytedance 93817909cb feat(base): add field extension shortcuts (#2463)
Co-authored-by: yballul-bytedance <273011618+yballul-bytedance@users.noreply.github.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
2026-08-27 17:05:45 +08:00
zgz2048 84f9414311 feat(base): streamline record workflows (#2529) 2026-08-27 13:39:20 +08:00
calendar-assistant 1c4f7588dd feat(calendar): add +join-event shortcut and share token support (#2508)
* feat(calendar): add +join-event shortcut for joining via share token

Add a share-token-only join path so callers cannot forge a plaintext
event id, wire it into Shortcuts(), and document the flow in the
lark-calendar skill.

* docs(calendar): document sharing events via share_info link

- Route "share event to person/group" intent to calendar events
  share_info then lark-im, clarifying the share link is not an applink

* fix(im): preserve calendar share token in shortcuts

---------

Co-authored-by: 张哲伟 <zhangzhewei@bytedance.com>
2026-08-27 11:20:00 +08:00
xiongyuanwen-byted 971e639622 feat(sheets): relax local-office token length check from 28 to >=25 (#2509)
The local-office token format is changing from 28 to 27 characters per the
new rule (OFL0X + 21 random + 1 office type enum). Relax the guard from
 to  so 27-char tokens can reach the OFL0X interleaved
marker check. All legacy detection paths (fake_office_ prefix, local_office_
prefix, OFL0X marker) are preserved unchanged.
2026-08-26 15:44:50 +08:00
ViperCai d0158ab289 fix(docs): recover PowerShell-dequoted presentation JSON (#2501)
* fix(docs): recover PowerShell-dequoted presentation JSON

* fix(docs): recover quoted-key shell JSON
2026-08-26 15:09:17 +08:00
xiaoxiangyu-123 0f9553385c feat(im): add chat AppLink output (#2491)
Add chat AppLink fields to IM chat outputs and update lark-im guidance to prefer CLI-provided links.
2026-08-26 10:55:59 +08:00
lark-cli-external-pr-digest[bot] c8f06cd167 chore: release v1.0.90 (#2503) v1.0.90 2026-08-25 22:07:14 +08:00
liangshuo-1 8493800ebf feat(config): support keychain-backed tenant access tokens (#2488) 2026-08-25 21:39:50 +08:00
dc-bytedance 36c7291cc1 fix(auth): exclude im:message.send_as_user from batch scope sets (#2471)
* fix(auth): exclude im:message.send_as_user from batch scope sets

* test(auth): isolate requested-scope cache in batch-exclusion test

* fix(auth): validate --exclude against pre-filter scope universe

The batch-exclusion filter dropped im:message.send_as_user before --exclude
was validated, so `--domain im --exclude im:message.send_as_user` returned
invalid_argument and never sent the device authorization request. That broke
automations relying on --exclude to skip the send-as-user approval.

Validate --exclude against the selected universe (post recommend/common
filter, pre batch exclusion) plus --scope; the wire request still uses the
batch-filtered effective scopes. Excluding a batch-withheld scope is now a
valid no-op, while excluding a scope outside the selection still errors so
typos are not widened.

* test(auth): assert exact scope membership in exclude regression test
2026-08-25 21:13:57 +08:00
caojie0621 083f0f4719 feat(drive): support appid in member-remove (#2499) 2026-08-25 20:30:39 +08:00
sang-neo03 6952d3aa7f feat(extension): add business command extension v1 (#2308)
* feat(shortcuts): import typed shortcut framework from c07b64621

* feat(extension): add public command contract

* feat(extension): compile and register business commands

* feat(cmd): assemble business command sets

* fix(auth): derive login domains from shortcuts

* test(extension): add business command test runtime

* ci: verify generated Go sources

* test(auth): match help and interactive domains

* fix(extension): validate command path segments

* fix(extension): satisfy generator and error guards

* fix(extension): follow generated domain naming contract

* fix(command): complete extension runtime contracts

* test(command): cover public extension surface

* fix(command): remove unused typed runtime APIs

* fix(command): address follow-up review findings

* fix(command): enforce extension v1 contracts

* fix(command): remove unreachable compatibility wrappers

* fix(auth): keep scope-less domains addressable via --domain, matching main

Move the declared-scope filter from allKnownDomains to the interactive
selector only. On main, a scope-less shortcut domain (event) passes
--domain validation and fails later with "no matching scopes found";
the previous unified filter changed that to "unknown domain" and
dropped it from the --help list. The interactive picker still hides
scope-less domains — selecting one can only fail.

* feat(command): add PathSegment for user-provided path values

Business code concatenates IDs into request paths but had no public
escape helper (internal/validate.EncodePathSegment is unreachable from
extension/command). Mirror its url.PathEscape semantics, use it in the
business command examples, and pin the traversal defense: the validator
decodes percent-encoding before the canonical check, so both raw and
escaped dot sequences fail same-origin validation.

* docs(command): add runnable chat-brief distribution example

Mirror the audit-observer precedent: a buildable wrapper main under
examples/ showing WithCommandSets against the real distribution shape
(plugins, strict mode, and service commands stay enabled). Covers the
single-read command (Validate, shared DryRun request, CallJSON,
PathSegment, Tips) and a Page[T] list command whose pagination flags
come from the compiler. The testdata/wrapper fixture stays test-only.

Verified offline: --help renders tag-driven parameters, +chat-brief-list
exposes --page-all/--page-limit/--page-delay, and --dry-run previews the
request with a fake env token and no network access.

* fix(command): normalize page envelopes and bound CollectAllPages

Two pagination contract fixes from the extension design (owner plan §8.3):

Page decoding accepted only a literal "items" array, so endpoints that
spell their list field differently (drive uses files, some responses use
records) walked every page while decoding nothing — CollectAllPages then
returned an empty set marked complete, and downstream writes ran against
it. Each page now normalizes its single top-level array field into
Page.Items; zero or multiple array fields fail closed with a typed
invalid-response error.

CollectAllPages previously reused the user-facing --page-limit maximum
(1000) as its walk bound. A complete-set collection holds every page in
memory before the workflow's writes run, so it now uses the design's
dedicated workflow bound of 100 pages.

* feat(commandhost): note bounded repetition in Page dry-run previews

A Page[T] command's dry-run can only show the first request; fabricating
response-dependent page tokens is forbidden. Append the bounded-repeat
explanation to the previewed request (preserving any business
description), matching the design's dry-run contract.

Also give the example's list command a --page-token resume flag seeded
into the request, documenting the resume convention: the framework owns
--page-all/--page-limit/--page-delay while the starting cursor is a
business-declared input, independent of --page-all.

* docs(command): generate domain constants with English comments

The generator emitted Chinese titles while the rest of the public
extension packages document in English. Switch the generator to the
"en" service title and regenerate. The generator already rejects a
domain missing either locale, so the switch keeps its own guard.

* docs(command): mark the host adapter read surface

The Host* types, InspectCommand, InspectDomain and CloneSets exist for
lark-cli's host adapter, not for business commands, but nothing said so
at the symbols themselves. They cannot move to a subpackage: a Command
holds its declaration unexported, so a sibling package has no way to
reach it, and moving the wire types to internal/ would cycle back
through CommandMetadata and CommandContext.

Also correct HostPagination, which is not adapter-only -- ContextOptions
and commandtest both carry it.

* refactor(command): type CommandMetadata.Service as DomainName

A set already declares its domain through ExtendDomain(DomainIm), yet
every command repeated the same domain as a bare string that only the
host compiler checked. Typing the field points authors at the generated
enum and forces an explicit conversion when the value comes from a
string variable.

This does not make a mistyped literal a compile error -- an untyped
constant still converts to DomainName -- so the mismatch check in
CompileSets stays the actual net. The example and the wrapper fixture
now declare command.DomainIm.

The chat-brief example was also not gofmt-clean, which the CI format
gate would have caught.

* refactor(command): extract the command-set assembly steps

buildInternalWithConfig is an orchestrator, and the business command
sets were compiled inline inside it. Move that step to
resolveShortcutSnapshot so the entry point reads as one call and the
built-in/external merge has a name.

newCommand carried six near-identical blocks that each nil-checked a
hook and wrapped it in the same type assertion. Split them into one
binder per hook shape; Normalize and Validate now share bindArgsHook
since their signatures match. The behaviour is unchanged: an undeclared
hook still erases to nil, and an empty renderer map still yields nil.

* perf(shortcuts): stop re-cloning an already-isolated snapshot

AllShortcuts deep-copies because a Shortcut carries slice fields whose
backing arrays a shallow copy would share: an external distribution
mutating registered[0].Flags[0] would corrupt the process-global list.
That copy is worth its ~165us over 500+ shortcuts.

Paying it four times per startup is not. auth, schema and the mount path
each cloned the snapshot again, but they receive it from
AllShortcutsWithExternal with no third-party code in between, and nothing
in this repository mutates a shortcut element -- mountDeclarative takes a
value receiver and only replaces slice headers. Drop those three copies
and document the boundary on AllShortcuts so the next reader does not
reintroduce them.

Startup drops from four full clones to one. Benchmarks pin the remaining
cost so a regression points at a new clone rather than at growth in the
shortcut set.

* fix(command): align the commandtest page bound and escape wrapper paths

Two review findings, both of which let a business command pass its tests
and then misbehave in production.

The commandtest recorder walked 1000 pages for a complete-set collection
while the host adapter stops at 100, so a command tested against 300
pages of fixtures would fail its first real --page-all run with
PaginationLimitError. The bound now lives in internal/pagination, which
both sides already import, and the hard-limit test scripts itself from
that constant instead of restating 1000 -- the literal was what let the
two drift apart.

The testdata wrapper concatenated args.ID straight into the request path,
contradicting PathSegment's own documented rule and the chat-brief
example. ValidateRequestView does not cover this: "abc/other-users-file"
cleans to itself, so an unescaped separator silently retargets the
request. Since testdata is what an integrator copies first, route both
call sites through one readRequest helper, mirroring chat-brief.

The e2e assertion could not have caught it either -- PathSegment("chat_1")
is "chat_1", so the check passed with or without the call. It now sends
"chat/1" and asserts %2F reaches the wire; removing PathSegment fails it.

* fix(command): deny network to the pre-confirmation hooks and four review findings

Normalize and Validate run before the high-risk confirmation gate, and
both received the full CommandContext, so a high-risk business command
could POST or DELETE from Validate and leave remote side effects behind
before the user was ever asked to confirm. Moving the gate earlier would
contradict the documented hook order and would also make --dry-run
require --yes. The design already forbids this from the other side --
Validate is specified as parameter checking that issues no request -- so
enforce that instead: Normalize and Validate get a context whose CallJSON
and CollectPages refuse, while PreflightScopes stays available. The guard
sits in CommandContext rather than in the wiring, so a future adapter
that wires the callbacks anyway still cannot reach the API. commandtest
mirrors it, otherwise a command would pass its tests and fail only in
production.

Page.Items now starts non-nil. It is declared required;nonnullable, but a
zero-item collection encoded as {"items":null}, which a caller generating
types from the published schema would reject.

NewCmdAuthWithRecovery and NewCmdSchemaWithVisibility are restored as
wrappers. Both were dropped for shortcut-aware variants, and both are
reachable from outside this module: CommandVisibility is an ordinary
exported func type, and *recovery.Projector cannot be named by an outside
caller but can be passed as nil. A signature test now pins them.

The path-traversal fixture said "../../secret", which the deterministic
gate rejects as a generic credential assignment -- the reason CI is
currently red. The filename carries no meaning; it is now "../../outside".

* test(cmd): exercise the retained constructors instead of naming them

The compatibility wrappers restored for outside callers are unreachable
from inside this repository by construction, so the incremental dead-code
gate rejected them. A signature-only assertion did not help: taking a
function value and discarding it leaves the body unreachable, and it
proved nothing about whether the wrapper still builds a working command.

Call each one and assert the command it returns. NewCmdAuthWithRecovery
is called with a nil projector, which is the exact call an outside module
can make and the reason the wrapper has to keep compiling.

Verified with the same deadcode version CI runs: neither function is
reported, and no other function in this branch's files is either.

* docs(command): document the hook contract and pin dry-run note idempotency

Hooks is the first type a business author reads and carried no field
documentation, so the rules lived only in the design doc: which of DryRun
and DryRunE to set, that setting both fails to compile, that Execute owns
the API call and must not write stdout, and that Normalize and Validate
run before the confirmation gate and therefore get no network.

The choice between DryRun and DryRunE is not old-versus-new -- neither is
legacy. It follows from whether building the preview can fail, which is
now what the field docs say.

Also pin the dry-run note as idempotent. convertDryRun writes the
bounded-repeat note into the projection it builds, never back into the
hook's *DryRun, and DryRunAPI.Desc assigns rather than appends, so a hook
that caches and returns the same preview cannot accumulate the note. Both
properties were true and neither was tested.

* refactor(command): settle the dry-run constructor, tips, and domain enum

Three narrowings of the V1 business-command contract, none of which has a
published compatibility surface: extension/command does not exist on main.

Preview and NewDryRun were the same constructor twice -- one empty, one
seeded with requests. Fold them into a variadic NewDryRun. Every existing
NewDryRun() call keeps compiling, and the domain word in the contract is
now spelled one way. The type DryRun already owns that identifier in this
package, so naming the constructor DryRun outright cannot compile.

Drop Metadata.Tips. It was pure passthrough into common.Shortcut.Tips and
nothing in the execution path read it, so business commands lose only the
ability to declare help tips; the repository's own typed shortcuts keep
theirs. The mount test asserted a tip reached the rendered help as proof
that metadata survives the extension -> commandhost -> common.Shortcut ->
help conversion; it now asserts the risk line, which travels the same path.

Hand-write the domain enumeration and delete the generator. Generating
from shortcuts.AllShortcuts silently omitted approval, attendance and
mindnotes: all three are published under `lark-cli --help` and served by
typed and raw API commands, they just own no shortcut. The enum is now
the 23 domains the CLI actually exposes.

Those three would otherwise have been constants that compile and always
fail, because CompileSets derived its mountable domains from the same
shortcut list. It now reads the service registry, and shortcuts/register.go
already creates a domain command group on demand when no built-in occupies
it, so a business command can mount under a shortcut-less domain.

* ci: stop generating extension/command

The domain enumeration is hand-written now and extension/command holds no
go:generate directive, so the path was a no-op that still read as if the
package carried generated files.

* refactor(command): drop DryRunE and let the preview render like a built-in

DryRunE has no counterpart in the shipped CLI: `git show
main:shortcuts/common/types.go` has no such field, it arrived with the
typed-shortcut framework this branch imported, and no shortcut in the
repository sets one. Business commands get the single DryRun hook that
built-in shortcuts have. Validate already runs before it and owns the
error channel, so a preview that cannot be built still fails there with a
typed error -- which is what the repointed tests now assert, end to end
through --dry-run and through commandtest.Preview.

Also stop appending the bounded-repeat note. convertDryRun added "with
--page-all, repeats with the returned page_token until exhaustion or
--page-limit" to every Page[T] preview, so an external command's dry-run
carried a sentence its author never wrote. Built-in paginated shortcuts
say this themselves when they want it (im_chat_members_list.go calls
dry.Desc), and external commands now do the same: the framework renders
the description it was given and nothing else.

The dry-run context keeps refusing requests -- runner.go does the same for
built-ins, so removing that would be the divergence, not the alignment.
Only the word changes: "offline" was our own vocabulary for what the rest
of the CLI calls dry-run.

* refactor(command): drop the partial-failure outcome from the contract

Business commands now return Success only. Partial, OutcomeDefinition,
PartialFailureDefinition and FailedItemDefinition leave the public surface
along with Execution.Partial and the host adapter's receipt conversion.

Result keeps its outcome field. It is no longer a choice -- Success is the
only value -- but it is also how the host tells a returned Result apart
from the zero value that accompanies an error, which is the check
commandtest.Execute makes before reporting "returned both Result and
error". Collapsing it to nothing would delete that signal.

The exemplar commands that returned Partial keep their scenarios: a
best-effort scope failure still marks every item failed and appends the
snapshot, and the multi-call audit still records the owner it could not
resolve. That information lives in the command's own Data (Items[].State
plus Failures), not in the outcome, so the tests assert the same facts and
only the outcome assertion is gone. The deep-copy test moved its nested
JSON exemplar from FailedValues to InputDefault.Value, keeping
cloneJSONValue covered.

shortcuts/common still defines PartialFailure for built-in typed
shortcuts. That is the imported framework, untouched here.

* refactor(shortcuts): walk pages once for built-in and external commands

Commit 4d0c6ea61 added internal/pagination, moved PaginateInto onto it,
and then wrote a second caller for externally declared commands. Both
assembled the same Walk options, cloned the same params, read the same
cursor and mapped the same walk error; only the policy source, the call
path and the accumulator ever differed.

Those three now parameterize one pageWalk. PaginateInto keeps calling
through the RuntimeContext and keeps its per-page progress line; external
commands keep CallTypedAPI, the walker's context and their undecoded
pages, which the public contract needs because it decodes them into its
own Page[T]. Behavior is unchanged on both sides -- the external walk
still leaves Wait nil, which internal/pagination fills with WaitContext,
so --page-delay works exactly as before.

pageWalk is deliberately generic-free so one struct serves both callers;
the typed half of the built-in path moved to addDecodedPage.

* refactor(shortcuts): make the external page walk PaginateInto's twin

CollectCommandPages now differs from PaginateInto only where the context
type forces it. It takes the same PageAccumulator, decodes each page into
T through the same addDecodedPage, returns the same *output.PaginationMeta
and reads the same state out of the same walk, in the same order.

What is left is what the interface cannot supply. An externally declared
command compiles in the business module, so it holds a CommandContext
rather than a *RuntimeContext: the context arrives as a parameter because
the interface carries none, the call goes through CallTypedAPI, and there
is no progress line because deciding to print one needs StderrIsTerminal,
JqExpr and Format, none of which the interface exposes.

The all parameter stays. It is the complete-set policy CollectAllPages
depends on -- collect to exhaustion under the hard page bound instead of
obeying --page-all and --page-limit -- and PaginateInto has no way to
express it, since resolvePaginationPolicy only ever reads flags. Dropping
it would quietly turn a command that must see the whole set into one a
user can truncate with --page-limit 1.

CommandPageCollection is gone with it: pages accumulate in commandhost's
own accumulator, the way every built-in shortcut already accumulates its
own. One consequence of sharing the decode: a page whose response carries
no data object is now an error on this path too, as it always was for
built-ins.

* fix(shortcuts): reject recursive Data and Args types during compilation

shapeForType and compileStructShape called each other without recording the
Go types already being walked, so a self-referential type recursed forever.
The walk ran during command registration and ended in a stack overflow --
a fatal runtime error rather than a panic, so no recover boundary could
contain it and one extension command took the whole CLI down before --help,
schema, or any unrelated command could run. That also broke
CompileErasedDefinition's documented promise to compile without panic.

Thread the struct types open on the current recursion path through both
functions and return a compile error on a repeat visit, pointing at the
explicit Shape escape hatch. Membership is scoped to the path, not the whole
walk, so a type reused as a sibling or at another depth stays legal.

Covers self-reference through a slice and through a pointer, mutual
recursion through two types, the JSON-encoded Args path, and the public
CompileErasedDefinition contract.

* fix(command): share one result protocol between the host and commandtest

commandtest.Execute only checked that Data carried the expected type. Generic
erasure leaves a correctly typed zero Data behind, so a business command that
returned Result{} instead of Success(data) passed the type assertion and the
test reported success. Production rejects the same result, which left
extension authors with green tests and a command that failed on every real
invocation -- exactly the guarantee commandtest exists to provide.

Add ValidateHostResult in extension/command and call it from both
commandtest.Execute and the host adapter's execute hook, so the two surfaces
cannot drift. It rejects an empty or unsupported outcome and mirrors the
pagination receipt checks the host already applies: declared Page output,
pages of at least one, non-negative items, and next-token state consistent
with completeness.

RunWithFlags is covered because it delegates to Execute.

* feat(command): expose reusable download capabilities

* test(command): make the chat-brief example testable and cover its hooks

The example declared both commands as inline Definition literals handed
straight to Define. Define erases the type parameters and returns an opaque
Command that cannot hand its Definition back, while commandtest.Execute takes
the Definition -- so the shape the example demonstrated could not be unit
tested at all. Neither shipped example had a test file, so nobody had walked
the copy-the-example-then-add-a-test path.

Lift both declarations into Definition-returning functions, the shape the
repository's own commandtest suites already use, and keep the compiled
Commands as package vars so main is unchanged. The configuration bodies are
untouched.

Add the tests that shape exists for: the single-read projection and its
Validate rejection, plus the Page[T] contract for default single-page reads
and a --page-all walk through RunWithFlags. All four run offline through the
commandtest recorder.

* test(commandtest): cover the ordered URL download script

Recorder.ReplyURL shipped without a caller, so the incremental dead code
gate flagged it as new unreachable code and blocked the branch. The existing
URL download test uses the unordered RespondFile constructor, which never
exercises the URL assertion ReplyURL exists for.

Mirror the ReplyJSON pair: one test walks two scripted URLs in order and
checks the recorded source URLs, content types, and artifacts; the other
points DownloadURL at an unscripted URL and expects the mismatch error.

* feat(command): accept @file input for external commands

V1 rejected the file value source, leaving external commands with inline
flags and stdin only. A process has one stdin, so a command whose body is
too large or too quoted for the shell -- an XML document update is the case
that surfaced this -- had no second way to receive it, and the caller had to
fall back to shell escaping.

Nothing downstream was missing: resolveInputFlags already resolves @path and
the @@ escape through the invocation's FileIO, help renders the "@file"
affordance, and legacyInputSources maps the source onto the compiled flag.
The gap was the public constant and the host allow-list.

Export SourceFile and let it through compilation. Unknown sources still fail
the same way, which the rewritten host test now pins alongside the compiled
flag actually carrying both extra sources -- silently dropping a declared
source is the regression worth catching.

Give the wrapper fixture a command whose content flag declares all three
sources, and drive it end to end: @file and stdin both reach the request
body, and the help text advertises them.

* revert(content): drop the default content exports from the extension surface

Exporting the repository's embedded skills and affordance trees turned two
content directories into Go packages, because go:embed cannot reach up out of
a package directory and the repository root is package main. That cost two
things: a .go file living inside an authored-content tree, where a future
content type is silently omitted until someone edits the glob, and an embed
directive per tree where the root previously covered both in one.

The need it served does not exist. The distribution driving this work ships
its own skills and does not consume the official set, and a wrapper that does
want them can supply a tree through cmd.SetEmbeddedSkillContent, which is what
extension/platform already documents.

Restore content_embed.go, the SkillsOverlay comments, and the platform README
to their main state, and delete the two exporting packages. The example and
fixture wrappers now ship no embedded content, which is what a wrapper that
does not compile the repository root actually gets; the e2e assertion that
depended on inheriting lark-doc goes with it.

* fix(command): close the review findings on API surface and storage commit

Five findings from the extension-v1 review, each verified by a test that fails
against the previous implementation.

Source compatibility of auth.LoginOptions. The shortcut snapshot was an
unexported field on a struct that appears in the exported runF signature, which
ends positional literals for every caller outside this module. It becomes a
closure capture plus an explicit authLoginRun parameter, and the six domain
helpers collapse into domainResolver methods, removing five xxxWithShortcuts
twins that only tests reached. common.Shortcut.DryRunE goes too: it was a new
exported field with no production caller. The unexported typed field stays, so
Shortcut itself is still not positional-literal compatible -- that is a
deliberate remaining gap, since relocating it would need a global mutable map or
a wider signature change.

One canonical wire projection. queryValues stringified and dropped nils for the
live call while the dry-run preview and the pagination walk forwarded raw values,
so a preview could describe a request the runtime would never send. canonicalQuery
is now the only projection and all three consumers derive from it. Dry-run output
for numeric parameters therefore reads "20" instead of 20, matching what the
query string actually carries.

No-clobber as a storage guarantee. IfExistsFail checked existence, downloaded,
then committed with a rename that replaces unconditionally, so a target created
during the transfer was silently overwritten. The commit step is now an optional
ExclusiveFileIO capability: content lands in a temp file and is published with
Link, which refuses an existing target and never exposes a partial file. A
provider without the capability is refused rather than served a guarantee it
cannot keep.

V1 public surface. Removes NewDomain and its options (host compilation rejected
them), HostDomain.IsNew, reservedRootNames, and the unproducible
ResultMetaDefinition.Count; narrows Hooks.Renderers to a single PrettyRenderer,
since pretty was the only key the compiler accepted; and demotes the generic
authoring layer in shortcuts/common to unexported, as no production code outside
that package used it.

commandtest runs the production compiler. Execute, RunWithFlags and Preview now
share compileForTest, so a wrong tag, Shape or relation fails in the unit test
instead of at CLI startup. Applying this surfaced two long-standing contract
violations in the package's own fixture.

* refactor(command): keep a single authoring contract

* revert(schema): keep the schema command blind to shortcuts

The schema command serves the generated API catalog only, matching main.
Drop the shortcut contract lookup and completion from cmd/schema and
restore the constructor surface. ShortcutSchema stays on the sealed
commandbridge surface, where the host compiler tests assert the contract;
the CLI itself no longer consumes it. The surface scenario and wrapper
e2e pin the boundary from the other side: schema resolution and
completion must not see mounted shortcuts.

---------

Co-authored-by: sang-neo03 <266690410+sang-neo03@users.noreply.github.com>
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
2026-08-25 20:22:13 +08:00
zhengzhijiej-tech 5f35c72bd3 feat(sheets): combine chart workflows and special chart types (#2374)
* feat(sheets): support partial chart snapshot schemas

* feat(sheets): add semantic chart shortcuts

* feat(sheets): improve semantic chart workflows

* fix(sheets): prefer semantic chart shortcuts

* fix(sheets): normalize chart range and flag inputs

* fix(sheets): normalize irregular chart ranges

* feat(sheets): add chart data update shortcut

* feat(sheets): harden chart update workflows

* feat(sheets): improve chart creation dimension handling

* feat(sheets): add dedicated chart batch shortcuts

* fix(sheets): allow chart color theme patches

* fix(sheets): persist chart color theme updates

* feat(sheets): simplify batch chart operations

* fix(sheets): preserve batch scope and cross-sheet chart ranges

* feat(sheets): support bubble waterfall and pareto charts

* fix(sheets): sync special chart tool schema

* feat(sheets): add semantic bubble chart indexes

* docs(sheets): sync combined chart workflow guidance

* fix(sheets): align combined chart artifacts

* feat(sheets): add x-axis number interpretation flag

* docs(sheets): validate chart axis semantics

* fix(sheets): allow recursive chart update patches

* test(sheets): isolate chart create schema check

* feat(sheets): refine semantic chart creation

* docs(sheets): sync semantic chart guidance

* docs(sheets): remove unrelated label position guidance

* fix(sheets): support all chart data label combinations

* fix(sheets): support numeric x-axis bounds

* feat(sheets): support chart y-axis bounds

* feat(sheets): add last-point chart label flag

* fix(sheets): preserve disabled waterfall stacking

* fix(sheets): nest last-point chart label property

* fix(sheets): resolve lint and dead-code CI failures

- lark_sheet_chart.go: drop redundant chartConfigUpdateInput /
  chartDataUpdateInput calls in Execute whose result is immediately
  overwritten by the *FromSnapshot variant (ineffassign); the snapshot
  variants already re-run the same validation internally.
- lark_sheet_chart_test.go: remove Go 1.22+ redundant loop-variable
  copies (copyloopvar).
- batch_op_dispatch.go / lark_sheet_batch_update.go: remove unreachable
  allowedBatchShortcuts and batchUpdateInput; callers use the lower-level
  allowedShortcuts and buildBatchUpdatePlan directly.

* fix(sheets): validate chart config updates

* fix(sheets): sync skill specs and chart schema validation

* fix(sheets): resolve chart review follow-ups

* fix(sheets): restore the two-color contract

* fix(sheets): require at least two chart colors

* docs(sheets): expose advanced chart shortcut flags

* fix(sheets): address chart review feedback

* fix(sheets): surface ignored batch locators

* chore(sheets): bump skill version to 3.1.6

* fix(sheets): surface batch warnings consistently

* test(sheets): satisfy copyloopvar lint

* docs(sheets): sync skill from spec

* fix(sheets): harden chart batch updates

* fix(sheets): tighten chart update validation

* fix(sheets): canonicalize chart ranges and batch targets
2026-08-25 18:53:47 +08:00
wangweiming-01 62f270afd6 fix(skills): scope markdown routing to Lark resources (#2497) 2026-08-25 18:37:13 +08:00
wangweiming-01 faa2f8d3e0 fix(docs): continue media downloads on permission scope errors (#2498) 2026-08-25 18:33:35 +08:00
BD-ZERO 36cd24aa73 fix(slides): avoid PID variable in examples (#2496)
Replace `$PID` with `$PRES_ID` in Slides Skill examples to avoid conflicts with PowerShell’s read-only `$PID` variable.

This only updates embedded Skill documentation. CLI flags, API fields, runtime behavior, and historical Skills remain unchanged.

Validation:
- Skill format check passed
- Quality gate passed
2026-08-25 17:55:03 +08:00
liujinkun2025 f09414f9d5 fix(wiki): keep node-get stderr machine-readable (#2449) 2026-08-25 17:27:41 +08:00
zhangjun-bytedance aec659c461 feat: words replace and minutes fix (#2490) 2026-08-25 17:18:07 +08:00
wangweiming-01 e668fff669 fix(drive): continue downloads on permission scope errors (#2494) 2026-08-25 16:59:27 +08:00
ethan-zhx a06d87333d feat(slides): add marginRight attribute to <p> element schema (#2493)
Add marginRight property to the paragraph element in slides XML schema definition,
matching the existing marginLeft attribute to support right-side paragraph indentation.
This resolves the xml_lint error "unsupported SXSD attribute "marginRight" on <p>".
2026-08-25 16:11:49 +08:00
R0bynZhu 1d24a39659 fix(slides): strip stale <note> id in +update-slide to avoid backend crash (#2475)
* fix(slides): strip stale <note> id in +update-slide to avoid backend crash

A +update-slide carrying a <note id="..."> that is not the page's current
note block makes RewriteSlideBySXSD reject the whole page with
"block is not NoteBlock". This happens when the XML is copied from another
page, or written over a page that was re-created (add-slide reassigns ids,
so the note block's id no longer matches).

Drop only the <note> id before sending. The backend then targets the page's
own note block and the write succeeds. Every visible element keeps its id,
so it is updated in place rather than rebuilt — text layout is preserved and
there is no risk to svg-internal id references.

* fix(slides): strip single-quoted and spaced note id too

The note-id strip only matched id="...". A single-quoted or spaced form
(id='...', id = "...") slipped through. Both are valid XML, and the backend
accepts single-quoted markup — verified on ppe: an all-single-quote page
updates fine, and a single-quoted stale note id reproduces the exact
"block is not NoteBlock" crash this strip is meant to prevent, while the
double-quoted equivalent is stripped and succeeds.

Widen the regex to `\s+id\s*=\s*("[^"]*"|'[^']*')` so any quote style and
whitespace around '=' are covered. Still a targeted edit on the <note> tag,
not a re-serialization, so the caller's bytes are otherwise preserved.

Add regression cases: single quotes, whitespace around '=', single-quote
attribute order, and a single-quoted visible-element id left untouched.

Addresses CodeRabbit review on #2475.

* test(slides): assert the note id attribute is removed, not just a value

The strip tests checked that a specific id value ("blw") disappeared, which
would also pass if the implementation swapped the id for another value.
Assert on the <note> opening tag carrying no id attribute at all (any quote
style / spacing) via a noteTagHasID helper, so the removal itself is verified.

Addresses CodeRabbit review on #2475.

* fix(slides): locate the <note> tag with the XML tokenizer before stripping id

The raw regex parsed XML as plain text, so it could miss or mis-edit valid
input: an id after an attribute whose value contains '>', and note-like text
inside comments or CDATA. It also had no notion of where the note sat in the
tree.

Walk the document with encoding/xml to find the start tag of the <note> that
is a direct child of the root <slide>, then delete the id attribute by editing
only that tag's bytes. Nothing is re-serialized, so quote style, attribute
order, whitespace, and every other element (notably inline <svg> namespaces,
whose round-tripping is a known source of "embed missing inner svg") survive
untouched — the same byte-preservation contract ensureXMLRootID keeps.

This covers the cases the regex could not: '>' in an attribute value, and
comment/CDATA text that merely looks like a <note>; and it scopes the edit to
the slide-level note only. Regression cases added for each.

Addresses CodeRabbit review on #2475.

* test(slides): assert everything but the note id survives byte-for-byte

Existing tests spot-checked that individual elements survived. Add exact-equality
cases asserting the output equals the input with only the one note id removed —
proving nothing else moves: inline svg subtrees, CDATA, a '>'-bearing attribute,
quote style, attribute order, and whitespace all stay verbatim.

Addresses CodeRabbit review on #2475.

* style: gofmt slides_update_slide.go

* test(slides): cover numeric char refs — InputOffset must not drift the note span
2026-08-25 14:36:16 +08:00
zhicong666-bytedance 35bd5ecfcd feat(vc): add agent meeting control shortcuts (#2466)
* feat(vc): add agent calendar meeting actions

* feat(vc): add meeting screenshot shortcut for visual context

* feat(vc): add meeting countdown commands and events

* fix(skills): avoid screenshot recall from meeting description

* fix(vc): omit screenshot log ID on success

---------

Co-authored-by: renaocheng <renaocheng@bytedance.com>
Co-authored-by: shike.11 <shike.11@bytedance.com>
2026-08-24 21:26:44 +08:00
wanghm25 0679884761 fix(base): hide dashboard auto analysis setting (#2465) 2026-08-24 20:18:33 +08:00
ethan-zhx 7874dc144b feat(slides): add media download shortcut (#2446) 2026-08-24 18:26:23 +08:00
ethan-zhx 8ebdc3f193 feat(slides): un-deprecate +replace-pages shortcut (#2470)
Remove deprecation markers from +replace-pages: the command is now a
supported shortcut again, not a deprecated compatibility shim. Delete
the deprecation-note constant and the "deprecated" output field from
dry-run, validate-only, and real-run envelopes, update the command
description and comments, and remove the deprecation-specific test.
2026-08-24 18:23:50 +08:00
chenxingyang1019 e0e90a4e1b fix(apps): classify the online DDL ban and the file storage quota failure (#2460)
* fix(apps): classify the online DDL/DCL ban on +db-execute

Running DDL against the online branch of a multi-env app came back as
api/server_error with exit 1, hinted "fix the SQL and re-run", and carried a
statement position that did not exist. All three point the caller the wrong way:

  - server_error means "upstream 5xx, retryable"; this is a product rule and no
    number of retries changes it;
  - the SQL is fine — the target environment is what has to change;
  - "(at statement 1 of 1)" is fabricated. The server pre-validates the whole
    batch and returns a single ERROR sentinel, so a 5-statement request with the
    DDL in position 4 still rendered as "1 of 1". The CLI does not split the SQL,
    so it cannot know the real count and cannot detect the mismatch generally —
    only codes known to be batch-level rejections can drop the suffix.

Give code 4000001 its own arm: validation/failed_precondition (exit 2, "change
the environment, do not retry"), a hint pointing at dev plus +db-env-migrate, and
no statement position. Every other code keeps its current classification, wording
and position suffix; a test pins that.

4000001 is a dedicated server-side code (ErrOnlineEnvForbidDDLDCL, client-error
band), raised only by the pre-validation pass when env==online on a multi-env
workspace. Syntax errors and PG errors use different codes, so keying on it is
safe. Matching on the numeric value also covers the "k_dl_4000001" wire form,
since codeString already strips that prefix — both forms are tested.

"No statements were applied" is stated rather than inferred here: the validator
walks every statement and rejects the batch on the first DDL, so nothing lands.
The default arm would have inferred the opposite for a DDL in a later position
("Earlier statements were committed"), which is wrong for this code.

* fix(apps): classify tenant file storage quota exceeded

+file-upload against a tenant whose file storage is full returned api/unknown with
no hint at all, so a caller could not tell "the quota is full, stop" from "the
upstream had a bad minute, retry" — and had no next step either.

Register 400000055 as api/quota_exceeded. That is a dedicated server-side code
(ErrTenantStorageQuotaExceeded, client-error band) raised only on the upload path,
and the subtype already carries "retrying will not help", so the framework's
existing quota wording is enough and no domain-specific hint is added.

Left in CategoryAPI (exit 1) rather than Validation (exit 2): a full quota is not
something a different argument fixes, and exit 2 would imply it is.

The test asserts the hint is non-empty on purpose. The wording comes from the
shared APIHint table, so if quota_exceeded is ever dropped from there this fails
and says the code now needs its own wording, instead of silently shipping an
empty hint.
2026-08-24 16:49:33 +08:00
R0bynZhu 1f53f6e2f5 refactor(slides): assert dry-run parent_type instead of deriving it from a placeholder (#2461)
* refactor(slides): assert dry-run parent_type instead of deriving it from a placeholder

A wiki --presentation cannot be resolved during a dry-run: the real
presentation token only exists after a get_node call a preview must not
make, so parent_node shows a "<resolved_slides_token>" placeholder.
appendSlidesUploadDryRun derived parent_type from parent_node, which sent
that placeholder through the office-token check.

The value it produced was correct. A placeholder matches no office token
shape, so it fell through to slide_file, and slide_file is right here: a
wiki ref that reaches an upload is native by construction, because
resolvePresentationID rejects any wiki node whose obj_type is not
"slides" and an imported office deck sits in drive as a "file" node.

It was correct by accident, though, which left the preview hostage to the
placeholder's spelling and to every rule later added to
isOfficePresentation. Pass parent_type in explicitly instead, so
slidesDryRunParentType states the wiki case as a decision with its reason
recorded, and the placeholder is never classified.

No behaviour change: dry-run output is byte-identical for native tokens,
imported office tokens, legacy office prefixes, slides URLs, wiki URLs,
and +create, across +media-upload / +add-slide / +update-slide.

Tests pin the contract the refactor protects, including a wiki ref whose
node token is itself office-shaped -- a wiki node token and the deck token
it points at are different tokens in different namespaces, so classifying
ref.Token would be wrong for a wiki ref even though it is right for every
other kind. That is the regression this makes impossible.

* test(slides): use the fixture hostname for the wiki dry-run probes

domaincontract rejects "bytedance.larkoffice.com": it is in neither
allowlist, and a real tenant host does not belong in a fixture. Use
example.feishu.cn, already in fixture-domains.txt and the hostname the
rest of the slides wiki tests use.

The URL is only a parse fixture -- nothing resolves it -- so only the
hostname changes.
2026-08-24 15:55:37 +08:00
bubbmon233 b624948e48 Support repeated mail compose flags (#2271)
* feat: support repeated mail compose flags

* fix(mail): address review feedback for repeatable inline flags

Change-Type: ci-fix

* test(mail): cover inline validation and upload assertions

Change-Type: ci-fix

* test(mail): assert inline validation category

Change-Type: ci-fix

* fix(mail): preserve inline compatibility cases

* test(mail): strengthen inline compatibility coverage

* docs(mail): prefer one repeatable flag form

* docs(mail): keep skill references unchanged

* docs(mail): drop skill reference edits

* docs(mail): document repeatable mail flags consistently

* docs(mail): standardize quoted flag examples

* docs(mail): keep inline flag constraints in help

* fix(mail): validate template inline cids

* fix(mail): preserve recipient names and validate template cids

* fix(mail): support repeated recipient parsing

Normalize repeated recipient values through ParseMailboxList for every flag occurrence so legacy comma lists still split, quoted display-name commas stay intact, and Unicode display names remain raw before final header rendering.

Local check: gofmt -l shortcuts/mail/helpers.go shortcuts/mail/mail_repeatable_flags_test.go

* fix(mail): scope template inline update validation

---------

Co-authored-by: bubbmon233 <272202079+bubbmon233@users.noreply.github.com>
2026-08-24 15:38:30 +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