* fix(line): deliver a location LINE cannot render instead of dropping it
A location whose title or address is blank makes LINE reject the whole
request, so every delivery path filtered it out before sending. The reply
then arrived without the pin, and nothing told the user or the operator that
a part of the message had been removed: the coordinates the sender supplied
were discarded silently.
The location builder now answers with the values the sender wrote — the
non-blank label plus the coordinates — as a text message, so an unrenderable
pin degrades into something the chat can show instead of disappearing. Both
delivery paths inherit that from the single builder, and the three call sites
that used to skip a null result no longer have a branch to take.
Live validation against the Messaging API confirms the shape: the authored
location is rejected with "May not be empty" on messages[0].address, the same
rejection kills an entire batch that also carries valid text, and both
degraded forms validate cleanly.
* fix(line): bound the location fallback to LINE's text limit
Nothing caps the location labels at the schema, so a long title with a blank
address produced one raw text message past LINE's 5,000-character limit — the
provider rejected it and the location was lost exactly as silently as before.
The pin path already caps each label at 100 characters. Name that limit and
apply it in the fallback too, so both forms carry the same bound instead of
one of them re-deriving the provider's rules.
* test(line): merge the duplicate send.js imports
* refactor(line): replace nine-marker prompt DSL with typed rich messages
Delete the LINE plugin's double-bracket marker language (quick_replies,
location, confirm, buttons, media_player, event, agenda, device,
appletv_remote) and its parser. Portable interactions now flow through the
existing presentation-block seam (renderPresentation, matching Discord and
Feishu); LINE-specific cards ride closed channelData.line schemas mapped to
the existing Flex renderers. Prompt section shrinks to four capability
lines and explicitly de-fangs marker text. Removes the stale
assertion-safety baseline entry for the deleted parser.
Production LOC net -69, tests net -433. Suite: 510/510 green.
* fix(line): declare rich message schema dependency
* fix(line): satisfy rich message type checks
* docs(line): mark card fragments as partial
* fix(line): preserve ordered delivery of oversized markdown tables
* fix(line): type ordered delivery at provider and test boundaries
* fix(line): narrow ordered provider messages to flex or text
---------
Co-authored-by: Peter Steinberger <steipete@macos.shared>
* fix(line): honor channelData.line.mediaKind on the reply-token path
The reply-token delivery built every media message with createImageMessage,
ignoring channelData.line.mediaKind (and previewImageUrl/durationMs/trackingId),
so a video/audio reply was silently downgraded to a broken image. The push path
already honored mediaKind via resolveLineOutboundMedia + buildLineMediaMessageObject.
Route reply-token media through those same helpers (relocated to outbound-media.ts
and reused by both paths) via an injected buildMediaMessage dep wired in monitor.ts,
preserving the delivery file's dependency-injection boundary. Generic media without
LINE-specific options keeps the image route; a media that cannot be built surfaces
as a visible partial delivery so the text still reaches the user.
* refactor(line): unify reply media delivery
* fix(line): normalize media delivery failures
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(extensions): make indexed access explicit across channel plugins
Transport-payload-safe burn-down: malformed Telegram/Discord/QQ/LINE
and sibling channel input keeps existing skip paths; no synthesized
fields, no new throws in delivery loops. Zalo escape sentinels preserve
literal matches instead of undefined replacements.
* fix(extensions): make indexed access explicit across provider and memory plugins
Stream and model iteration, tool-block guards, capture guards, and
sparse accumulators; singleton model reads carry named invariants.
* fix(extensions): make indexed access explicit across tooling plugins, flip the extensions lane
Remaining plugins (oc-path, qa-lab, browser, logbook, and siblings) plus
the tsconfig.extensions.json flag flip. Cleanup: logbook sampleFrames
NaN index at max=1, QA retry clamp at non-positive attempts, dead Canvas
probe and OpenShell no-op slice removed, twitch test setup leak excluded
from the prod lane.
* refactor(plugin-sdk): expose expectDefined via a focused SDK subpath
Extensions imported @openclaw/normalization-core directly, crossing the
external-plugin packaging boundary (it only worked because the runtime
builder bundles undeclared workspace helpers). expect-runtime joins the
canonical entrypoints JSON, generated exports, API baseline, docs, and
subpath contract test; all 78 extension imports now use the SDK seam.
Two scanner-shaped locals renamed for review-bundle hygiene.
* chore(plugin-sdk): raise surface budgets for the expect-runtime subpath
One new entrypoint with one callable export, added intentionally as the
packaging-honest seam for extension invariant helpers.
* fix(line): truncate outbound altText, location, menu, and code fields on code-point boundaries
* fix(line): use safe truncation for receipt card altText
* fix(line): count rich menu limits by grapheme
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>