* fix: prevent dollar-pattern injection in prompt template and approval substitution
Three call sites used String.replace/replaceAll with a string replacement
fed a runtime variable, causing dollar-amp/dollar-1/dollar-backtick
sequences in user-supplied args or approval ids to corrupt the rendered text.
Switch each to a function replacement so the value is treated literally:
- prompt-template-arguments: dollar-ARGUMENTS and dollar-@ substitution
corrupted slash-command args containing dollar signs
- get-reply-inline-actions: bundle command template expansion had the
same issue with normalizedArgs
- approval-reaction-runtime: approval id placeholder rendering mangled
ids containing dollar signs; the iMessage sibling
(extensions/imessage/src/approval-text.ts) already escapes this
* fix: rebase, drop unproven approval rewrite, add dollar regression
Address review: the approval placeholder helper has no canonical
/approve <id> producer in the plugin-SDK manual fallback path, so its
rewrite is unproven - revert it. Keep the two reachable prompt-template
fixes and add an owner-boundary regression covering literal dollar
sequences in dollar-ARGUMENTS and dollar-@ substitution (fails on the
old string replacement form, passes with the callback form).
* test(auto-reply): cover literal dollar bundle command arguments
* fix(agent-core): resolve prompt placeholders in a single substitution pass
* fix(cli): image-heavy Claude CLI turns abort after about fifteen screenshots
The Claude CLI emits every image tool result twice in its stream-json
output: once inside `message.content[].tool_result.content[]` and once in
the `tool_use_result` field it echoes alongside the message. The
normalizer added in #119486 only walks the message copy, so the echoed
copy still counts against the 8 MiB per-turn raw-output budget and is
still retained in the live-turn line buffer.
Walk the whole parsed user record instead, so both copies are stubbed,
and cover the `tool_use_result.file.base64` shape the built-in image
read echoes. openclaw never reads `tool_use_result` (no references in
src), so nothing downstream changes.
[AI-assisted]
* fix(cli): make echoed-payload normalization stack-safe
Addresses the review finding on the recursive walker. The normalizer now
lives in its own module as one canonical entry point, walks the record
iteratively with an explicit stack, and falls back to raw accounting when
a record is too deeply nested for `JSON.stringify` to re-serialize.
Adds a 50,000-deep regression record, built as text because
`JSON.stringify` itself cannot serialize one that deep.
[AI-assisted]
* fix(cli): normalize Agent SDK REPL binary echoes
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(update): make managed handoff own successor
Park the exact native service before Gateway close and coalesce same-root handoff requests so only the detached updater can activate and restart.
* refactor(update): reuse restart ownership helpers
* test(gateway): isolate supervisor restart fixtures
* fix(update): cancel handoff before restart fallback
* fix(update): harden managed service handoff
* fix(update): use canonical SQLite opener
* fix(update): preserve lifecycle runtime boundaries
* feat(ui): configure capabilities before session start
Reuse the active-chat Plus menu on new sessions, move Draft into it, and persist admin-scoped tool overrides before the initial turn. Closes#128079.
* test(ui): follow new-session Draft menu
* refactor(ui): reconcile new-session capability ownership
* fix(ui): gate terminal launch on capability overrides
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
---------
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(node-host): retry worker supervisor initialization
Clear failed initialization ownership for retry, retain valid container supervisors after transient reconciliation errors, and retry from the runtime until capacity becomes authoritative or shutdown cancels the lifecycle.\n\nRefs #128794
* fix(node-host): preserve permanent container mismatch failures
Keep durable engine and daemon-context mismatches on the actionable disabled path while retrying only transient supervisor reconciliation failures.\n\nRefs #128794
* fix(node-host): withdraw hosting on late context mismatch
Stop retrying when a later reconciliation attempt proves a permanent container context mismatch, close the supervisor, and withdraw worker hosting from runner inventory.\n\nRefs #128794
Preserve native provider commentary as keyed progress items across live delivery and history reloads while keeping cursor pagination reachable.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Queue Telegram-visible Mantis proofs at workflow level so only one shared-user run allocates a runner at a time.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* test(ui): split settings node suite by concern
ui/src/app/settings.node.test.ts sat at 999 oxlint-counted lines against
the 1000-line max-lines cap, so any added assertion failed the core lint
shard — and, as in #128474, could pass locally while only the merge ref
tripped the cap.
Split the 42-test monolith along the surfaces it actually covers: the
base file keeps gateway URL derivation, tab-local token handling, and
per-gateway session scoping; preference persistence and layout
persistence move to dotted siblings, matching the existing
settings.sidebar-prefs.node.test.ts precedent. Test names and count are
unchanged (47 across the settings node suites, before and after).
The location/base-path/settings-fixture helpers and the storage
lifecycle hooks are now shared from ui/src/test-helpers/settings-node.ts
instead of being copy-pasted per file; sidebar-prefs drops its duplicate
copies too.
* test(node-host): write fake container-engine state atomically
checks-node-compact-large-3 failed twice on this branch in different
tests of node-worker-supervisor.container.test.ts, most recently with
"SyntaxError: Unexpected end of JSON input" raised from the fake engine's
load() while the supervisor ran `docker rm --force`.
The shim saved container state with a truncating fs.writeFileSync while
sibling shim invocations (rm, inspect, wait, ps) read the same path, so a
reader could observe the zero-length window and exit 1. Write to a
".pending" sibling and rename over the target instead; rename is atomic,
so readers always see a complete previous or next state. The ps handler
filters on the ".container.json" suffix, so pending files are ignored.
A concurrent write/read probe over the old pattern produced 2014 partial
reads in 212900; the same probe over write-then-rename produced 0 in
203468. Not reproducible end-to-end on macOS, where the truncation
window is far narrower than on the loaded Linux shard.