* fix(ui): rename the Memory Palace browser to Memory Wiki and document it
* refactor(memory-wiki): drop the old memory-palace module files
* docs: refresh generated docs map
* test(ui): align dream-diary hub-tab assertion with the wiki sub-tab id
* docs: describe memory wiki clustering behavior accurately
* docs(memory-wiki): record the wiki.overview rename decision at the registration site
* test(ui): query the Agents channels hub tab by tab role after the hub-tabs refactor
* feat(gateway): add loopback locality controls
* fix(gateway): keep loopback auth delays enforced under concurrency
The pending-timer cap let an attacker park cheap failures in every slot and then guess without penalty. Delays now key off a per-key deadline, so parallel guesses wait out the same escalating penalty and are still bounded by the max delay.
* fix(gateway): share one loopback penalty timer per key
Concurrent failures on a key now share a single timer and deadline instead of allocating one per in-flight request. Also corrects the security doc: the delay raises the cost of repeated guessing from one source, but credentials are compared before the failure response is delayed, so it is not a defense against parallel fan-out.
* docs(gateway): record why loopback delay stays post-verification
* docs: refresh generated docs map
* docs: refresh plugin SDK API baseline
* test: update loopback locality CI expectations
* fix(codex): restore connected apps for token-authenticated runs
* fix(codex): keep app inventory protocol types private
* fix(codex): align native runtime with Codex 0.146.0
Co-authored-by: Steven Lee <stevenlee@openai.com>
* fix(codex): clean up latest app-server integration
Co-authored-by: Steven Lee <stevenlee@openai.com>
* fix(codex): keep internal protocol types private
* fix(ci): repair current main Codex landing gates
* fix(ci): format inherited code mode matrix
* fix(codex): reconcile native app-server contracts with main
Prepare a verified GitHub-hosted mainline merge while preserving the reviewed Codex 0.146.0 fixes and canonical OpenAI authentication.
Co-authored-by: Steven Lee <stevenlee@openai.com>
* fix(codex): keep QA evidence in its owning plugin
Resolve the current-main Code Mode test rename without resurrecting the retired core test path.
Co-authored-by: Steven Lee <stevenlee@openai.com>
* fix(codex): enforce canonical OpenAI app-server auth
Reject retired provider aliases without runtime compatibility, direct operators to the doctor migration, and remove the redundant OpenAI API-key predicate.
Co-authored-by: Steven Lee <stevenlee@openai.com>
* chore(codex): reconcile latest main dependency graph
Preserve current main dependency changes while preparing the original Codex PR for an ancestry-preserving signed mainline merge.
Co-authored-by: Steven Lee <stevenlee@openai.com>
* fix(codex): unify bundled Codex 0.146 runtimes
Keep the ACP adapter on the same 0.146.0 Codex release as the managed runtime, remove obsolete 0.145.0 platform artifacts and unused semver compatibility, and preserve the latest main dependency upgrades.
Co-authored-by: Steven Lee <stevenlee@openai.com>
---------
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs: add local speech TTS setup
* docs: clarify local HTTP release requirement
* docs: require Speech Swift v0.0.23
* docs: use released speech-core Windows package
* docs: use current TTS config in local recipes
Co-authored-by: ivan-digital <root@ivan.digital>
---------
Co-authored-by: ivan-digital <root@ivan.digital>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(channels): make channel health reflect inbound ingress, not just the transport
`connected` is a transport signal, so a channel holding a healthy socket while its
durable ingress queue is unavailable evaluated as `health: healthy` and admitted
nothing. On a production gateway that hid 26+ hours of completely dead inbound Slack
behind `connected, bot:config, app:config, health:healthy`; outbound kept working,
which is what kept the transport looking fine.
Add inbound admission as its own health dimension rather than overloading `connected`
(deliberately tri-state since 30fda068aa, and 17 of 27 channel plugins depend on
absent meaning "no transport signal"). The shared ingress monitor now rethrows a
typed ChannelIngressUnavailableError when it cannot open its durable queue, the
channel supervisor records that as `ingressUnavailable` on the account, and
`evaluateChannelHealth` reports `ingress-unavailable` ahead of every lifecycle
window. Absence stays "unknown", never "fine", so socketless and simply quiet
channels are untouched; no traffic-staleness heuristic is introduced.
The health monitor deliberately does not restart such an account. A denied or
unusable ingress queue is a capability/config failure that a restart cannot fix, and
the channel's own bounded backoff ladder already tried. Today that case instead loops
at the 10-restarts-per-hour cap forever, so this removes a restart treadmill rather
than adding one.
* fix(channels): keep automatic recovery for a dead-ingress account
The health-monitor skip latched a transient queue-open failure into a permanent
inbound outage: ingressUnavailable is only cleared by a later start, and skipping
the restart meant that start never happened. Report the dimension and name the
restart reason instead of suppressing recovery.
* fix(channels): scope the ingress dimension to running accounts
Shadowing not-running would have made readiness flap during the supervisor's
documented restart-backoff grace. The blind spot the dimension exists for is a
running channel with a live transport and dead inbound, so evaluate it there and
leave stopped accounts on their lifecycle reason. Also applies oxfmt.
* fix(channels): keep the ingress reason and give readiness its own backoff grace
Two review cycles pulled in opposite directions on the same bounded window: the
health policy must keep the ingress cause in the not-running state a failed start
actually lands in, while readiness must not flap during restart backoff. Put each
in its owner -- the policy states the truth, and readiness extends its existing
restart-handoff grace to the ingress reason. Once the ladder stops setting
restartPending, the account stays red instead of hiding dead inbound.
* style: reshape the readiness backoff predicate for oxfmt
* fix(ci): regenerate the docs map and drop an unused ingress export
The new health.md section needs docs/docs_map.md regenerated, and knip flagged
CHANNEL_INGRESS_UNAVAILABLE_CODE as an unused production export -- it only has
callers inside its own module.
* feat(plugins): catch code-mode drift between catalogs sharing one model
Adds a contract test that groups bundled catalog rows by shared upstream
model and requires every row in a group to declare compat.codeMode once any
sibling does. Rows sharing a model id group automatically; rows under
different ids opt in with the new manifest-only `upstreamModel` marker.
Moves the kimi catalog into its manifest so the scan can see it, and records
the tier reseller catalogs were silently missing as explicit "capable".
* docs: regenerate docs map for the shared-model code-mode section
* feat(memory): add provenance and recall metadata to the memory index
* feat(memory): provenance-gated promotion and capture hygiene
* feat(dreaming): LLM consolidation with deterministic gates, on by default
* feat(active-memory): deterministic recall lane with escalation default
* feat(memory): user model file and standing intents
* docs(memory): document the memory architecture
* fix(memory): live-QA fixes — metadata writers, provenance classes, intent scope, claim accumulation
* feat(plugins): deliver sessions.changed to plugin services
* docs: regenerate docs map
* refactor(clickclack): split reconcile scheduler and history formatting out of the discussion service
* style: format discussion service
* feat(plugins): mirror local coding sessions to a remote Beam receiver
* fix(plugins): satisfy static gates for the Beam mirror seam
* fix(plugins): import the config type from the narrow contracts subpath
* fix(plugins): require catalog consent and loopback-only plaintext for the Beam mirror
* refactor(memory-core): remove orphaned shadow trials
* refactor(qqbot): migrate direct voice upload formats
Release note: QQBot configs using voiceDirectUploadFormats now migrate through openclaw doctor --fix to audioFormatPolicy.uploadDirectFormats at root and account scope. Existing nested uploadDirectFormats values win conflicts, and runtime reads only the nested policy.
* refactor(feishu): migrate tools base alias
Release note: Feishu configs using tools.base now migrate through openclaw doctor --fix to tools.bitable at root and account scope. Existing bitable values win conflicts, and runtime accepts only the canonical key.
* refactor(extensions): remove dead runtime state
* chore(config): refresh bundled channel metadata
* refactor(codex): keep realtime fallback type private
* test(ci): align QA compatibility scenario count
* feat(tools): per-model code-mode capability flags and auto master-switch tier
* feat(anthropic): flag claude-sonnet-4-6 as code-mode preferred
* revert(anthropic): drop claude-sonnet-4-6 from the code-mode preferred set
* test(anthropic): type the manifest compat field in the catalog contract test
* fix(anthropic): carry catalog compat onto hand-built forward-compat model rows
* refactor(slack): retire inline interactive directives
Release note: Slack configs using channels.slack.capabilities.interactiveReplies, including per-account settings, are removed by openclaw doctor --fix. Slack-only [[slack_buttons:...]] and [[slack_select:...]] markup is no longer parsed; emit typed presentation buttons and selects instead.
* docs: refresh Slack docs map
* feat(cron): bind agent turns to current sessions
* feat(commands): add loop chat command
* test(cron): cover session defaults and loop commands
* docs(cron): document current defaults and loops
* fix(commands): scope /loop status and stop by conversation name tag
* fix(commands): widen /loop conversation tag to 48 bits
* fix(commands): include disabled jobs in /loop status and stop
* docs(cron): regenerate docs map
* test(cron): split session-target default tests to satisfy max-lines
* feat(ui): durable session board face and dashboards index
Board face lived only in client-side boardSessionViews, capped at 50 entries,
so the preference never followed the user to another device, evicted as
sessions accumulated, and could not be seen as a set.
Persist it as SessionEntry.boardFace, which rides the existing entry_json blob
and so needs no SQLite schema change or version bump. Expose it on the session
list row and add it to the sessions.patch write-scope allowlist alongside label,
pinned, and archived: setting your own view preference is user-level chat
organization, not policy. Unknown patch fields still fail closed to
operator.admin.
Generic navigation now reads the stored face, so the sidebar and session list
open a thread on the face you left it on. boardSessionViews keeps only
activeTabId and reopenDockByTab, which are genuinely per-device.
Add /dashboards listing threads whose preferred face is dashboard. Filtering
runs server-side in filterSessionEntries before pagination, because the client
holds only a capped page and a client-side filter would silently omit
dashboards.
* test(protocol): assert the pre-rename face param is rejected
The gateway-protocol validator test still passed the pre-rename 'face' key,
which the closed schema rejects. Use boardFace, and pin the old name as a
negative case so it cannot silently return.
* chore(protocol): regenerate Swift bindings and docs map for boardFace
Adding boardFace to the sessions schema changes two committed generated
artifacts: the Swift gateway models (pnpm protocol:gen:swift) and the docs map
(pnpm docs:map:gen), which now lists the dashboards index section.
* refactor(prompt): plain inbound context labels with a provenance marker
Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.
Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.
Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.
Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.
* fix(ci): resolve gate failures for plain inbound context labels
- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
so the Kysely connection-boundary guardrail holds; unexport the now-internal
transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
the 700-line oxlint cap; suppressions are disallowed, so follow the existing
sibling record-module pattern. Public exports and PluginCompatCode literals
unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
only). The bracket de-fang and System: rewrite it expected were removed with
sanitizeInboundSystemTags; forged system lines are neutralized at the
system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.
* fix(prompt): harden inbound context label migration and drop in-band sanitizer
Review follow-ups on the plain-label + provenance-marker change:
- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
look-alike `System:`/`[System]` markers corrupted legitimate user text and is
not a real injection boundary; role separation plus external-content wrapping
is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
now requires a complete known legacy sentinel line, a legacy label followed by
a fenced JSON body, or the complete legacy external-content header. The prior
predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
header's line break, migrated assistant rows skip newline normalization, and
without it the marked-header replace wins and the body strips to empty. Added
a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
prose-block strip path.
Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
* feat(ui): path-based session and dashboard URLs
* docs(ui): document control UI URLs
* fix(ui): finalize session path routing
* feat(ui): anchor session URLs on stable keys
* docs(ui): clarify stable session URL identity
* fix(ui): resolve session prefixes with full prefix and pagination
Query sessions.list with the full supplied prefix instead of an eight-char
truncation, and paginate up to a bounded page count so longer disambiguation
links resolve instead of being reported ambiguous. Zero strict-prefix matches
now fall through to literal-key resolution rather than rendering an empty
ambiguity view.
Also document the ~dot/~dotdot segment escape: peer ids reach session keys
trimmed and lowercased only (src/routing/session-key.ts), so a literal '.' or
'..' segment is reachable and browsers would normalize it away.
* fix(ui): synchronize committed session routes
* test(ui): split native shell host coverage
* fix(ui): thread configured mainKey through session URL builders
Reserved-set disambiguation needs the operator-configured mainKey at runtime, so
thread it from agentsList through every session path builder and the ClickClack
control URL. Unambiguous non-hex single-segment rests now resolve literally while
short-id-shaped rests still fail closed, which restores ClickClack channel
compatibility detection and control-link reconciliation.
Also bound prefix-resolution retries, preserve catalog thread identity, and keep
draft state on ambiguous candidate links.
* fix(ui): repair session URL CI integration
* perf(ui): lazy-load session route resolution
* perf(ui): isolate session prefix resolution
* perf(ui): defer session path parsing
* perf(ui): defer session navigation startup
* fix(ui): preserve first-run and literal session navigation
* fix(ui): satisfy session routing type and export gates
* fix(clickclack): preserve unscoped control link agent
* style(ui): satisfy chat page line limit
* refactor(ui): move chat page helpers to owning modules
* fix(ui): preserve destination session route identity
* fix(ui): preserve agent identity in session routes
* fix(ui): escape dots in literal session path segments
encodeURIComponent leaves periods intact, so a literal key segment like
channel:release.js produced /chat/main/channel/release.js. In-app navigation is
intercepted by the SPA, but a refresh, an external link, or a ClickClack link
would be served as a static asset request and never reach the app.
pathForWorkboardBoard already escapes dots for this reason; mirror it in both the
session URL contract and the ClickClack encoder, route the agent id through the
same segment encoder, and pin the case in both shared vector tables.
* fix(ui): stop bootstrap after teardown race
* refactor(ui): centralize session navigation targets
* fix(ui): consume bootstrap teardown abort
* fix(ui): canonicalize configured main session routes
* fix(ui): preserve distinct session references
* fix(ui): redirect released session query links
* fix(ui): make bootstrap teardown abort-safe
* build(deps): remove npm shrinkwrap; mirror pnpm lock into transient package locks
npm 12 removed shrinkwrap (command + tarball/root loading). Delete all 82
committed npm-shrinkwrap.json files and stop publishing lockfiles; keep
pnpm-lock.yaml as the single reviewed dependency boundary. The generator
becomes scripts/generate-npm-package-lock.mjs and feeds plugin bundling via
a transient package-lock.json + npm ci (works on npm 11 and 12). Tarball
validation treats the published 2026.7.2 beta train as a shrinkwrap
transition; self-update npm detection now uses install topology instead of
the shipped shrinkwrap.
* fix(deps): repair lint, deadcode, and test-type lanes for the npm 12 migration
- sort integrity comparisons with an explicit comparator (oxlint)
- keep resolveBunGlobalNodeModules module-local (knip unused-export gate)
- model npm pack --json as npm<=11 array / npm 12 name-keyed object
- default calver destructuring in the tarball test fixture
The update_plan checklist tool has been default-on with no model gating for a
while, so keeping it under tools.experimental misrepresented it and kept a
container key alive for a single boolean. Rename it to a plain boolean
tools.updatePlan (camel-cased tool id, matching tools.agentToAgent and
tools.toolSearch), keeping the !== false default-on semantics.
The strict schema rejects the retired key, so openclaw doctor --fix now lifts
tools.experimental.planTool to tools.updatePlan and deletes the emptied
container. Registration also drops five parameters that were never read
(agentSessionKey, agentId, modelProvider, modelId, pluginToolAllowlist).
Docs: config-tools.md dropped a stale claim that the tool defaults off behind a
strict-agentic GPT-5 rule that no longer exists, experimental-features.md no
longer lists the tool and now cites the real Codex app-server floor (0.143.0,
per MIN_CODEX_APP_SERVER_VERSION).
Config baseline core count drops 2307 -> 2306.
* docs: correct retired cron/audit config keys, cron failure-alert default, memory recall default, and tool-search telemetry claims
- configuration-reference: cron block documented cron.webhook and cron.failureDestination, both retired by the config-surface reduction tranches (58452de711, edecdbd05e); the cron schema is strict so a copied snippet is rejected. Document only the live keys and note the doctor --fix migrations.
- configuration-reference: root-level audit block is retired; canonical path is logging.audit (src/config/zod-schema.root-shape.ts).
- configuration-reference: cron.failureAlert.after default is 2, not 3 (src/cron/service/failure-alerts.ts).
- memory-config: rememberAcrossConversations defaults on for personal installs (packages/memory-host-sdk/src/host/config-utils.ts), matching the canonical table earlier in the page.
- tool-search: telemetry records catalogSize, per-source counts, and search/describe/call counts, and only on tool_search_code results. No byte accounting exists in the runtime.
* docs: retire remaining references to removed cron, audit, and logging config keys
Sweep follow-up to the previous commit, covering the same bug class in the pages that still contradicted it.
- cron-jobs/cli-cron: global cron.failureDestination is retired; the destination fields now live on cron.failureAlert (src/config/zod-schema.root-shape.ts, merged by legacy-config-migrations.runtime.retired.ts:379). Per-job delivery.failureDestination bullets left intact.
- gateway/audit, cli/audit, gateway/protocol: root-level audit.* is retired; canonical path is logging.audit.*.
- logging: logging.redactSensitive is retired (dead-config-keys.test.ts:198; removed by legacy-config-migrations.runtime.tier-eval.ts:12). resolveConfigRedaction hardcodes DEFAULT_REDACT_MODE = tools, so redaction is unconditional. Also documented that redactPatterns replaces the defaults on the log path (redact.ts:419) while tool payloads always merge them.
- logging: consoleStyle accepts only pretty|json (zod-schema.root-shape.ts:106); compact remains the automatic non-TTY rendering style (logging/console.ts:40) but is no longer settable, and doctor maps a stored one to pretty.
- security: security --fix no longer touches redaction and the logging.redact_off audit check is retired (src/security/audit-loopback-logging.test.ts asserts it never fires).
* chore(docs): regenerate docs map after retired-key cleanup