Address the owner review's blocking finding on PR #1998: the 128 KiB windowed
pretty scan could be bypassed — a match straddling a window boundary (via the
default instruction_override rule's unbounded \s+) matched the full text but
neither window, so block mode still wrote it to stdout.
- Scan the complete rendered text as one string (no windows): correct regex
semantics, and the []any window round-trip through normalize disappears.
- Add a no-truncate full-text scan path (extcs.ScanRequest.FullText, additive)
so content past the scanner's 128 KiB per-string cap is still scanned;
latency stays bounded by the existing 100 ms scan timeout. The structured
API-response scan path keeps its cap.
- Block mode now FAILS CLOSED when a scan cannot complete (timeout/error/panic):
nothing is written and a typed ContentSafetyError is returned. warn/off keep
failing open. This intentionally changes the §0.3 content-safety red line for
block mode; the legacy oracle golden is updated accordingly.
- Report an unknown --format before the --jq conflict (with the --format param),
and validate the framework --format on the --print-schema path too.
Regression tests: cross-window instruction_override payload is now blocked;
full-text scan catches matches beyond the per-string cap; regex boundary
semantics preserved; block-mode fail-closed on scan timeout/error.
Further review fixes:
- Normalize the framework-injected --format to its canonical lowercase and
write it back to the runtime context and the cobra flag, so shortcuts that
branch on the exact value (e.g. Format == "pretty") behave correctly for
mixed-case input like --format Pretty, which previously slipped past those
checks and produced empty output.
- Size the pretty rendered-text scan window to the content-safety scanner's
native 128 KiB per-string capacity (was 64 KiB) so the windowing is no more
restrictive than scanning the raw value; keep the 4 KiB overlap for matches
crossing a window boundary.
Address code-review findings on the emitter follow-ups:
- Large pretty output is scanned in overlapping 64 KiB windows instead of one
string, so content past the safety scanner's 128 KiB per-string cap is no
longer skipped — this was a content-safety bypass reintroduced by the
buffer-then-scan change.
- Feed the canonical Format.String() into the dry-run path (api/service/
shortcut) so a mixed-case --format Pretty still renders the plain-text
preview instead of falling through to the JSON envelope.
- The raw api/service unknown-format error lists only json/ndjson/table/csv,
not the shortcut-only pretty, so it no longer suggests a value those commands
reject.
apiPaginate and servicePaginate were near-identical; merge them into one shared
client.PaginateToOutput. The two call-site differences are injected: checkErr
(both pass APIClient.CheckResponse) and markErr (cmd/api passes errs.MarkRaw,
cmd/service passes nil). markErr wraps only the PaginateAll / StreamPages /
checkErr errors — never the WriteSuccessEnvelope return — preserving both
commands' exact stdout/stderr bytes and error semantics. Pure refactor.
When --jq is applied, the jq expression can filter the _content_safety_alert
field out of stdout, hiding the warning. Whether a stderr fallback warning was
written used to depend on a caller-set EmitOptions.JQSafetyWarning flag, so the
raw api/service paths warned but shortcut commands did not — the safety alert
was silently lost. Remove the flag and always write the stderr warning when jq
is applied and an alert exists.
When creating an event as a bot, resolve the bot's own open_id via
/bot/v3/info and add it to the attendee list, mirroring how a user is
auto-joined to their own events; warn and proceed without it if the
lookup fails. Also note in the +create skill doc that the user-search
API is user-only, so resolving a name to open_id needs --as user.
The pretty path scanned the structured `data` argument but rendered via an
opaque closure, so a renderer that printed content absent from `data` could
bypass content-safety in block mode. Render pretty output into a buffer, run
the safety scan on the actual rendered text, and only copy to stdout when it
passes — the bytes that reach stdout are now exactly what was scanned. Applies
to both Success's pretty path and StreamPage's pretty branch; json/table/csv/
ndjson are unchanged (they render from the scanned data directly).
An unknown --format now fails with a typed ValidationError instead of printing a
stderr warning and silently degrading to JSON, and unfulfillable combinations are
rejected at the flag boundary rather than dropped silently.
- Add FormatPretty to the Format enum and ParseFormatStrict, which returns a
typed validation error (param --format) for any unrecognized format.
- EmitOptions.Format / StreamOptions.Format / Emitter.streamFormat are now the
typed output.Format; the upstream .String() -> ParseFormat round-trip and the
Emitter's internal unknown-format fallback (printLegacyDataJSON) are removed.
- Strict parsing runs at the api, service, and shortcut boundaries, before the
dry-run branch so both dry-run and emit reject an unknown format. The strict
contract applies only to the framework-injected --format; a shortcut that
declares its own format flag (base +record-* markdown|json, mail +watch
json|data) keeps its own enum, validated by validateEnumFlags.
- The raw api/service commands reject --format pretty on the emit path (no
response pretty renderer) while preserving the dry-run plain-text preview;
the check runs before confirmation and client init.
- ValidateJqFlags classifies the format via ParseFormat so --jq's JSON-only
check is case-insensitive and single-sourced: --format JSON --jq no longer
mis-rejects, while any non-JSON value (including a shortcut's markdown/data)
still conflicts with --jq.
BREAKING CHANGE: an unknown --format value (e.g. a typo like `--format tabel`)
is now a typed validation error with a non-zero exit code instead of a stderr
warning plus JSON output. Scripts that relied on the unknown-format JSON
fallback must pass a valid format (json, ndjson, table, csv, or pretty).
* refactor: add output emitter contract and differential harness
Introduce a leaf Emitter in internal/output that composes the existing
output primitives (content-safety scan, envelope, jq, format rendering,
notice) behind a single command-scoped port. The emitter is unwired: no
production caller is migrated, so CLI output stays byte-for-byte unchanged.
A differential test harness drives the real legacy entry points
(RuntimeContext.Out/OutRaw/OutFormat/..., WriteSuccessEnvelope and the
pagination formatter) and asserts byte-identical stdout/stderr plus typed
errors, locking behavior before later slices migrate callers.
* refactor: tighten emitter API and cover pagination with real tests
- split Emitter.Success/PartialFailure and drop EmitOptions.OK so a
missing ok flag can no longer silently emit ok:false
- give StreamPage its own StreamOptions (format + pretty) instead of
reusing EmitOptions, making "jq needs aggregation" a compile-time fact
- pin the Emitter jq-error contract (returns error, writes no stderr);
the caller adapter re-emits the legacy stderr line on migration
- add in-package tests driving the real apiPaginate/servicePaginate over
a mock transport: multi-page aggregation, empty-result fallback,
MarkRaw handling, and the business-error raw-response red line
* test: use standard TestFactory harness for pagination tests
Replace the hand-rolled RoundTripper + APIClient construction in the
apiPaginate/servicePaginate tests with cmdutil.TestFactory and its
httpmock.Registry, and isolate LARKSUITE_CLI_CONFIG_DIR to t.TempDir(),
matching the repo's standard HTTP-mocked test convention. Assertions and
coverage (multi-page aggregation, empty-result fallback, MarkRaw, and the
business-error raw-response red line) are unchanged.
* refactor: route success output through the single Emitter port
Migrate the success-output surfaces onto internal/output's Emitter,
byte-for-byte identical (proven by frozen golden diffs and the real
paginate/HandleResponse tests):
- RuntimeContext.Out/OutRaw/OutFormat/OutFormatRaw/OutPartialFailure now
build an Emitter and call Success/PartialFailure; emit and outFormat are
removed. An adapter maps the returned error back to the legacy
outputErrOnce / jq-error stderr / exit-code behavior.
- WriteSuccessEnvelope degrades to a thin Emitter.Success delegate; its 8
callers are unchanged.
- apiPaginate/servicePaginate stream pages via Emitter.StreamPage; the
aggregate and business-error raw-response branches are untouched.
- HandleResponse routes its non-JSON structured-response branch through
Emitter.Success.
Frozen golden fixtures replace the runtime legacy oracles so the
differential harness cannot go self-referential after migration.
* fix: keep _notice on struct payloads in Emitter's unknown-format fallback
printLegacyDataJSON now normalizes via toGeneric first (matching FormatValue), so a struct / named-map payload retains its injected _notice on the unknown-format -> JSON fallback rather than dropping it silently. Add a regression test that fails against the pre-fix path.
* refactor: make the Emitter own write failures and stop mutating inputs
Route every Emitter stdout path through a render-to-buffer-then-copy helper so a marshal/render failure leaves stdout empty and surfaces a typed internal error (with cause), and a stdout write failure is propagated instead of silently swallowed. Leaf writers gain error-returning Write* cores; the legacy Print*/FormatValue wrappers keep their exact behavior for unmigrated callers.
- handleEmitterError now captures every error, not only the jq/safety branches; flip OutRaw's write-error test to assert propagation.
- Clone the map before injecting _notice so a caller's payload is never mutated and an existing _notice is never overwritten.
- Preserve jq's own typed error (validation/api) on a bad expression or runtime failure; only wrap genuine stdout write failures.
- Split tests: normative emitter_contract_test.go vs frozen emitter_legacy_compat_test.go (base SHA recorded, self-update env vars removed).
* fix: satisfy license-header and forbidigo lint on the emitter changes
- Move the base-SHA note below the copyright header in the renamed legacy-compat test so the license-header check sees a valid header at the top.
- Route the leaf wrappers' marshal/format stderr messages through a single legacyStderrf helper (one //nolint:forbidigo) instead of bare os.Stderr, preserving exact legacy behavior for unmigrated direct callers while passing forbidigo; drop the now-unused os imports.
* fix: stop legacy CSV wrappers reporting write failures to stderr
Align FormatAsCSV/FormatAsCSVPaginated and FormatValue/FormatPage's CSV branch with the other leaf wrappers: report only marshal failures, swallow write failures. Previously they emitted a 'csv write error' for the (empty) line and the JSON-fallback write failures that the pre-refactor code ignored, and mislabeled a JSON write failure as a CSV one. Failure-path only; success output is unchanged (golden double-diff still byte-for-byte).
* feat(apps): add design_html app type support and credential author identity
- Add design_html to appTypePolicies (same as modern_html: skip install/env-pull/skills-sync)
- Route +html-publish via policy (useTOSPublish) instead of hardcoded type check
- Parse commit_author_name/commit_author_email from +git-credential-init response
- Use server-provided author identity for repo-local git config, fallback to defaults
- Support meta_token as identifier in +get command
- Use envvars.AgentName() for source_agent in +create (reads LARKSUITE_CLI_AGENT_NAME)
- Add creative HTML guide reference skeleton and SKILL.md routing entry
- Update git-credential skill docs with new output fields
* fix(apps): unify html-publish to TOS path, add html to init skip policy
- Remove useTOSPublish policy field, html-publish always uses TOS upload
- Add html type to appTypePolicies (skip install/env-pull/skills-sync)
- Remove design_html from policies (not yet in use)
- Fix git credential dry-run test for new local_effects entry
* feat(apps): validate --app-id format to reject meta_token with resolution hint
* feat(apps): integrate creative-design skill and update skill docs
- Add creative-design skill under lark-apps/ (same level as references/)
- Update SKILL.md description with creative design trigger keywords
- Add creative design routing in development path selection table
- Add --path relative path guidance in html-publish reference
- Remove old creative-html-guide skeleton (replaced by creative-design)
* feat(apps): skip app sync for html/modern_html in +init
Add skipAppSync policy field; html and modern_html skip npx app sync
on non-empty repo path since static HTML sites don't need it.
* fix(apps): merge creative-design into html routing and add intent entry
- Merge static HTML and creative-design into one path selection row
- Add creative-design intent routing entry before html-publish
* docs(apps): add html local dev flow, unify publish link source
- Add html端到端 flow in local-dev.md (create → init → dev → release-create)
- Unify publish link source: html and full_stack both use +release-get
- Update SKILL.md routing and publish护栏 accordingly
* fix(apps): update html-publish dry-run and skill docs for TOS flow
- DryRun shows actual 3-step TOS flow (pre_release → TOS PUT → release-create)
- Skill docs: output is release_id, use +release-get to poll for online_url
- Remove references to legacy multipart upload and data.url
* TEMP: pin miaoda-cli alpha and add BOE header for testing
- Pin miaoda-cli to 0.1.24-alpha.fb2cf0a (revert to @latest before merge)
- Add x-tt-env=boe_aily_lark_cli header globally (remove before merge)
- html app-type uses --template design-html instead of --app-type (remove before merge)
* docs(apps): add creative mode link format and meta_token recognition
- Add creative mode (html) link format `https://{tenant}/page/{meta_token}` in publish护栏
- Note dev and publish URLs are the same for creative mode, unlike full_stack
- Add meta_token to app_id resolution with full link format in app_id获取
* docs(apps): route html apps through local-dev git pipeline by default
- Select dev path: html apps now default to local-dev pipeline instead of skipping local/cloud axis
- Intent routing: creative-design publishes via local-dev flow instead of +html-publish
- Remove +html-publish fallback from local-dev "when not to use" section
* docs(apps): generalize skill references to cover both html and full_stack
Remove full_stack-only wording from init, create, list, env-pull, and
release-create references since html apps now share the same local dev
and release flow.
* feat(apps): add meta_token to +get pretty output and dry-run description
* docs(apps): unify html as creative mode, fix routing and local-dev flow
- Remove "HTML" as separate dev path; html and full_stack both go through local-dev
- Intent routing: read local-dev before creative-design to establish git pipeline first
- Mark +html-publish as legacy, redirect to local-dev for creative mode
- Split html local-dev into 3 scenarios: first-time, iteration, pre-generated files
- git add . instead of selective add to capture all creative-design output files
* docs(apps): remove dev link from html-publish output, only return release-get online_url
* fix: add license header to deck-stage.js
* docs(apps): clarify dev link only for full_stack, creative mode shares dev/pub URL
* docs(apps): remove +html-publish from intent routing, description, and guardrails
All HTML apps now go through local-dev pipeline. +html-publish is deprecated.
* docs(apps): remove html-publish references from create/release-create/cloud-dev pages
html-publish is no longer the recommended path for HTML apps; all html
and full_stack apps now follow the same local-dev + release-create flow.
* fix(apps): address PR review feedback
- html-publish dry-run: register all 3 API calls (GET pre_release, PUT TOS, POST release-create) instead of hiding steps in metadata
- validateRealAppID: remove cli_ prefix check (not a valid app_id prefix)
- E2E: update git-credential dry-run to expect 4 local_effects
- E2E: update html-publish dry-run to expect GET pre_release
* fix(apps): address PR review — remove legacy multipart dead code, fix docs
- Delete html_publish_client.go and html_publish_client_test.go (legacy multipart)
- Remove runHTMLPublish, enrichHTMLPublishAPIError, buildHTMLPublishFailureHint
- Migrate tests from runHTMLPublish to prepareHTMLPublishTarball (same coverage)
- Remove cli_ prefix from validateRealAppID (not a valid app_id prefix)
- Fix html-publish.md error wording to match actual message
- Register all 3 TOS API calls in html-publish dry-run
- Update E2E tests for new dry-run contract
* fix(apps): correctly merge SKILL.md with main (role mgmt, auth wording, source boundary)
Rebuild SKILL.md from our branch version, then merge in main's additions:
- description: add HTML静态站点发布, 应用角色与成员管理, 应用角色/角色成员
- 身份与授权: use main's updated wording (no proactive re-login)
- intent routing: add +role-* row, +init refs 平台资源与应用源码边界
- 能力边界 → 平台资源与应用源码边界 (7 rules from main)
- 禁止预授权底线: add role ② and html-publish ③ clauses
* docs(apps): route legacy html-publish only for non-git html apps
* docs(apps): strengthen local-dev routing and git recovery guidance
fix:cherry-pick and resolve conflicts
* fix: gofmt apps_errors.go and apps_errors_test.go
* docs(apps): strengthen git credential recovery and add file-upload guidance
- Generalize git error recovery: any git operation failure triggers
+git-credential-init refresh, with environment analysis on failure
- Add resource file upload rule: use +file-upload instead of local
paths, base64 inlining, or git commits; files are app-scoped
* test(apps): strengthen html-publish dry-run assertions for TOS 3-step contract
* fix: 文件资源上传
* docs(apps): update creative-design skill content
* fix: re-add license header to deck-stage.js
* refactor(apps): merge system-prompt.md into SKILL.md for creative-design skill
Consolidate the thin SKILL.md wrapper and the full system-prompt.md
methodology into a single file, eliminating an unnecessary indirection.
Update references in claude.md and codex.md accordingly.
* chore: revert TEMP changes — miaoda-cli back to @latest, remove BOE header
* docs(apps): remove 可见范围 from 发布态护栏
创意模式的可见范围权限走 lark-drive 文档权限体系,而非妙搭应用
权限体系,当前的 +access-scope-set/get 无法正确管理创意模式应用
的可见范围。待文档协作支持妙搭能力后,再通过 lark-drive 域能力
引导修改。
TODO: 等文档协作支持妙搭能力后,在 skill 中加入使用文档域权限
能力修改创意模式可见范围的引导。
* docs(lark-apps): 在平台资源与应用源码边界添加路径规则,引导 agent 使用相对路径
`apps` 命令的 `--path`、`--file`、`--output` 只接受 cwd 下的相对路径,传绝对路径会报错。
* docs(lark-apps): 新增创意模式评论路由和裸 meta_token 识别引导
- 意图路由表新增创意模式应用评论,引导走 lark-drive 文档评论体系
- app_id 获取章节补充裸 meta_token 识别:非链接非 app_ 开头时尝试用 +get 解析
* refactor(apps): flatten creative-design built-in-skills into references
- Delete built-in-skills/ directory (9 nested sub-skill folders)
- Move media skill content to references/ as flat .md files
- Add assets/index.html React+Babel starter template
- Integrate publishing flow into creative-design SKILL.md
- Update harness reference docs (aily/claude/codex.md)
- Simplify lark-apps SKILL.md routing to point directly to creative-design
- Remove creative-design standalone .git directory
* refactor(apps): rename creative-design/SKILL.md to creative-design.md
Avoid being mistaken as an independent skill entry point.
Update all internal references (lark-apps routing table + 10 reference files).
* fix(apps): fail closed when queryAppType fails instead of falling back to full_stack
queryAppType now returns an error instead of silently returning "".
+init aborts if the app type cannot be determined, preventing wrong
scaffold type from being committed and pushed to the repository.
---------
Co-authored-by: zhangli <zhangli.268@bytedance.com>
* feat: clarify local trigger automation
* docs: refine trigger automation guidance
* docs: correct trigger release contracts
* docs: separate trigger enable and probe authorization
* docs: link the enable-only trigger path
* test: harden trigger authorization contracts
* docs: harden trigger disabled-state handling
* docs: harden trigger release state handling
* docs: verify a finished release before enable
* docs: split trigger start and test flows
* docs: fail closed after trigger probe errors
* docs(apps): fail closed on trigger test and release-create failures
Harden the automation guide's state handling. When testing an existing
online trigger, a formerly-disabled trigger is always restored to
disabled on probe success, failure, uncertain result, or early exit.
When +release-create itself errors or returns no release_id, treat it as
not published and restore the prior trigger state; when the result is
unknown, keep it disabled and verify via +release-list before deciding.
* docs(apps): flag online_url as creator-only before sharing
Point the local-dev and release-get release flows to the access-scope
step so a returned online_url is not presented as a shareable link
without the creator-only visibility caveat, matching the SKILL.md
visibility contract.
* docs(apps): drop out-of-scope SKILL.md edits from the trigger change
The local trigger automation work does not require touching the lark-apps
SKILL.md: its description already routed automation, so compressing it only
dropped routing keywords (access scope, monitoring metrics, trigger
subtypes) to satisfy a non-blocking length convention. Restore SKILL.md to
its prior state and remove the description/optional-output assertions that
only guarded those reverted edits. Release-output-as-optional correctness
remains covered by the release-get contract.
Clarify that smart notes (AI summary) and their verbatim docs are
auto-authorized to participants, while minutes carry the raw recording
and require explicit authorization. Rewrite the artifact-selection rule
to cover transcripts: use whichever exists when only one is present,
follow the user's explicit choice, and default to smart notes when both
exist and the user is unspecified.
* docs(base): disambiguate filter DSL and value shape to cut retry loops
Eval traces show the Base filter/view chain loses time to avoidable
error->lookup->retry loops:
- record/view --filter-json (tuple [[f,op,v]]) gets confused with
+data-query's object filters ({field_name,operator,value}) -> 800010701
- scalar fields (text/number) get array-wrapped values -> 800010507
- agents guess a field is select from its name, or guess enum values in
Chinese when stored values are English -> 0 hits then retry
Add a top-of-doc section to the tuple-DSL SSOT (value shape by field type,
check field type first, don't confuse with data-query, use real stored
values), a reciprocal warning in data-query, and two recovery rows in
SKILL.md. Flag-level details (--limit vs --page-size) are left to command
--help per the skill's stated design.
* refactor(base): fold filter guidance into existing sections, drop overfit examples
Address review feedback on the first pass:
- remove the added top-level '## 0 …先读' section — it duplicated §3 (per-type
value rules) and §7 (易错点), and its examples (状态=="Open", 工时>=3.5)
overfit the eval case and even clashed with §3's own 状态-as-select example.
- instead sharpen what already exists: §7 names the shared commands and the
data-query object shape to avoid; §6 gets one process rule (confirm field
type / real values first); all example-free and principle-based.
- revert the data-query.md note (wrong direction; the confusion is fixed at
the record/view tuple-DSL SSOT).
- slim the SKILL.md recovery rows to terse, message-keyed, reference-pointing
entries matching the table's style.
* docs(base): clarify full and partial update guidance
* docs(base): clarify partial update payload guidance
---------
Co-authored-by: wanglei.75 <wanglei.75@bytedance.com>
* fix: standardize CLI shortcut text in English
- translate Docs create and update help descriptions
- remove localized permission annotations
- replace Chinese examples and fallback text
- use English labels for Docs IM Markdown resources
- update regression tests for English output
* test: strengthen English output contracts
* ci: deduplicate PR runs and serialize live E2E
* ci: preserve live E2E cleanup on supersession
* ci: harden live E2E supersession check
* ci: gate live E2E on dry-run planning
Make the dry-run result a hard prerequisite for live E2E so skip-mode changes never acquire the repository-wide slot. This intentionally trades one full dry-run duration of live startup latency for lower contention on the exclusive queue.
* ci: bound dry-run E2E planning
The dry-run job is now a hard prerequisite for live E2E. Bound its
execution so a stalled planning job cannot delay a PR verdict for the
default six-hour job limit.
* test: tighten live E2E supersession contract
Register approval.instance.status_changed_v4 and approval.task.status_changed_v4 with custom flattened schemas and user-auth pre-consume subscription setup.
Handle approval subscription_type as optional multi-value pre-registration metadata: omitted values register both involved and managed relations, explicit values can be single, comma-separated, or JSON array, and consumers do not unsubscribe on exit.
Report partial approval subscription registration failures with registered and failed relation context while preserving the underlying typed error classification.
Document approval event output fields and subscription semantics, and refresh approval skill references from API metadata.