Two review follow-ups on the agent command tree.
Artifact download (data integrity): fetchArtifactURL now reads one byte past
maxArtifactBytes and refuses a body over the cap with a typed error, instead of
letting io.LimitReader silently truncate a >256 MiB artifact to a corrupt,
partial file that reported success (exit 0). The LimitEnforced test is inverted
to assert the rejection.
Capabilities: the single multi_turn card bit is replaced by three independent
capabilities — context_list / context_get / context_delete — each derived from
its own wired hook (ListContexts / GetContext / DeleteContext). One bit could
not honestly represent three separately-deliverable verbs: a provider wiring
ListContexts but not DeleteContext advertised multi_turn=true while `context
delete` failed claiming multi_turn=false. Each context verb now gates on and
reports its own capability. Card schema, capability matrix, the three context
gates, tests, and the lark-agent skill docs are updated in lockstep.
Review follow-up on the agent command tree. A batch of low-risk hardening
fixes; no behavior change for the shipped example provider.
- Terminal-injection: pretty/TSV renderers now sanitize the agent-controlled
State / UpdatedAt / CreatedAt fields (kvValue on pretty rows, stripANSI on
TSV), matching the id/summary/title fields — a malicious provider can no
longer inject CSI/OSC escapes via a forged state or timestamp.
- Nil-safety: `task get --watch` and artifact download return a typed
invalid_response error when a provider hook yields (nil, nil) (a legitimate
Call[*T] result on an empty "data") instead of panicking; an artifact with
neither inline bytes nor a URL no longer writes a 0-byte file.
- Array convention: task/context/agent list normalize a nil slice to [] so an
empty list serializes as [] not null, matching Card.Parameters.
- Error hint: unknown-agent errors keep LookupSpec's scheme-scoped
`agent list <scheme>` hint instead of being flattened to the generic one.
- agent list <scheme> (online path) sets the resolved identity on its
envelope, consistent with the other leaves.
- Comment/doc drift: drop references to the removed Deps probe / Discoverer /
ProviderInfo / resolveProvider symbols; rename Supports(cap) -> capKey.
- Tests: cross-agent isolation in the example store, empty-list [] contract,
State/timestamp sanitization regression, and a real stdout assertion for
context list --jq.
Runtime.CallAPI/CallMultipart now return the response "data" object as
json.RawMessage instead of map[string]any, and provider hooks decode it through
the generic Call[T] / CallUpload[T] helpers: a hook declares the response struct
it expects and the framework unmarshals + classifies errors, instead of poking
at a map. Call[map[string]any] remains available for genuinely dynamic shapes; a
response with no "data" (e.g. a pure write) yields T's zero value and a nil error.
decodeData centralizes the unmarshal + invalid_response classification. cmdRuntime
re-encodes the unwrapped "data" sub-object to raw JSON after CheckResponse. Test
doubles (cmd/agent + example fakeRuntime, card_test fakeRT) updated to the raw
signature; the runtime tests keep the raw-data assertion and add a Call[T]
decode assertion.
The agent scope preflight now mirrors cmd/event's scopeRemediationHint for both
identities, replacing the bespoke replacement-era hint:
- user: the re-auth hint lists ONLY the missing scopes (the open platform
authorizes incrementally, so re-login with just the missing keeps existing
grants — no merge needed). Uses the canonical repo-wide `auth login --scope`
phrasing instead of a one-off Chinese string.
- bot: previously skipped entirely; now checks the app's published TenantScopes
(fetched best-effort via appmeta.FetchCurrentPublished behind a swappable seam;
a fetch failure downgrades to a no-op). A missing scope reports the
developer-console re-publish remediation (the event-style scan-to-enable deep
link lives in cmd/event and is not duplicated here).
preflightScopesForRef keeps its signature (no call-site changes); bot fetch uses
a bounded background context so no ctx param is threaded. Tests cover the
incremental user hint, the bot missing/present/no-scopes branches, and the bot
seam wiring.
`task list` / `context get` carried only {task_id, context_id, state,
is_terminal} — too thin for a caller (especially an AI) to tell which task
to resume without a `task get` per item. Enrich the summary surface so the
list is self-sufficient for triage, aligning with A2A's Task
(status.timestamp + last message).
- TaskSummary: add updated_at + summary (last agent message, or the pending
prompt for input_required; rune-truncated)
- AgentTask: add created_at + updated_at
- ContextSummary: add updated_at + task_count + awaiting_input
- ContextDetail: drop the embedded tasks[]; add updated_at, task_count,
awaiting_input, and active_task (the latest-updated task). Full task
enumeration stays in `agent task list --context-id`.
- task list / context list sort by updated_at desc
- route task list / context list / context get through the content-safety
scan (they now carry untrusted agent text); ANSI-strip + flatten summary
in pretty/TSV
- example provider fills the new fields; tests + lark-agent skill docs updated
Supersede the struct-of-func-fields Provider with a runtime-injection model that
stops leaking framework plumbing to integrators (the Deps{Client, As} an
onboarding author previously had to receive and destructure — the mock didn't
even use it).
Framework (internal/agent):
- Provider is now one declarative value per business domain (scheme): metadata +
a Catalog []AgentSpec (offline-enumerable) XOR an Instance *AgentSpec template,
plus an optional online ListAgents hook. AgentSpec carries per-agent card
metadata, the FileInput/InputRequired flags, and the verb hooks.
- Every hook receives an identity-opaque agent.Runtime (AgentID/IsBot/CallAPI/
CallMultipart) instead of a raw client; the concrete cmdRuntime lives in
cmd/agent (like event's consumeRuntime), so internal/agent no longer depends on
internal/client and the Deps struct is gone. CallMultipart is the centralized,
SafeInputPath-validated file-upload seam that makes file_input deliverable.
- Register takes a Provider (pure-struct validation, fail-fast). LookupSpec
resolves ref→spec fully offline. Capability = wired-hook presence
(DeriveCapabilities), card synthesized by BuildCard (rt=nil ⇒ offline caps +
static metadata; rt!=nil ⇒ best-effort Describe enrichment). Deleted catalog.go,
Deps, Factory, Resolve, NewCard, the zero-Deps probe, and the Discoverer interface.
Command layer (cmd/agent):
- resolveSpec (offline: identity + LookupSpec) then capability nil-gate BEFORE
runtimeFor, so an unsupported verb returns unsupported_capability (exit 2)
before any client is built — uniformly across list/context/artifact (previously
only cancel gated offline). Then runtimeFor + scope preflight + spec.<hook>(rt).
- agent list: catalog enumerates offline (ListCatalog); instance enumerates via
the online ListAgents hook, else reports not-enumerable.
Providers: example is now a declarative Provider() value + plain hooks reading
rt.AgentID() (echo minimal / reporter full differ only by wired fields). Explicit
aggregation in agent/register.go.
Adds cmd/agent runtime tests (CallAPI unwrap/error/transport, IsBot, CallMultipart
SafeInputPath), negative capability-gate tests (send --file / task list / context
get / artifact download), the https-only artifact check, and BuildCard's dynamic
Describe path.
Replace the fat 9-method Provider interface with a struct of function fields,
mirroring the events KeyDefinition / shortcuts Shortcut convention: a provider
wires only the capabilities it supports and leaves the rest nil. This removes the
two things every integrator previously had to keep in sync by hand — the
Capabilities bool matrix and the per-method ErrUnsupported returns.
- Provider is now a struct: core Send/GetTask (mandatory, asserted at Register)
plus optional func fields (ListTasks/CancelTask/context trio/DownloadArtifact/
ListAgents) whose presence == support, plus FileInput/InputRequired flags and
an optional Describe for per-agent card metadata.
- The card capability matrix is DERIVED from which fields are wired
(DeriveCapabilities / BuildCard), so declaration and behavior are single-
sourced and cannot drift. CatalogEntry drops its Capabilities field.
- The command layer gates every optional verb on the nil field and returns a
unified unsupported_capability (exit 2) before any network access; the
ErrUnsupported sentinel and convertUnsupported are deleted. --file is now
capability-gated too (file_input=false ⇒ unsupported before the upload prompt).
- The Discoverer interface becomes the ListAgents field; catalog providers must
wire it (asserted at Register). example expresses echo's minimal set vs
reporter's full set purely by which fields its Factory wires per agent — no
bool matrix, no refusal code. Conformance + tests updated to the new shape.
Mirror the events layering: the framework/SPI stays in internal/agent, the
concrete business providers move to a top-level agent/ package (agent/example/),
and agent/register.go blank-imports each so their init() self-registration runs.
cmd/build.go blank-imports the top-level agent package (alongside events); the
command layer (cmd/agent) no longer wires providers directly.
A test-only blank import keeps the example scheme registered for cmd/agent
tests, which exercise example:echo / example:reporter offline.
lark-agent skill: a framework-layer SKILL.md (verb contract, task state
machine, polling, exit codes) written with provider placeholders, plus
per-provider files under references/providers/. Adding a provider means
adding one provider file; the framework docs and verb references stay put.
- StaticCatalog (internal/agent/catalog.go): a framework helper carrying
the catalog-provider boilerplate — enumeration, per-agent card lookup
and typed unknown-id errors — so catalog providers do not reinvent it.
- agenttest.RunConformance: a one-call conformance suite pinning the SPI's
implicit contracts (registration metadata, zero-Deps factory, card
single source, enumeration stability).
- example provider (internal/agent/example): an offline in-memory
reference provider (echo / reporter, deliberately different capability
matrices) that doubles as the provider-onboarding template and the
command tree's zero-network demo backend.
Add the `lark-cli agent` command tree: a provider-neutral surface over
remote A2A agents. One constant verb set (list / card / send / task /
context) routes by agent_ref (<scheme>:<agent_id>) to registered
providers; remote agents never grow new top-level commands and their
capabilities are declared in a machine-readable card.
- SPI (internal/agent): Provider interface, registry with fail-fast
registration checks, typed ProviderKind / IdentityType, a closed
Capabilities struct, NewCard single-source card synthesis, and the
9-state task machine aligned with A2A.
- Command surface (cmd/agent): list / card / send / task / context with
default JSON envelopes, meta.next suggested commands, fire + bounded
`--watch --timeout` polling, local all-or-nothing scope preflight,
and capability gating. Two CLI-enforced high-risk-write confirmations:
`send --file` (off-machine upload) needs --yes, and artifact download
refuses to clobber an existing -o target without --force; both return
confirmation_required (exit 10) before any network/write. Artifact
download is SSRF-guarded, https-only and size-capped.
- Typed error contract with stable exit codes and codemeta classification.
- Ignore local-only proof artifacts (tests_e2e/, tests_skill_eval/,
coverage.html).
* fix: decouple --json shorthand registration from default format injection
* fix: fold --json shorthand into format flag before consumption
* fix: enable --json shorthand for mail +triage and mail +watch
* fix: enable --json shorthand for base +record-list
* docs: document --json shorthand for triage, watch and record-list
* docs: clarify record-list JSON output for script consumption
* docs: guide agents to JSON output for machine consumption scenarios
* test: make test comments self-contained
* test: assert typed error metadata in enum validation test
* docs: correct mail +watch --format default and enum in skill doc
* docs: keep --json shorthand undocumented as a silent fallback
* chore: bump oapi-sdk-go/v3 to v3.7.2 for filename-aware multipart upload
* fix: preserve original filename in multipart file upload
BuildFormdata read local files into a bytes.Reader before handing them
to the SDK, so the SDK's part-filename detection (which only reads
*os.File) fell back to "unknown-file" for every local --file upload.
Use AddFileWithName with the file's basename instead.
Refine `vc +meeting-events` around a stable agent-facing output contract.
The command now exposes structured meeting metadata, current read identity, normalized event rows, warnings, and pagination fields across JSON/NDJSON/pretty output. Event rows include stable event identifiers, event time, actors, and event-specific payloads for participant, chat/reaction, transcript, and magic-share events.
Improve meeting status inference by treating participant-left events with meeting-ended leave reasons as an ended signal, and keep compatibility with payload-only event shapes by falling back to `payload.activity_event_type`.
Update `lark-vc-agent` guidance for forwarding meeting chat and reactions to IM. Agents should build Feishu post content from JSON events, emit IM `emotion` nodes only for whitelisted reaction keys, and fall back unsupported reaction keys to text.
Add focused unit and dry-run E2E coverage for the event-type fallback and `vc +meeting-events --dry-run` request shape.
* fix(apps): make db --environment optional, auto-select branch server-side
All db shortcuts defaulted --environment to "dev", which forced single-env
apps (whose DB lives on the online branch, with no dev branch) to fail with
"Invalid DB Branch: dev" unless the user explicitly passed --environment
online.
Change the default to empty: when --environment is omitted the CLI sends no
env, letting the server pick the branch by the app's multi-env state
(multi-env → dev, single-env → online), matching miaoda-cli's behavior of
not carrying dbBranch when unset. Explicit --environment dev|online is
unchanged; explicit dev on a single-env app still errors as expected.
- 10 db shortcuts: dbEnvFlags default "dev" → "" (+db-execute, +db-table-list,
+db-table-get, +db-quota-get, +db-data-export, +db-data-import,
+db-changelog-list, +db-audit-list/-set/-status)
- dry-run e2e assertions updated: default env is now unset, not "dev"
- skill docs (lark-apps-db, lark-apps-db-execute) describe the auto-select
* fix(apps): omit empty --environment param; refine dry-run tests and skill doc
Address PR #1735 review:
- omit-empty: when --environment is unset, drop the env query key entirely
instead of sending env="" — matches the family's omit-empty convention
(cf. page_token) and miaoda-cli's "no dbBranch when unset". Add dbEnvParams
helper; apply across all db shortcuts (execute, table-list/-get, quota-get,
changelog-list, audit-list/-set/-status, data-export/-import) plus the
export/import query params, queryExportTotal and audit-list table/status probes.
- e2e dry-run assertions pin env is omitted via .Exists() (was Equal "").
- skill doc (lark-apps-db): rewrite the --environment guidance from an agent's
decision POV — read vs write, single-env writes hit online prod, explicit dev
on single-env as a probe; drop redundant/changelog phrasing.
* fix(apps): db recovery --environment support + diff/migrate display fixes
- +db-recovery-diff/-apply: add --environment (env → query param on submit
and both status polls), aligned with the recovery env IDL
- recovery diff: parse string row counts (inserted/deleted arrive as strings)
so they render as "-N rows" instead of "no changes"; drop the redundant
per-table data-row line when a schema action (drop/restore/alter) exists for
the same table; count tables_affected by distinct tables
- +db-env-migrate: run a dry_run preview before apply to backfill the change
count when the server reports changes_applied=0 on a cold apply (matches
miaoda-cli's diff-then-apply)
- lark-apps-db.md: drop the redundant recovery clause (recovery follows the
standard --environment rule)
* test(apps): cover no-env dry-run defaults + numericAsFloat string path
Address CodeRabbit review threads on PR #1735:
- numericAsFloat: add numeric-string cases ("13.5", " 13.5 ", int, empty)
- db-data-import: assert dry-run omits env when --environment unset (table
still defaults to file basename)
- db-quota-get: assert dry-run omits env when --environment unset
feat: remove reference lark-sharded skill
docs: clarify calendar write feedback
docs: split calendar scheduling workflow
docs: tighten calendar skill references
docs: annotate +freebusy scope to avoid unnecessary reads in scheduling flow
When users express "find free time + create event" intent, AI would
previously read freebusy.md before entering the scheduling workflow.
Adding a scope note to the shortcut table directs AI to use +suggestion
instead, reducing token consumption.
feat: support calendar +get
fix: optimize calendar skill
fix: optimize shortcuts and meta api
fix: optimize skill
Co-authored-by: calendar-assistant <tangfengyuan@bytedance.com>
* fix(schema): fall back to runtime catalog when no embedded metadata
Binaries built from the bare Go module (plugin builds) embed only the
empty meta_data_default.json stub because meta_data.json is gitignored
and fetched at build time. The schema command, its completion, and the
affordance command-form resolver read the embedded-only catalog, so
every schema lookup failed with "Unknown service" even though the
runtime registry had already sync-fetched full metadata.
Add registry.SchemaCatalog(): embedded when compiled in (official
builds unchanged, still deterministic), otherwise the merged runtime
catalog seeded from cache or remote fetch. When neither source has
data (offline plugin build with a cold cache), schema now returns a
failed_precondition error with an actionable hint instead of
"Unknown service" with an empty candidate list.
* fix(registry): gate cached meta overlay on version newer than embedded
The cached remote meta was overlaid onto the embedded meta_data.json
unconditionally, so after a CLI upgrade an equal- or older-version
cache kept shadowing the freshly shipped embedded definitions until a
later refresh happened to rewrite it.
Only overlay when the cache version is strictly newer than the
embedded baseline. The bare-module stub baseline is "0.0.0", so plugin
builds without compiled metadata still take any real cached version
(TestOverlayGate_StubEmbedded_OverlaysRealCache) and the schema
runtime fallback keeps working offline from a warm cache.
Ports #1376 onto the typed meta model.
---------
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
* feat(envvars): add LARKSUITE_CLI_AGENT_NAME accessor
envvars.AgentName() reads LARKSUITE_CLI_AGENT_NAME so business code can identify the agent driving the CLI. Value is sanitised (trim, reject control chars, 128-byte cap) and returns "" when unset or invalid. Local-only; not sent to the server.
* refactor(envvars): consolidate agent env accessors, dedupe sanitize
Move agent-trace reading from cmdutil.AgentTraceValue into envvars.AgentTrace(), symmetric with AgentName and sharing one sanitizeSingleLine. secheader.go now only assembles headers (BaseSecurityHeaders calls envvars.AgentTrace()); the duplicated sanitize and its os/unicode imports are gone. The X-Agent-Trace header and its behaviour are unchanged.
---------
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
The stdout success envelope is {ok, identity, data, meta?} and carries no
top-level code/msg field, but wrappers following the raw OpenAPI convention
test code == 0 and misclassify every successful call as a failure. Around
write commands this defeats caller-side idempotency and causes duplicate
creates (see #1689).
Document the contract in ERROR_CONTRACT.md (success-envelope counterpart to
the error wire format), both READMEs (JSON Output Contract section), the
lark-shared skill (shared prerequisite for all skills), and add a success
response example to lark-task-create.md.
Closes#1689
Add docs +history-list, +history-revert, and +history-revert-status backed by docs_ai history OpenAPI endpoints.
Document the safe history workflow and extend dry-run/live E2E coverage for the new shortcuts.
The Search v2 API rejects queries longer than 30 characters (counted by
Unicode code point, CJK 1 each) with 99992402 field validation failed —
it is a hard error, not truncation. Surface this in the --query -h help
text and the lark-drive search skill so callers compress long queries
before searching instead of hitting the error.
Change-Id: Ieb30a66edae7a573690c49719627ec8fb2500a1a