* fix(feishu): upgrade single newlines to paragraph breaks in post md messages
* fix(feishu): move post-md normalization out of adapter chunker, add card-mode regression test
* fix(feishu): normalize post-md newlines before chunk/limit decisions
* fix(feishu): remove post-md normalization from outbound chunker
Move newline normalization out of feishuOutbound.chunker so card and
document-comment text paths are not affected. Post-md normalization
remains scoped to sendOutboundText, buildFeishuPostMessagePayload, and
editMessageFeishu where the render mode is known.
Add regression test verifying the chunker preserves single newlines
unchanged.
Ref: https://github.com/openclaw/openclaw/pull/99394#issuecomment-4873230477
* fix(feishu): route card on raw text, add post-md expansion length guards
* fix(feishu): scope reply target to first subchunk in expanded post fanout
* fix(feishu): skip redundant post-md normalization for pre-chunked outbound and reply text
ClawSweeper review identified that subchunks from already-normalized
post-md text re-enter sendMessageFeishu which normalizes again. If a
chunk boundary falls inside a fenced code block the second chunk lacks
the opening fence, so findCodeRegions treats code newlines as prose and
expands them to paragraph breaks.
Add alreadyNormalized flag to buildFeishuPostMessagePayload and
sendMessageFeishu so pre-normalized callers (sendOutboundText overflow
path and reply-dispatcher sendChunkedTextReply) can skip the redundant
normalization pass.
* fix(feishu): convert markdown tables before post-md newline normalization in outbound path
ClawSweeper identified that sendOutboundText normalizes newlines before
sendMessageFeishu runs convertMarkdownTables. The blank lines inserted by
normalization break markdown table row contiguity, so raw-mode Feishu
accounts can lose table rendering while fixing paragraph spacing.
Convert tables before the newline upgrader so the converter sees the
original table structure. sendMessageFeishu runs convertMarkdownTables
again internally, but that is a no-op on already-converted text.
This matches the reply-dispatcher path which already converts tables
before normalization.
* chore(feishu): avoid exporting internal card builders, test through public send API
The card-mode newline preservation tests now exercise sendMarkdownCardFeishu
and sendStructuredCardFeishu instead of calling buildMarkdownCard and
buildStructuredCard directly. This keeps the SDK surface unchanged from
upstream/main.
The schema-2.0 width config and buildStructuredCard header-fallback tests
are removed because upstream/main already covers them under the sends
cards with schema-2.0 width config describe.
* fix(feishu): preserve markdown newlines in posts
* fix(feishu): preserve multiline markdown structure
* fix(feishu): preserve markdown structure while chunking
* fix(feishu): satisfy package and type gates
* fix(feishu): chunk posts within byte envelope
* fix(feishu): restore channel chunker contract
* chore(feishu): leave release notes release-owned
* chore(feishu): shrink max-lines baseline
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Rebase onto main pulled in the ClawHub plugin-install trust check (#102197);
ported it into the renamed system-agent operations/tool/tests, refreshed
native i18n, deadcode, and max-lines baselines for the renamed paths.
Refs #107237
* chore(lint): enforce no-floating-promises repo-wide
* feat(tooling): split DOM globals out of the Node-side typecheck program
tsconfig.core.json now compiles src/ + packages/ with lib ES2023; new
tsconfig.ui.json owns ui/ with DOM. Web-global names used by Node code
alias to canonical undici/stream-web types; highlight.js loads through a
validated narrow API because its d.ts force-includes lib.dom. Boundary,
sparse-guard, changed-lane routing, and profile lanes cover the new ui
graph.
* chore(deps): declare undici-types and teach knip about the highlight.js runtime require
* ci: retrigger dropped pull_request synchronize event
* refactor(types): derive web-global aliases from @types/node fetch globals, drop undici-types dep
* ci: retrigger after dependency guard cleared
* fix(scripts): add ui lane to changed-lanes declaration union
* feat: correlate native search outcomes in audit history
Metadata-only audit ledger for agent runs and tool actions in the shared
state DB: stable event identity, closed action/status/error vocabularies,
one-way-hashed tool-call ids, never-inferred terminal outcomes for native
web-search (explicit completed/failed only; otherwise unknown), bounded
retention, audit.list gateway RPC and openclaw audit CLI. Squashed from
the 82-commit audit stack for replay onto current main.
* feat(audit): add audit.enabled config gate (default on)
The metadata-only audit ledger records by default: an audit trail enabled
only after an incident cannot explain the incident, and the rows are
strictly less sensitive than the transcripts every install already
stores. audit.enabled=false stops new writes at the gateway subscription
seam; audit.list and openclaw audit keep serving existing records until
they expire. Documented in the configuration reference, protocol page,
and CLI reference.
* fix: repair full-matrix CI findings after rebase
- break the dynamic-tools/dynamic-tool-execution import cycle by
extracting resolveCodexToolAbortTerminalReason into a leaf module
- restore main's session-worktree protocol exports lost in the
index.ts auto-merge
- register the audit event writer worker as a knip entry point
- docs table formatting; subagent wait-cancellation test scoped to its
audit intent (outcome + timing) and advanced past main's new
lifecycle-timeout retry grace
* 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
Refactor the Control UI around route-owned page lifecycle and state while preserving existing behavior and design.
Prepared head SHA: bd51b6fa76
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
* fix(ollama): skip auto-discovery for remote/cloud base URLs
When the Ollama provider base URL points to a remote/cloud instance
(e.g. ollama.com), the plugin should not auto-discover all available
models via /api/tags. Cloud instances are shared tenants where the
provider manages the model catalog; users should only get models they
explicitly configure.
- Add remote-baseUrl guard in resolveOllamaDiscoveryResult
- Local/loopback URLs still auto-discover as before
- Remote URLs with explicit models return only those models
- Remote URLs without explicit models return null (skip discovery)
- Add tests covering remote guard, explicit models, and local fallback
* fix ollama cloud discovery ci
* fix(ollama): narrow discovery guard to hosted Ollama Cloud only
The previous guard blocked auto-discovery for ALL remote base URLs
without explicit models. This was too broad — it also blocked
self-hosted Ollama instances at custom domains (e.g.,
https://ollama.mycompany.com).
Replace the !isLocalOllamaBaseUrl() check with a targeted
isHostedOllamaCloud() check that only matches *.ollama.com
hostnames. Remote self-hosted Ollama endpoints now correctly
auto-discover as before.
Add isHostedOllamaCloud() helper with unit tests and a
regression test confirming remote self-hosted URLs still
auto-discover.
* fix(ollama): ensure models array in explicit-models return path
* fix(ollama): replace deprecated config-types import with local type
The openclaw/plugin-sdk/config-types subpath is deprecated and flagged
by the CI architecture check. Replace it with a local OllamaProviderConfigInput
type alias defined from non-deprecated provider-model-shared exports.
- discovery-shared.ts: define OllamaProviderConfigInput locally
- provider-base-url.ts: define OllamaProviderConfigInput locally
- Both files: remove import from openclaw/plugin-sdk/config-types
* chore(ollama): drop unrelated formatting churn
* fix(sessions): preserve Matrix room-id case in session keys (#75670)
Matrix room IDs (and thread event IDs) are opaque, case-sensitive per the
Matrix spec, but session-key canonicalization lowercased them. That forked
one room into duplicate sessions and produced 403 M_FORBIDDEN on recovery /
delivery paths that reconstruct the target from the (lowercased) session key,
even though deliveryContext.to stayed correct.
Introduce a generic, opt-in case-preservation registry (CASE_PRESERVING_PEERS)
consulted at all three lowercasing sites:
- construction: normalizeSessionPeerId
- store canonicalization: normalizeSessionKeyPreservingOpaquePeerIds
- gateway send: explicit request.sessionKey
Signal group preservation is encoded to match prior behavior exactly (segment
span, unscoped, thread suffix still lowercased). Matrix channel/group enrolls
the opaque tail (room id with embedded :server + any 🧵<event> suffix).
Exact mixed-case keys now win over folded legacy aliases in
resolveSessionStoreEntry and delivery-info lookup; existing lowercased rows
collapse on the next write. Matrix DM/MXID and non-enrolled channels keep the
default lowercase behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): guard Matrix folded alias delivery proof
* test(agents): cover cold OpenAI gpt-5.5 fallback
* fix(sessions): preserve non-opaque alias freshness
* fix(sessions): prevent Matrix cross-room thread recovery
* build(protocol): refresh tools effective Swift models
* test(codex): include effective cwd in startup fixture
* test(codex): align startup failure cleanup expectation
* fix(sessions): keep Signal folded aliases fresh
* fix(sessions): preserve unscoped Matrix room keys
* fix(sessions): recover legacy Matrix thread aliases
* fix(sessions): preserve Matrix keys in state migrations
* fix(sessions): keep Matrix structural alias freshness
* fix(sessions): preserve unscoped Matrix migration keys
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>