Commit Graph

536 Commits

Author SHA1 Message Date
huangmengxuan
0ff9c84cd8 feat(skill): add relative-path-only rule to lark-shared security section
lark-cli rejects absolute paths for --file, --output, --output-dir,
and @file with 'unsafe file path'. Document this in lark-shared so
agents know to use cwd-relative paths or stdin for data input.

Change-Id: I50cf801c2c5d0e3cbb98a76e1752d410518c8636
2026-06-03 13:59:06 +08:00
huangmengxuan
175c9f6ffc feat(skill): add CRITICAL instruction to lark-task, lark-contact, lark-slides
Same enforcement as the previous commit — require reading reference
docs (or -h) before calling shortcuts. These three skills use
non-standard section headers but still have shortcut tables with
reference links.

Change-Id: I5170cc763c15e3030c4117a36af36c9f1e94501e
2026-06-03 13:59:06 +08:00
huangmengxuan
575bcc407b feat(skill): add CRITICAL instruction to enforce reference reading before shortcut execution
Evaluation data shows AI call failure rate <1% when reference docs are
read vs ~29% when not. Add a CRITICAL line to the Shortcuts section of
14 SKILL.md files and the skill template, requiring agents to read the
linked reference doc (or run -h for commands without one) before
invoking any shortcut.

