* 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
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>
* refactor(anthropic): explore official Claude Agent SDK runtime
* refactor(anthropic): replace handwritten Claude sessions with SDK
* refactor(anthropic): collapse SDK live-session ownership
* refactor(anthropic): simplify SDK ownership and preserve live skills
* fix(anthropic): fence cancelled SDK runs before process startup
* fix(anthropic): harden SDK approvals, lifecycle, and packaging
* refactor(anthropic): own SDK process trees and streamline runtime
* fix(anthropic): repair rebased packaging and legacy test fixtures
Channel `/models` now reuses the Gateway-prepared plugin metadata snapshot and visibility policy across model resolution, keeping browse responses responsive while preserving plugin-owned aliases.
Fixes#127379.
Reviewed-by: @shakkernerd
* fix(channels): preserve gateway context for inbound turns
* fix(plugin-sdk): type bound channel reply dispatcher
* fix(channels): carry bound reply dispatchers
* fix(channels): keep reply carrier internal
* fix(auto-reply): consolidate dispatcher type imports
* fix(channels): keep reply dispatch typing internal
* fix(channels): derive inbound reply dispatcher types from turn plan
Keep ChannelRuntimeSurface identical to main: adapters read the bound
dispatchReplyFromConfig through the existing PluginRuntime["channel"]
wiring type and derive its type from the public ChannelInboundTurnPlan
contract, so the compatibility surface no longer grows a Gateway-bound
reply member.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(channels): hoist bound channel runtime casts for assertion ratchet
The assertion-safety ratchet collects SAFETY comments with a plain
ts.Scanner pass, which desyncs at the first template literal with a
substitution — comments after that point in a file are invisible, so
deep casts in monitor files cannot be SAFETY-covered. Hoist one shared
cast per call-site scope (absorbing the pre-existing buildContext
casts) and retype Discord's internal channelRuntime chain as
PluginRuntime["channel"] so dispatch reads need no assertion.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(channels): verify gateway dispatcher ownership
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* test(codex): assert sequenced node process notifications
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* docs(changelog): preserve release-owned root changelog
Channel Gateway tools preserve the owning dispatcher for Telegram, Discord, iMessage, Signal, and WhatsApp so terminal tools remain available. Thanks @VACInc.
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* test(codex): avoid shadowing process notification bindings
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
---------
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): retain admitted runtime generation
* fix(agents): own admitted runtime generation scope
* style: format prepared runtime generation handoff
* fix(agents): resolve generation scope through a process-global singleton
* test(agents): prove admitted generation survives runtime replacement
* test(qa): prove admitted generation survives gateway replacement on ephemeral gateway
* fix(agents): drain queued follow-ups outside the admitted generation scope
Queued follow-up turns re-admit on the generation current at drain time.
The follow-up drain now leaves any ambient prepared-generation scope so a
parked turn can never inherit the predecessor run's replaced generation
through the embedded runner's ambient fallback.
* test(qa): prove queued follow-up drains on the replacement generation
Run the ephemeral-gateway proof in followup queue mode so the parked turn
becomes its own drained run, wait on the gateway's queued-run state instead
of a fixed delay, poll agent.wait through the queue phase, and always persist
the redacted gateway log.
* refactor(agents): simplify runtime generation regressions
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* docs(changelog): avoid concurrent release-note conflict
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* docs(changelog): leave release notes to maintainer closeout
Channel runtime replacement keeps active turns on their admitted Gateway generation while detached follow-up turns adopt the current generation. Thanks @VACInc.
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
---------
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(heartbeat): keep background runs off the main lane
* docs(queue): describe heartbeat background lane
* fix(agents): forward heartbeat lane to embedded fallback
* test(agents): prove heartbeat and visible lane overlap
* test(agents): expose heartbeat lane in embedded harnesses
* refactor(agents): keep heartbeat lane choice at fallback boundary
Signed-off-by: sallyom <somalley@redhat.com>
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
Adds a generic plugin-declared selectable-context-window surface mirroring thinkingLevels: ModelCatalogEntry.contextWindows + contextWindowDefault through catalog normalization and the gateway protocol, session validation on sessions.create/patch, and a 200K/1M switch inside the Control UI model picker for Claude CLI 5-series models. The Anthropic plugin owns the option mapping: explicit 1m → `[1m]` argv suffix, 200k → bare id + CLAUDE_CODE_DISABLE_1M_CONTEXT=1, omitted → bare id (shipped default argv). Run budgets follow the selection on both CLI and native paths, so a 200K session gets a matching auto-compact window instead of a silent 1M budget.
Review fixes landed in this PR: run-owner prepared-fact plumbing so ordinary replies honor the selection; atomic catalog overlay merge and normalization for the options/default tuple; one-owner tuple reads in the picker; sessions.create key-presence patch semantics; native-run budget capping.
Feature direction and in-picker switch shape by @obviyus (maintainer review).
The /restart command is dispatched from a durable channel ingress row. The
gateway begins its restart drain before that dispatch returns, so the row is
still claimed when root-work admission closes. The drain sees
GatewayDrainingError and releases the row without spending its retry budget
(#125918), which is correct for an ordinary message and wrong for a command
that caused the drain: the successor gateway claims the same row and runs
/restart again, forever. Every boot is clean, so the gateway looks healthy
throughout.
Adopt the ingress claim in the restart command handler before scheduling the
restart. The command is not idempotent, so losing the acknowledgement is better
than an unbounded restart loop. The drain's deferral branch is unchanged.
Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
* fix: unblock replies after recovery owner release conflict
* fix(diagnostics): keep an unreclaimed active run as an observed skip
Removing the noop outcome left recoverStuckDiagnosticSession able to fall off
the end of its try block when an active run neither aborted nor released,
returning undefined from a Promise<StuckSessionRecoveryOutcome> (tsgo TS2366).
Report that residual path as skipped/active_embedded_run so the watchdog never
clears diagnostic state for work that still owns its session.
* fix(sessions): compare persisted row bytes in session-entry replacement CAS
The replacement projection snapshotted entries with the status/store readers
(no participant projection) and revalidated inside the transaction with
readExactSessionEntryRow, which merges session_participants into the entry.
Any row with a participant that is not its owner therefore serialized
differently on the two sides, so the compare-and-swap threw "SQLite session
entry changed before replacement" on every attempt with no concurrent write
at all. Startup orphan marking selects by status, so such a session could
never be repaired: it stayed status=running across restarts, its rotated
session id never persisted, and every later turn failed with "changed while
starting work", retried by the ingress spool for 24h.
Compare the persisted entry_json bytes on both sides, the same raw-bytes CAS
the sibling lifecycle and projection paths already use, so separately mutable
decorations (participants today, owner columns next) cannot invalidate a
logical-session write. Renames the raw reader to readExactSessionEntryJson
now that it is the general CAS reader rather than repair-only, and aligns
shouldRemoveSessionEntry with the participants-excluding equality its own
callers already use.
* fix(sessions): fail closed when a selected replacement row has no persisted bytes
The raw-bytes CAS could not distinguish "row unchanged" from "row gone". If a
selected row was deleted between hydrating the snapshot entry and reading its
persisted bytes, both the snapshot and the transaction read undefined, the
compare agreed, the transaction's source map stayed empty, and the stale
replacement was written back into the deleted key.
A selected key must hold bytes, so treat a missing snapshot read as the
conflict it is. Reported by ClawSweeper on the previous head.
---------
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* refactor(agents): reduce tool failure warnings to two rules
* test(agents): remove obsolete tool recovery receipt proof
* refactor(agents): drop unused meta param from buildToolMutationState
* test(agents): remove stale tool warning assertions
* test: export runtime source snapshot from closed runtime-snapshot mocks
Heal the main breakage introduced by 9441e3fe6e / #126531, which added a runtime source-snapshot read to provider model route resolution. Closed Vitest factories now return null for that source snapshot, preserving their pre-projection behavior.
* test(gateway): make compaction read-error faults order-immune
Generation-2 CI failure in run 32342180898, job 96343444772 showed that the mock factory initialized while shared gateway-server importers remained bound to the real transcript reader.
The dedicated isolated project fixes normal shards. Complete its ownership by adding it to the root project matrix and excluding the test from the non-isolated OPENCLAW_GATEWAY_PROJECT_SHARDS=0 fallback.
Restore intentional silence for undirected room events while retaining visible-answer recovery for mentions, commands, and ordinary requests.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix: surface Codex input prompts across runtimes
Codex structured input now reaches bounded Gateway questions in native and ACP runs, with exact turn ownership, explicit unsupported outcomes, and cancellation fencing. Consume the published ACPX elicitation support.
* chore: align elicitation helpers with current guards
Use protocol-specific helper names required by current main and update the reservation regression to the generalized input owner.
* fix: formalize structured input SDK surface
Expose one documented, frozen agent-harness structured-input contract with runtime and subpath coverage, replacing the accidental function-property API.
* fix: satisfy elicitation architecture gates
Register the real-process ACPX fixture as an executable test root and move shared structured-input types into the boundary leaf to keep Knip and Madge clean.
* fix: remove structured input lint suppression
Preserve the rejected control and invisible-character ranges with an explicit code-point check so the production suppression inventory stays closed.
* fix(channels): separate routing from receipt identity
Routing identifiers no longer fabricate message delivery evidence; provider-canonical thread placement is preserved.
* fix(channels): preserve conflicting receipt threads
Keep aggregate thread placement absent when provider receipt parts disagree, even when a requested route thread is available.