* fix(sessions): flatten Markdown in session list previews
Session-list previews were extracted verbatim from the last transcript
message, so raw Markdown leaked into every surface that renders the
subtitle as plain text — Control UI sidebar, TUI picker, native session
lists, and the sessions_list tool. A finished session read as
"Landed [PR #124879](https://github.com/...)".
Flatten lastMessagePreview at the Gateway producer, inside the
watermark-validated title-field cache, so the cost is amortized and no
consumer re-implements stripping. The flattener is the regex chain that
already existed privately in the Control UI narration line; it moves to
@openclaw/normalization-core/markdown-plain-text and both surfaces now
share one implementation. Session titles keep their own normalization
and are unchanged.
Also let an unread final observer digest outrank the raw last reply in
the sidebar subtitle, so the utility model's headline wins the slot it
was written for. The finalDigestUnread gate is untouched, so an
already-read digest still falls back to the flattened preview.
* fix(ci): register Markdown preview module
* fix(sessions): preserve literal preview punctuation
* fix(sessions): flatten previews before truncation
* fix(test): adapt session projection callback
* feat(normalization-core): add promise-like guard
* refactor: consolidate promise-like guards
* fix(normalization-core): keep isPromiseLike non-throwing on hostile then getters
ClawSweeper finding on #121861: the diagnostics-path local guard caught throwing
then getters; the canonical guard must classify, never throw.
* test(normalization-core): annotate intentional hostile-thenable fixture
* feat(tooling): add noUncheckedIndexedAccess strict-ratchet lane
* fix(packages): make indexed access explicit across ratchet packages
Burns down all 65 noUncheckedIndexedAccess errors in the seven ratchet
packages with behavior-identical restructuring (iteration, charAt/slice,
regex-group guards, validated hextet tuple). net-policy invariant
violations now throw instead of failing open. Adds
@openclaw/normalization-core/expect (expectDefined/first/last) with
subpath export and tests. memory-host-sdk fixes kept but the package
stays out of the lane: it re-exports core src/** so the flag would apply
transitively to all of core.
* fix(lint): keep .oxlintrc.json strict-JSON parseable for extension lint wrappers
* refactor: extract reusable AI runtime package
* refactor: complete AI provider relocation
* refactor: keep llm core internal
* refactor(ai): make @openclaw/ai self-contained with host policy ports
Move pure transport helpers (tool projections, strict-schema normalization,
prompt-cache boundary, stream guards, anthropic/openai compat, request
activity) from src into packages/ai; move utf16-slice into
normalization-core. Inject host policy (guarded fetch, redaction,
strict-tool defaults, diagnostics logging) through AiTransportHost with
inert library defaults installed by src/llm/stream.ts. Narrow the public
barrel to instance-scoped createApiRegistry/createLlmRuntime; the
process-default runtime moves behind internal/ and
registerBuiltInApiProviders takes an explicit registry. Delete the
src/llm/api-registry re-export facade.
* fix(ai): teach node, jiti, and vite resolvers the @openclaw/ai and utf16-slice subpaths
The workspace alias tables in root-alias.cjs, plugin-sdk-native-resolver,
sdk-alias, the shared vitest config, and the Control UI vite config only
knew @openclaw/llm-core; Node-side plugin loading resolved @openclaw/ai
through the pnpm symlink to the unbuilt dist (checks-node-compact CI
failures), and the Control UI build broke on the new
normalization-core/utf16-slice subpath.
* chore(ui): drop leftover service-worker debug logging
* build(release): ship @openclaw/ai with its own shrinkwrap and honest dependency set
packages/ai declares only its six real runtime deps (kysely, chalk, json5,
tslog, zod, fs-safe, and proxyline were never imported); orphaned root deps
removed. generate-npm-shrinkwrap now treats publishable packages/* like
publishable plugins so the AI tarball pins its transitive tree even though
workspace deps are omitted from the root shrinkwrap. knip learns the
package entry points; the tsdown dts neverBundle option moves to its
documented deps.dts home; the README documents the no-semver internal/*
contract and host ports.
* docs(ai): add minimal external-consumer example app
examples/ai-chat consumes only the public @openclaw/ai surface (built dist
via the workspace link): isolated runtime, built-in provider registration,
one streamed completion. Supports Anthropic/OpenAI via env keys and a
keyless local Ollama target; live-verified against Ollama.
* docs(ai): document the @openclaw/ai package and workspace shrinkwrap boundary
* chore(check): include examples/ in duplicate-scan targets
* fix: emit normalization package subpaths
* fix: complete AI package boundary artifacts
* fix: align AI package boundary contracts
* fix(ci): stabilize package release contracts
* test: align documentation contract checks
* test: keep cron docs guard aligned
* test: align restored docs contract guards
* test: follow upstream docs contracts
* docs: drop superseded talk wording
Extract shared normalization/coercion helpers into private @openclaw/normalization-core workspace package while preserving existing plugin SDK helper subpaths.\n\nAlso keeps direct normalization-core imports internal, wires UI/build/loader resolution, and replaces the slow PR network CodeQL lane with a fast added-line boundary scan while retaining full CodeQL for scheduled/manual runs.\n\nVerification: local moved tests, plugin SDK boundary tests, extension loader tests, agents-support shard, UI build/test, build artifacts, lint, workflow guards, autoreview, and GitHub CI passed on PR head 963d893715.