Change-Id: Ia4204518eb43a9f6c8295b95633ee5d9cf2f5352
2026-06-03 13:59:06 +08:00
MaxHuang22
24ce3ec151 feat: add --json flag as no-op alias for --format json (#1104)
* feat(api): add --json flag as no-op alias for --format json

* feat(service): add --json flag as no-op alias for --format json

* feat(shortcut): add --json flag as no-op alias for --format json

Skip registration when a custom --json flag already exists on the
command (e.g. base shortcuts use --json for body input).

Change-Id: If66236cadeea7fa81811061cce775deff51b92ce
2026-06-03 13:58:14 +08:00
dc-bytedance
2bbab4d851 feat: validate credentials after config init (#1151)
* refactor: extract FetchTAT sharing the TAT-rejection classifier

doResolveTAT minted the tenant access token inline. Extract the HTTP call
into FetchTAT(ctx, httpClient, brand, appID, appSecret) so callers that
already hold plaintext credentials — notably the post-config-init probe —
can validate them without a second keychain round-trip.

FetchTAT routes a non-zero TAT body code through the same
classifyTATResponseCode the credential layer already uses, so a rejection is
the canonical CategoryConfig / SubtypeInvalidClient (10003 / 10014) typed
error — identical to what every token-resolving command returns. Transport,
HTTP-status and JSON-parse failures stay raw (untyped) so callers can use
errs.IsTyped to separate a deterministic credential rejection from upstream
noise. doResolveTAT now delegates to FetchTAT; observable behavior unchanged.

* feat: validate credentials after config init

After config init saves the App ID / App Secret, fire a best-effort probe:
mint a tenant access token with the just-saved credentials, then POST the
application probe endpoint. When the credentials are deterministically
rejected, FetchTAT returns a typed errs.* error and runProbe propagates it,
so config init exits non-zero with the canonical ConfigError / invalid_client
envelope (the same one every other command shows for the same bad creds)
instead of letting the user discover the mistake on a later request.

Ambiguous failures (transport, HTTP non-200, JSON parse, timeout,
http-client init) come back untyped and are swallowed (errs.IsTyped is the
discriminator), so a valid configuration is never blocked by upstream noise.
The probe is wired into all four init paths and skipped when the user reused
an existing secret. The saved config is not rolled back on rejection: stdout
still records what was saved, stderr carries the typed error envelope.
2026-06-03 11:44:04 +08:00
evandance
98173ae5a9 feat(drive): emit typed error envelopes across the drive domain (#1205)
Drive-domain errors now leave the CLI as typed, machine-branchable
envelopes — a stable `type` plus `subtype` and named fields (param,
params, retryable, log_id, hint) — so scripts and AI agents can branch on
structure and act on a recovery hint instead of parsing prose.

Changes:
- Every error produced in the drive domain — validation, file I/O, and the
  failures returned from its Lark API calls — is emitted as a typed errs.*
  error; the exit code is derived from the error category. Drive's API calls
  now go through a shared typed classifier, so failures carry subtype,
  troubleshooter, a recovery hint, and the request's log_id whether the
  server returns it in the response body or the x-tt-logid header; an
  already-typed network/auth error is never downgraded into a generic API
  error.
- Known API conditions (resource conflict, cross-tenant, cross-brand, ...)
  carry a recovery hint keyed by their error class; a command can refine
  that hint with command-specific guidance.
- Batch partial failures (+push / +pull / +sync, where some items succeed
  and some fail) now report an honest ok:false multi-status result on
  stdout — the summary and every per-item outcome stay machine-readable —
  and exit non-zero, instead of a misleading ok:true success envelope.
- Duplicate rel_path conflicts report each colliding path as a structured
  params entry (RFC 7807 invalid-params style).
- Static guards lock the drive path so legacy error construction — direct
  envelopes or the auto-classifying API helpers — cannot be reintroduced,
  making drive the template for the remaining domains.

Output changes worth noting for consumers:
- Error envelopes now carry typed type/subtype and named fields; exit
  codes follow the error category (malformed or incomplete API responses
  are reported as internal errors rather than generic API errors).
- Batch partial failures (+push / +pull / +sync) emit an ok:false result
  envelope on stdout (summary + per-item items[]) and exit non-zero; the
  per-item results stay on stdout rather than in a stderr error envelope.

Errors surfaced through shared cross-domain helpers (scope precheck, media
import upload, metadata lookup, save-path resolution) are not yet typed;
they migrate with the shared layer in a follow-up change.
2026-06-03 10:27:15 +08:00
zhangheng023
c8e205eed2 fix: recover toUpdate skills empty fallback (#1233) 2026-06-02 23:26:16 +08:00
zgz2048
04932c2421 feat: add base record filter and sort json flags (#1228)
* feat: add base record filter and sort json flags

* test: cover base record query flags
v1.0.46
2026-06-02 22:02:56 +08:00
liangshuo-1
531d7265b5 chore(release): v1.0.46 (#1229) 2026-06-02 21:58:26 +08:00
91-enjoy
6d7f8ba442 feat: im card message format (#1218)
Interactive card messages (msg_type: interactive) can contain @user elements in their card
body. The json_attachment.at_users field stores resolved user info, but the user_id there is
the sender-side platform user_id — not the reading app's canonical open_id. When the backend
populates a mention_key on each at_users entry, it signals that the API-level mentions[]
array carries a more authoritative open_id and display name for the reading context. This PR adds
support for this two-level lookup: it threads the raw mentions[] array into the card converter,
indexes it by mention_key for O(1) access, and renders the canonical open_id + display name
whenever the link is resolvable. All existing fallback paths (no mention_key, nil mentions) are
preserved without behavioral change.

Change-Id: I00f846d76482adba315d07361c35909b71ca74c7
2026-06-02 20:42:59 +08:00
liangshuo-1
b216363e63 fix(cli): remove FLAGS section from root --help (#1226)
Follow-up to #1223. The hand-written FLAGS block in `lark-cli --help`
restated leaf-command flags at the root level — flags that are not
registered on the root command (they error "unknown flag" there). Even
trimmed to an illustrative example list, it duplicated information Cobra's
per-command `--help` already renders authoritatively, and any static list
in root help drifts from the real per-command flag sets over time.

Drop the section entirely: Cobra's per-command `Flags:` output is the
single source of truth. `USAGE:`/`EXAMPLES:` still show flags in context,
and the `Flags:` block at the bottom of root help lists the actual root
flags. Also removes the now-obsolete TestRootLong_FlagsSectionPointsToCommandHelp.
2026-06-02 20:31:45 +08:00
liangshuo-1
b0b163d0ef fix(cli): stop root --help listing per-command flags as global (#1223)
The hand-written FLAGS block in `lark-cli --help` listed --params, --data,
--as, --format, --page-all, --page-size, --page-limit, --page-delay, -o,
--jq, -q and --dry-run as if they were global flags. None are registered
on the root command — they all error "unknown flag" at the top level and
exist only on leaf commands (api, service). The block also contradicted
the Cobra-generated "Flags:" section rendered directly below it, which
shows only -h/--help, --profile, -v/--version.

Replace it with a short illustrative example list (common flags first) and
a pointer to `lark-cli <command> --help` for the full per-command set.
Root help stays a discovery signpost without claiming the flags are global
or restating defaults/descriptions that drift from the real flag sets.

Change-Id: Ia1cab889dd70b6b49a61dac468dedfd7fe39043f
2026-06-02 20:11:20 +08:00
91-enjoy
0aa9e96d18 feat: resolve markdown blank-line formatting inconsistency in post messages (#1216)
Simplifies the markdown-to-post rendering pipeline in the IM shortcut. The previous
implementation split markdown at blank-line boundaries into multiple post paragraphs,
using zero-width space (\u200B) sentinel characters to preserve visual spacing.
While well-intentioned, this approach introduced fragility around edge cases such as
blank lines inside fenced code blocks, messages with only blank lines, and interactions
with the heading-normalization pass. This change consolidates rendering back into a
single {"tag":"md"} segment, making the output more predictable, the code significantly
easier to follow, and the test surface easier to maintain.
Change-Id: Ic2870ecbcb31ae7d36121f120102f2ff964f5169
2026-06-02 17:49:45 +08:00
zgz2048
e57d97f341 docs: optimize base skill references (#1171) 2026-06-02 17:30:10 +08:00
MaxHuang22
57ba4fae61 feat: unconditionally inject --format flag for all shortcuts (#1156)
* feat: unconditionally inject --format flag for all shortcuts

Removes three HasFormat guards in runner.go so every shortcut
gets --format regardless of the Shortcut.HasFormat field value.
Shortcuts that already define a custom 'format' flag in Flags[]
are skipped to avoid redefinition panics (e.g. mail +triage, +watch).
HasFormat is retained in the struct but marked deprecated.

Change-Id: I5e8fe07e839d5aed4cefaf7d753dabbaee68fb6e

* test: isolate config dir in format-universal test

Change-Id: I3a59942aa8a6753cd949ca42f2a19a72f032ff55

* test: revert unnecessary config-dir isolation (mount-only test)

Change-Id: I0146e5a2f57f5419863bdeeaa1a662fd8f70bddf
2026-06-02 16:55:02 +08:00
YH-1600
925ae5ecd6 docs: add lark drive knowledge organization workflow (#1028)
Change-Id: I2343fcdf26ceefb898cc8d4faeae4b17384cfea8
2026-06-02 16:28:25 +08:00
liangshuo-1
4710a294f5 refactor(transport): own all HTTP transport in internal/transport, fix util layering inversion (#1213)
internal/util imported internal/proxyplugin (SharedTransport, FallbackTransport,
NewHTTPClient, and WarnIfProxied via proxyPluginStatus), so a foundational util
package depended up into a feature package, pulling binding/core/vfs into the
transitive cone of every util importer.

Move internal/proxyplugin -> internal/transport and make it the single owner of
outbound transport: fold the two SharedTransport functions into one Shared()
(proxy-plugin override -> LARK_CLI_NO_PROXY -> http.DefaultTransport), and move
Fallback/NewHTTPClient/WarnIfProxied/DetectProxyEnv/noProxyTransport out of the
now-deleted internal/util/proxy.go into the new package. The proxy-plugin probe
is demoted to a private pluginTransport(); the duplicate redactProxyURL collapses
to one. internal/util keeps no proxy code and is a leaf again.

Re-point all consumers (registry, doctor, config, auth, cmdutil, update) to
internal/transport. Behavior-preserving: package move + symbol rename + dedup.
Two new tests lock the fail-closed contract (plugin overrides NO_PROXY; malformed
config never falls through to direct egress).
2026-06-02 16:10:35 +08:00
JackZhao10086
bc8e9bd6ef feat: increase agent trace max length to 1024 (#1211) 2026-06-02 11:08:53 +08:00
JackZhao10086
f65712cacf feat: add proxy plugin mode for CLI HTTP transport (#1181)
* feat: add security plugin for proxy

* docs: remove outdated proxyplugin README files

* refactor(proxyplugin): tighten proxy URL validation and add security checks

* refactor(proxyplugin): cache blocked transport and clean up error handling

* fix(proxyplugin): fix CR issues for Security hardening

---------

Co-authored-by: AlbertSun <sunxingjian@bytedance.com>
2026-06-02 10:57:02 +08:00
zhangjun-bytedance
915cc623cc feat(vc): inline transcript from artifacts API and add keywords (#1206) 2026-06-02 10:36:41 +08:00
liangshuo-1
3bfb80951d chore(release): v1.0.45 (#1207) v1.0.45 2026-06-01 22:08:11 +08:00
hugang-lark
639259fbfd fix: add vc-domain-boundaries and enrich vc +notes (#1172) 2026-06-01 19:03:55 +08:00
JackZhao10086
0bdd7de807 refactor(auth): update login hint and split-flow docs (#1201) 2026-06-01 16:47:18 +08:00
evandance
99e314fe0b feat(errs): typed envelope contract for auth-domain errors (#1135)
Every failure on the authentication, authorization, and configuration
path now surfaces as a typed structured error instead of an ad-hoc
envelope. Users and scripts that consume CLI output get:

  - a fixed nine-category taxonomy on the wire, each mapped to a
    stable shell exit code (authentication/authorization/config = 3,
    network = 4, internal = 5, policy = 6, confirmation = 10)
  - identity-aware detail fields (missing_scopes, requested_scopes,
    granted_scopes, console_url, log_id, retryable, hint) carried
    uniformly on the envelope
  - a single canonical policy envelope at exit 6; the legacy
    auth_error carve-out is retired
  - per-subtype canonical message + hint that preserves Lark's
    diagnostic phrasing and routes recovery to the right actor:
    app developer (app_scope_not_applied), user (missing_scope,
    token_scope_insufficient, user_unauthorized), or tenant admin
    (app_unavailable, app_disabled)
  - wrong app credentials classify as config/invalid_client whether
    surfaced by the Open API endpoint (99991543) or the tenant
    access-token mint endpoint (10003 / 10014), instead of
    collapsing to a transport error or api/unknown
  - local shortcut scope preflight emits the same
    authorization/missing_scope envelope (identity + deterministic
    missing-scope set) used by the post-call permission path, so AI
    consumers read the same structured shape from precheck and from
    server-returned permission denial
  - streaming download/upload failures keep the same network subtype
    split (timeout / TLS / DNS / transport) as the non-stream path
    instead of collapsing every cause to a generic transport failure
  - console_url is carried only on the bot-perspective
    app_scope_not_applied envelope (where the recovery action is
    "developer applies the scope at the developer console"); the
    user-perspective missing_scope envelope drops the field, since
    the only actionable user recovery is `lark-cli auth login --scope`
    and pointing an end user at a console they cannot modify is
    misleading
  - bind workflows (Hermes / OpenClaw / lark-channel) flatten dynamic
    Type tags to wire 'config' with the original module name kept
    as a metric label

All 10 typed errors are cause-bearing, nil-safe on .Error() and
.Unwrap(), and defensively clone slice setter inputs. Four lint
rules (CheckNilSafeError / CheckBuilderImmutable / CheckUnwrapSymmetry
/ CheckBuildAPIErrorArms) lock these invariants on migrated paths.
2026-05-30 19:08:41 +08:00
sang-neo03
50b3f0a2af feat(platform): support multiple policy rules per plugin (#1182)
* feat(platform): support multiple policy rules per plugin

Extend the command policy framework from single-Rule to multi-Rule
semantics. A plugin (or policy.yml) may now contribute several scoped
Rules; the engine combines them with OR -- a command is allowed when it
satisfies every axis of at least one rule. This lets one integration
apply different risk ceilings and identity restrictions to different
command groups.

The cross-plugin fail-closed boundary is preserved: two distinct plugins
both calling Restrict still aborts startup (multiple_restrict_plugins).
Single-Rule behaviour is fully backward compatible -- the rejection
reason_code / rule_name / envelope shape are byte-for-byte unchanged;
multi-rule rejection surfaces the aggregate reason_code no_matching_rule.

- engine: New keeps single-rule compat, add NewSet for OR over rules
- resolver: dedupe by owner (one plugin may contribute many rules),
  return []*Rule; yaml gains a top-level rules: list
- registrar/builder/staging: Restrict may be called more than once;
  retire the double_restrict error
- config policy show / config plugins show: emit a rules array
- inventory: PluginEntry.Rules is now a slice (fixes last-rule-wins
  overwrite when a plugin contributes multiple rules)

* fix(platform): clone rules in Builder.Restrict and inventory snapshot

Address review feedback. Builder.Restrict stored the caller's *Rule
directly, so reusing and mutating one Rule object across multiple
Restrict calls collapsed entries to the last mutation; clone the rule and
its slices on append, mirroring the staging registrar.

BuildInventory likewise reused the source Allow/Deny/Identities slices;
copy them when building the RuleView snapshot instead of relying on
cloneInventory downstream.

Add a regression test: reusing and mutating one Rule across two Restrict
calls now yields two independent rules.

* fix(platform): skip yaml when a plugin owns policy; reject empty rules list

Two policy-config robustness fixes from review:

- A malformed ~/.lark-cli/policy.yml could abort a plugin-governed
  binary. applyUserPolicyPruning read yaml before resolving, and
  build.go fail-closes on any policy error when a plugin is present.
  Plugin rules shadow yaml anyway, so skip reading yaml entirely when a
  plugin contributed rules -- an unrelated broken file on the user's
  machine can no longer lock the CLI.

- A present-but-empty "rules: []" collapsed to a single all-zero Rule
  that allows every annotated command ("looks like policy, enforces
  almost nothing"). yaml.Parse now distinguishes absent from
  present-but-empty (Rules is a pointer) and rejects the empty list.

Add regression tests for both.
2026-05-30 17:05:33 +08:00
syh-cpdsss
b1ecf2d0f9 fix: whiteboard skill (#1180)
Change-Id: If62f9446dea1273a422567394a9e7d91b40be16e
2026-05-30 10:35:01 +08:00
liangshuo-1
d126ea2f92 chore(release): v1.0.44 (#1176) v1.0.44 2026-05-29 19:43:31 +08:00
max
1ba107da2e fix(vc): correct --minute-token to --minute-tokens in recording reference (#1170)
Fix 3 occurrences of --minute-token (singular) to --minute-tokens
(plural) in lark-vc-recording.md to match the actual CLI flag
definition in minutes_download.go.
2026-05-29 16:42:54 +08:00
yballul-bytedance
0e6274d947 feat(base): add dashboard block data shortcut and workflow docs (#1067)
Change-Id: I52c471886bdb2d4b7be021ce86c34bbb78385017
2026-05-29 16:35:32 +08:00
lhfer
e18ea9a2e8 fix(im): correct 64-bit MP4 box size handling to prevent panic on crafted media (#1165)
The size==1 (64-bit "largesize") branch of all three MP4 box walkers
(findMP4Box, readMp4DurationBytes, readMp4Duration) set boxEnd to the raw
largesize instead of offset+largesize — even though the 32-bit branch right
below correctly uses offset+size. Two consequences:

- Correctness: for any MP4 that carries a 64-bit box size at a non-zero
  offset, the box walk is computed from the wrong end, so the moov/mvhd
  lookup is truncated and the media duration is silently lost.

- Robustness/security (CWE-190): the unguarded uint64->int(64) conversion of
  a largesize with the high bit set yields a negative boxEnd. The in-memory
  walkers then assign it to offset and feed it back as a slice index
  (data[offset:]), panicking with "slice bounds out of range" and crashing
  the CLI on a crafted or corrupt MP4. This is reachable via URL-sourced IM
  media, whose bytes the caller does not control.

Fix: compute boxEnd as offset+largesize (matching the 32-bit branch) and
reject largesize values smaller than the 16-byte header or larger than the
remaining input. Malformed media now honours the parsers' best-effort
contract by returning 0/-1 instead of panicking, and the bounds guarantee
the conversion can no longer overflow.

Add regression tests covering both the overflow (must not panic) and a
64-bit box at a non-zero offset (must walk correctly).
2026-05-29 16:04:21 +08:00
shifengjuan-dev
365e0a2880 feat(im/chat-list): support --types flag for listing p2p single chats (#1077)
Add a new --types flag (string_slice; values from {group, p2p}) to
+chat-list, backed by the new GET /open-apis/im/v1/chats `types` query
parameter. Accepts CSV (--types group,p2p) and repeated-flag forms
(--types group --types p2p).

Defaults to groups-only (backward compatible). Under user identity,
p2p single chats appear with chat_mode="p2p" plus p2p_target_type /
p2p_target_id fields. Under bot identity:

  - --types=p2p alone is rejected at validation
  - --types=p2p,group is silently downgraded to types=group (no runtime
    notice; skill docs document this contract)

Updates Shortcut.Description, lark-im SKILL.md (frontmatter trigger
+ shortcut table row), and the chat-list reference doc with command
examples, the new parameter, output field documentation, and a
dedicated "Bot identity and p2p" section.

Change-Id: I637ce23b3c6ce4ec350f0ac26dbac8120761bb71
2026-05-29 15:29:37 +08:00
syh-cpdsss
0a2c3202cb fix: whiteboard skill (#1166)
Change-Id: Ib1da37c1520d7697eaee7146555185ffbc749217
2026-05-29 14:23:11 +08:00
JackZhao10086
176d452cc1 feat: add agent header support (#1158)
* feat: add agent header support
2026-05-29 13:44:15 +08:00
liangshuo-1
a2cc5e124e fix(install): detect curl version before using --ssl-revoke-best-effort (#1124)
* fix(install): detect curl version before using --ssl-revoke-best-effort

(cherry picked from commit da14737702)

* test(install): cover curl version gate and refactor for testability

Extract the version comparison out of curlSupportsSslRevokeBestEffort()
into a pure isCurlVersionSupported(output), so the >= 7.70.0 logic is unit
testable without spawning curl. Add cases for 7.55.1 / 7.69.0 / 7.70.0 /
8.x plus the unparseable and libcurl-token edge cases (the regex must read
the leading "curl X.Y.Z", not the trailing "libcurl/X.Y.Z").

Memoize the `curl --version` probe: curl's version is invariant for the
install's lifetime while download() runs once per mirror URL, so probe at
most once instead of re-spawning curl on every attempt.

---------

Co-authored-by: EllienTang <146210093+Ellien-Tang@users.noreply.github.com>
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
2026-05-28 22:51:16 +08:00
liangshuo-1
a2dde84158 chore(release): v1.0.43 (#1161) v1.0.43 2026-05-28 21:46:02 +08:00
hugang-lark
21998b9ca8 feat: support note generated event (#1159) 2026-05-28 21:10:14 +08:00
liangshuo-1
ce2abff8ae fix(config): propagate Lang across credential boundary; respect CurrentApp in priorLang (#1157)
Two issues caught in review of #1132 that the existing tests missed because
they constructed RuntimeContext/CliConfig directly, bypassing the credential
edge where the bug lives.

P1 — Lang dropped at credential boundary
  credential.Account had no Lang field, so AccountFromCliConfig and
  ToCliConfig silently dropped cfg.Lang. The production Factory builds
  CliConfig via acct.ToCliConfig() (factory_default.go Phase 3), which
  meant RuntimeContext.Lang() always returned "" in production and
  shortcuts/mail/mail_signature.go always fell back to zh_cn — defeating
  the whole point of persisting --lang.

  Fix: add Lang i18n.Lang to Account and copy it in both directions.

  Regression test: TestFullChain_LangSurvivesProductionPath walks the
  real path (SaveMultiAppConfig -> DefaultAccountProvider.ResolveAccount
  -> ToCliConfig) and asserts Lang survives, so any future field added
  to CliConfig forces the same audit.

P2 — priorLang ignored CurrentApp in multi-profile workspaces
  priorLang scanned all Apps and returned the first non-empty Lang. If a
  user had multiple profiles and the active one disagreed with Apps[0],
  a re-bind without --lang would silently inherit the wrong profile's
  preference.

  Fix: read multi.CurrentAppConfig("").Lang instead.

  Regression tests cover CurrentApp wins over Apps[0], single-app
  fallback, and malformed bytes.

Change-Id: If7a276605f84f398cec329c2c942b471b4c32749
2026-05-28 20:53:15 +08:00
sammi-bytedance
893555a1b1 perf(im): parallelize reactions, thread_replies, and merge_forward fetches (#1146)
Follow-up to #1095. The reactions auto-enrichment shipped, but on busy chats the strictly-serial per-resource fetches in EnrichReactions, ExpandThreadReplies, and merge_forward expansion stretched the command's wall time above 14s — enough that wrapper agents (30–60s wall-clock budgets) saw timeouts even though the CLI itself never errored. This PR parallelizes all three with the same bounded-concurrency pattern, batches the follow-up contact-API sender resolution so it doesn't fan back out into a serial stall, and fixes two correctness bugs that surfaced during review. Scoped to convert_lib/{reactions,thread,merge,content_convert}.go + tests + the 4 shortcut Execute hooks + the reference doc.

Change-Id: I0206d10ad204382170bd42aec67f82578923736e
2026-05-28 19:25:11 +08:00
YangJunzhou-01
8d496b8a48 docs: update IM skill urgent APIs (#1153)
Add support for IM urgent messages.
Change-Id: Ide2416af6d3d47d35cfd4c60b31e2137889081c6
2026-05-28 19:22:41 +08:00
HanShaoshuai-k
01fe71d7db fix(config): allow lark-channel bind source override (#1154)
Change-Id: I406ea13e372e6bdd5f3d9d6210b04ebdf0354182
2026-05-28 18:56:36 +08:00
luozhixiong01
3b770558e5 feat: decouple --lang preference from TUI display language (#1132) 2026-05-28 18:55:40 +08:00
Kyalpha
3cd84fca90 test(drive): drop redundant CONFIG_DIR isolation in inspect Execute tests (#1121)
The six TestDriveInspectExecute_* tests set
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) but build the CLI via
cmdutil.TestFactory(t, cfg), which provides an in-memory config closure
(func() (*core.CliConfig, error) { return config, nil }) and never reads the
filesystem. Per the repo learning from PR #343, this env var should only be
set for tests exercising the real NewDefault() factory path. None of these
tests use NewDefault(), so the calls are dead and removed.

No behavior change; all TestDriveInspect* tests still pass.

Co-authored-by: kyalpha313 <kyalpha313@users.noreply.github.com>
2026-05-28 17:32:31 +08:00
YangJunzhou-01
c2e737434c fix(im): clarify messages-send dry-run chat membership (#1150)
clarify messages-send dry-run chat membership
2026-05-28 16:39:00 +08:00
zgz2048
b91f6a23f3 fix: include log_id in base attachment media errors (#1133) 2026-05-28 11:54:18 +08:00
bubbmon233
bbef3cbfb1 feat(mail): HTML lint library + Larksuite-native autofix + lark-mail … (#1019)
* feat(mail): HTML lint library + Larksuite-native autofix + lark-mail skill

为 lark-cli mail 域写信链路引入 HTML lint 能力,提升邮件 HTML 的兼容性、
安全性与 Larksuite-native 格式适配。

lint 库(shortcuts/mail/lint/):
- 四档分类:pass / native-autofix / warn-autofix / error-strip
- 安全规则覆盖 script / iframe / on* 事件处理器 / javascript: 及其它
  危险 URL scheme 等 XSS 向量,未知 scheme 一律删除并归 error
- Larksuite-native 格式自动修复:双层 div 段落、原生多级列表结构、
  灰边引用、Larksuite 蓝链接
- cleaned_html 输出确定性稳定(位置索引派生 data-ol-id),便于
  golden-file 测试与缓存

+lint-html 独立预检 shortcut:
- 只读、不调 API、不建草稿,供 AI / 用户 / CI 在写信前预览 lint 结果

写入路径内置 lint(6 个 compose shortcut):
- +send / +draft-create / +draft-edit / +reply / +reply-all / +forward
  在 emlbuilder 之前强制 lint 净化 HTML
- 默认 envelope 对 lint 改动透明(无 lint 字段),保持小巧供 AI 消费;
  --show-lint-details 显式取证返回 lint_applied[] / original_blocked[]
- --body-file 支持从文件读取 body(32MB 上限),与 --body 互斥

预制 HTML 邮件模板(skills/lark-mail/assets/templates/):
- 资讯周报 / 个人周报 / 团队周报 / 调研报告 / 求职简历 5 套
- 按 Larksuite mail-editor 原生格式编写,含正确的多级列表嵌套结构

lark-mail skill 文档:
- references/lark-mail-html.md:邮件 HTML 写法指南(24 个格式 section
  + 颜色调色盘 + URL scheme + 官方模板套用流程)
- references/lark-mail-lint-html.md:+lint-html 用法
- SKILL.md 顶部 CRITICAL 引导

* fix(mail): remove unused readAttr func and apply gofmt

Drop the unused `readAttr` helper in shortcuts/mail/lint/linter.go
that was flagged by golangci-lint (unused linter). Apply gofmt to
linter.go and rules.go which had minor formatting issues.

* fix(mail): address compose lint and guidance
2026-05-27 22:23:32 +08:00
liangshuo-1
cdae999541 chore(release): v1.0.42 (#1137)
Change-Id: Id4478295cf364a01b712b7ddcd4a6cbdc264e28d
v1.0.42
2026-05-27 20:52:24 +08:00
raistlin042
36ff632a13 fix(apps): update miaoda scopes after platform consolidation (#1127)
妙搭/spark consolidated the apps domain onto spark:app:read / spark:app:write.
The standalone spark:app:publish and spark:app.access_scope:* scopes are retired.

- +html-publish:      spark:app:publish            -> spark:app:write
- +access-scope-get:  spark:app.access_scope:read  -> spark:app:read
- +access-scope-set:  spark:app.access_scope:write -> spark:app:write

Verified against the official docs for upload_html_code_and_release,
get_app_visibility and update_app_visibility. +create/+update/+list were
already correct (spark:app:write / spark:app:read).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:51:59 +08:00
xukuncx
ab94ee9f54 feat(mail): add +draft-send shortcut for batch draft sending (#1017)
Add `lark-cli mail +draft-send` shortcut that takes one or more existing
draft IDs and sends each via POST /drafts/:draft_id/send sequentially.
Per-draft failures are isolated and aggregated into a structured output;
fatal failures (auth, permission, network, mailbox quota) abort the
entire batch immediately while recoverable failures honor --stop-on-error.

Also extend internal/output with six mail-send-specific errno constants
(LarkErrMailboxNotFound=4013, LarkErrMailSendQuota{User,UserExt,TenantExt},
LarkErrMailQuota, LarkErrTenantStorageLimit) consumed by isFatalSendErr.

Risk is "high-risk-write" so the framework's --yes gate applies; the
shortcut declares only the minimal mail:user_mailbox.message:send scope
to avoid asking users for permissions it does not need.
2026-05-27 18:12:41 +08:00
sammi-bytedance
30327abacb feat(im): enrich messages with reactions + output update_time (#1095)
- Pull messages now auto-call im.reactions.batch_query and attach a
  reactions block (counts + details) to each message. Stops AI from
  misjudging "user already reacted" as "no response yet" and
  re-sending duplicate reactions. Server caps queries[] at 20 per
  call, so messages are split into batches of size <= 20.
- Edited messages additionally surface update_time. The server echoes
  update_time == create_time for unedited messages too, so the field
  is only emitted when updated == true; otherwise every message
  output would look "edited". The value is read via an explicit
  string assertion + TrimSpace so empty strings are filtered properly
  (the previous `v != ""` was a no-op for non-string types).
- All four message-pulling shortcuts (+messages-mget,
  +chat-messages-list, +messages-search, +threads-messages-list) get
  a --no-reactions opt-out flag for callers that want to skip the
  extra round-trip.
- Each shortcut declares im:message.reactions:read on its
  UserScopes/BotScopes (or Scopes for the user-only search command) so
  the auth flow covers the new dependency.
- Each shortcut's --dry-run output now lists the
  reactions/batch_query call (or omits it when --no-reactions is set),
  so callers can audit the full set of API calls before execution.
- Warnings go through runtime.IO().ErrOut (forbidigo lint requires
  IOStreams over os.Stderr in shortcut code).
- Duplicate message_id inputs (e.g. mget --message-ids om_a,om_a)
  attach the reactions block to every entry while still querying the
  API only once per distinct id.
- EnrichReactions walks msg["thread_replies"] recursively, and mget/
  chat-messages-list call it after ExpandThreadReplies, so replies
  receive reactions in the same batched call as their parent message.
- When the batch_query call fails or returns per-message failures,
  the affected messages get reactions_error=true (mirroring the
  thread_replies_error flag from thread.go) so consumers can
  distinguish "fetch failed" from "no reactions exist" by reading
  stdout alone, without depending on the stderr warning channel.
- lark-im skill docs: the default-enrichment contract lives in a
  standalone references/lark-im-message-enrichment.md so the generated
  SKILL.md can't strand it on regeneration. The four read references
  and the raw reactions API reference link to it, and the template
  source skill-template/domains/im.md carries a durable pointer.

Change-Id: Ia9ea74b11945644262bb25c6503fb9b2003c6c98
2026-05-27 18:06:36 +08:00
sang-neo03
70081f62b1 feat: use description and command in affordance example schema (#1126)
Affordance examples previously carried a title plus a structured input
object mirroring the inputSchema. Replace that with a description plus a
command string holding a ready-to-run lark-cli invocation, which is what
an AI agent driving the CLI actually consumes.

No affordance data exists in the registry yet, so this only reshapes the
consuming AffordanceCase type and its tests; the data pipeline
(registry-config.yaml -> gen-registry.py -> meta_data.json) forwards the
new keys verbatim.
2026-05-27 16:08:21 +08:00