Keep readable native images when another attachment fails resolution.
Suppress only unresolved media facts across persisted and transient runner paths.
Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>
* feat(ai): continue stateful OpenAI SSE turns
Use the prior OpenAI Responses ID for native stateful HTTP/SSE turns, retry missing server state with full history in the same turn, and keep stateless/custom routes unchanged.
Co-authored-by: Markus Hartung <mail@hartmark.se>
* test(ai): assert Responses retry attempt metadata
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Make the process action enum authoritative for model-facing validation and direct executor calls, rejecting malformed actions before session lookup.
Refs #69582.
Punchcard-Session: clear-orchard-timber-c2
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: adone0 <vladyslav.yavorskyi@outlook.com>
* refactor(infra): extract shared git exec and verified snapshot-copy helpers
Moves the worktrees git wrapper to src/infra/git-exec.ts (with optional
maxOutputBytes for large buffered reads) and the online-backup/sanitize/
VACUUM/verify snapshot step into src/snapshot/openclaw-snapshot-copy.ts so
snapshot backends share one hardened copy path. Behavior-identical moves;
all importers updated.
* feat(snapshot): git-backed versioned SQLite snapshot engine
Deterministic per-table JSONL dumps (PK-ordered, lossless bigint/blob
encoding), verbatim DDL preservation, virtual/shadow-table skipping with
FTS rebuild on restore, secret-table redaction policy, manifest with
per-table row counts and content hashes, and restore verification by
re-serialization. Unchanged data produces no commit.
* feat(backup): recorded runs, freshness surfacing, and scheduled git backups
Every backup attempt is recorded in the previously writer-less backup_runs
table (bounded to 200 rows). openclaw status gains a Backups overview row
and JSON payload; doctor prints an informational hint when no successful
backup is recorded or the newest is stale. New commands: backup git
init/create/log/verify/restore and backup enable/disable, which provision
one idempotent gateway cron job running scheduled git backups.
* fix(state): stop bumping schema_meta.updated_at on unchanged opens
updated_at now records when schema metadata actually changed instead of
when the database was last opened; unconditional bumps dirtied the row on
every open and defeated no-change backup detection.
* docs: document versioned git backups, scheduling, and backup freshness
* fix(backup): satisfy CI ownership checks
* fix(backup): complete CI contract coverage
* fix(backup): complete credential table redaction
* fix(backup): isolate git repository ownership
* fix(backup): persist push degradation
* fix(backup): atomically converge schedules
* fix(status): isolate backup freshness environment
* fix(status): carry scan environment to freshness reads
* fix(backup): harden Git repository ownership
* docs(backup): document Git repository safety
* fix(backup): non-creating outcome log and origin preflight for pushed schedules
Recording a backup outcome never bootstraps an absent state database (a
failed backup on a fresh host would otherwise create a blank DB that a
retry treats as real input), and backup enable --push now requires the
repository to have an origin remote, pointing at backup git init --remote
instead of scheduling permanently degraded pushes.
* refactor(worktrees): use shared git exec helpers
* refactor(worktrees): remove unused git buffer wrapper
* refactor(worktrees): consume buffered git helper
* feat(backup): redact pushed schedules by default
Unattended recurring pushes retain credential-bearing tables durably in
remote Git history, so backup enable --push now defaults to
--exclude-secrets; --include-secrets is the explicit full-fidelity
override (still warned). Local non-push schedules keep full fidelity for
complete restores.
* fix(backup): redact audit HMAC and OAuth pending state; tolerate absent backup_runs
Adds audit_identity_keys (audit HMAC key) and mcp_oauth_pending_authorizations
(live OAuth callback state) to the redaction inventory, and makes read-only
backup freshness treat a same-version database without the additive
backup_runs table as no recorded backups instead of failing before a
writable open converges the schema.
* fix(backup): restrict schedules to local gateways
* fix(snapshot): harden Git restore and redaction
* fix(backup): block pushes of adopted history
* fix(backup): contain commits and pairing secrets
* test(secrets): cover code-mode nested exec store env; document harness boundary
* docs(secrets): warn that store env does not reach external agent harnesses
* fix(agents): resolve session-stable reply mode for direct resolver turns
Heartbeat wakes and other direct getReplyFromConfig callers bypass
dispatch, so no session-stable delivery mode reached their CLI binding
facts and prepare computed no messageToolPolicyHash, while dispatched
chat turns hashed the stable mode. resolveCliSessionReuse treats that
one-sided hash as a policy change, hard-invalidating the binding on
every chat<->heartbeat transition and leaving heartbeats to run in
fresh CLI sessions with no conversation history (historyPrompt=none).
Extract the synthetic-turn stable-mode resolution that
prepareAgentCommandExecution already used into
resolveSessionStableReplyMode, and apply it in prepareReplyRunContext
whenever a synthetic turn arrives without dispatch's injected mode, so
every turn kind on a session derives the same binding facts and hash.
Fixes#121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): derive synthetic binding facts from the dispatch policy owner
Consolidate the synthetic-turn stable-mode resolution onto dispatch's own
policy pieces instead of a simplified parallel resolver:
- resolveVisibleRepliesPolicy is extracted from dispatch prepare-context
(configured visible-replies guard + harness default chain) and shared, so
the fallback and dispatch cannot drift on harness defaults, live-vs-entry
chat facts, or ExplicitDeliverRoute.
- The stable fact now applies dispatch's messageToolAvailable downgrade via
the canonical tool-policy resolvers: tool-only delivery with a policy-denied
message tool records automatic, matching dispatch.
- Synthetic turns no longer fall back to their effective turn mode: a
response-tool heartbeat's message_tool_only is per-turn enforcement, not
session policy, and previously bypassed the stable fallback entirely.
Regression coverage: response-tool heartbeat facts match dispatched turns,
and a message-tool-denied config downgrades the synthetic stable fact.
Refs #121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): keep the session-stable reply mode sender-independent
Dispatch's stable-mode downgrade used the turn's sender-aware message-tool
availability, so a sender-scoped message denial hashed the stable policy as
automatic on chat turns while sender-less synthetic turns hashed tool-only —
the same binding reset loop on another axis.
resolveStableMessageToolAvailability becomes the one sender-independent
owner: dispatch passes it as sessionStableMessageToolAvailable for the
stable-mode resolution only (effective per-turn enforcement keeps the
sender-aware verdict), and synthetic binding facts already consume it.
Computed only when the visible-replies candidate is message_tool.
Refs #121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agents): derive stable reply facts from session surfaces, not wake plumbing
Third-pass review fixes on the #121485 consolidation:
- Strip system-event wake providers ("heartbeat", "cron-event") from the
stable context and resolve origin-less entries as internal, so synthetic
facts take the same internal-channel branch dispatch's live webchat turns
do instead of falling through to harness defaults.
- Fall back to persisted session facts (delivery channel/origin, groupId,
groupChannel/subject, accountId) for the sender-independent availability
stack, so bare-ctx callers like command prepare resolve the same
group/account-scoped policies as dispatched turns; dispatch now passes its
session entry too.
- Collapse the redundant second mode resolution into the availability
downgrade, surface dispatch's injected stable mode separately from
resolvePromptSourceReplyMode so the synthetic slot has one owner, and
reuse resolveTurnModelOverride instead of an inline copy.
- Refresh docs/.generated/plugin-sdk-api-baseline.jsonl: closure hashes only
(17 exports, zero declaration changes, verified) — plugin-sdk reply/meeting
entrypoints transitively bundle the reply pipeline this branch refactors.
Regression: origin-less entry heartbeat resolves internal-automatic facts.
Refs #121485
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: adapt to main module moves and split API baseline
Mechanical rebase refresh onto d3696f8d76: subagent-capabilities moved to
src/agents/subagents/spawn/, and the plugin-sdk API baseline is now
per-entrypoint content hashes (#122082) — regenerated for the reply-pipeline
closure this branch touches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* refactor(agents): split CLI runner concepts
* chore(lint): ratchet cli-runner max-lines baseline
* refactor(agents): remove stale runner import
* refactor(agents): preserve cleanup error typing
* refactor(agents): disambiguate CLI backend predicate
* test(agents): align code-mode pending boundary
Fixes red main: both host calls are unsettled when the guest snapshot is first parked; later waits still prove that settled calls are filtered.
* test(agents): tolerate code-mode settlement race
Fixes red main: the fast host call may settle as the guest snapshot is parked, while the deliberately slow call must remain pending.
* feat(gateway): projects.list groups known checkouts by repo identity
Implements docs/plan/runners.md milestone 4 derived projects read model.
* feat(ui): regroup the Where picker by gateway, devices, and cloud
* feat(ui): placement chip shows where a session runs with reclaim
Implements docs/plan/runners.md milestone 4 placement display and reclaim.
* refactor(agents): delete dead model-selection surface, consolidate compaction target assembly
Removes production surface with zero live callers: buildConfiguredAllowlistKeys
(orphaned since fallback allowlist filtering was split out), the
retryTransientProviderRuntimeMiss resolveModelAsync option (its gateway startup
prewarming caller was replaced by prepared runtime snapshots), the ignored
useAsyncModelResolution plumbing (kept only as a deprecated no-op field on the
plugin-SDK-shipped prepareSimpleCompletionModelForAgent), and dead facade
re-exports (inferUniqueProviderFromCatalog, ThinkLevel, ModelRefStatus).
Consolidates resolveEmbeddedCompactionTarget's five hand-built result sites into
one assembleTarget helper owning the auth-profile-drop-on-provider-change rule;
resolution precedence is unchanged and now pinned by new table-driven cases
(unique-provider inference, ambiguous literal, profile-suffix preservation).
Net -93 production LOC, -106 test LOC.
* chore(sdk): regenerate plugin SDK API baseline after facade export removals
* fix(agents): use unique isolated completion ids
Prevent same-millisecond CLI completions from sharing run authority by minting a UUID-backed wrapper identity.
* test(agents): type isolated completion release barrier
* fix(agents): scope quota failures to auth profiles
* test: repair provider suspension CI coverage
* test: keep suspension reset fixture internal
* fix(agents): spend cooldown probe only on transient candidates
Consume the one-run cooldown probe only when the candidate’s own unavailable reason is transient, so a billing-disabled pin cannot block a recoverable backup.\n\nFinding from the ClawSweeper review on openclaw/openclaw#121278.
* refactor(sessions): deprecate QuotaSuspension.laneId instead of removing
The shipped plugin-SDK surface deprecation policy requires keeping the inert field until the next surface window.
* fix(agents): extend transient probe policy to plugin-harness auth path
* fix(agents): keep provider overload from cooling auth profiles
* fix(agents): exhaust rotation candidates without cooldown records
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* test(agents): align auth rotation mocks with current main
* docs: regenerate plugin SDK API baseline
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
* style(agents): format session-suspension test after rename resolution
---------
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): unify tiered model resolution for chat and compaction
Manual /compact failed with Unknown_model when the agent model was
configured as an undated ref (e.g. anthropic/claude-haiku-4-5): the chat
run resolved models through a two-tier path (discovery-free lookup, then
prepared stores with bundled static-catalog fallback) while both
compaction entry points made a single bare resolveModelAsync call and
dead-ended before the static catalog could resolve the undated id.
resolveTieredModel is now the canonical resolution owner used by the
chat run, direct compaction, and queued compaction; the duplicated
chat-only tier logic and both bare compaction lookups are removed.
Regression test proves chat and manual compaction resolve the same
undated configured model through the shared owner.
* fix(agents): satisfy lint and test-types on tiered resolution call sites
Drop the redundant ?? {} spread fallbacks (spreading undefined is a
no-op) and give the test registry mock its real (provider, modelId)
signature for check:test-types.
Supervisor-capture tests asserted real telegram/slack channel ids, so
their pass/fail depended on which channel plugins the selected vitest
lane loads (stubs when focused, real normalizers under unit-fast);
switch to a neutral synthetic channel. Two embedded-runner tests
carried a shared literal /tmp/openclaw.sqlite storePath — one actually
opened it, colliding with stale artifacts across parallel runs; both
now use realpath'd auto-cleaned per-test temp dirs.
Evidence in PR #122164 body; pre-fix aggregates failed 12/1202 (cli)
and 4/4750 (embedded), post-fix green in default and shuffled order.
* fix(delivery): preserve suppressed send outcomes
Preserve authoritative durable-send suppression outcomes so task, subagent, and exec followups no longer report false delivery or duplicate ambiguous adapter dispatches.
* fix(delivery): keep suppression reason internal
Derive the outbound projection from the existing durable batch result union so the repair does not change the public plugin SDK contract.
* fix(delivery): avoid retrying ambiguous task updates
Advance the state-change event cursor after an adapter returns no identity so a possibly visible progress notification is not sent twice; intentional suppression remains retryable.
* refactor: use canonical re-export names
* fix(test): classify suite support as test source
* fix(agents): retarget gateway stub session-entry import
* test(gateway): retarget session-utils mock keys after alias removal
Use the selected harness for isolated title generation, including native Codex auth.
Retry failed dashboard titles from the first user message without overriding manual names.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
sessions.create reset-in-place persists the retired worktree's removedAt
before the RPC resolves, but a successor worktree create that rolled the
same random crustacean slug (1/420) hit nameIsUnavailable's owner-match
bypass and silently restored the retired registry record, clearing
removedAt and reviving the old branch (flaked
server.sessions.create.test.ts reset-in-place).
Generated names (title slugs and random slugs) now treat the caller's
removed records as collisions and take a numeric suffix; explicit
worktree names keep their documented reuse/restore semantics.
* refactor: consolidate coercion ownership
Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.
The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.
* fix: guard integer option ownership
Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.
* fix: keep integer helpers on numeric facade
Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.
* fix: point numeric coercion to number runtime
Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
Record successful logical target tools at the attempt owner so Code Mode exec receipts retain the nested tool evidence needed by release qualification.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(cron): stop advertising trigger-gated fields when cron triggers are disabled
When cron.triggers.enabled is off (the documented default posture), the
cron tool still advertised job.trigger/patch.trigger, schedule.kind
"stream" with its stream-only fields, and payload.kind "script" — all of
which the scheduler unconditionally rejects. Schema-eager models attach
junk triggers to plain reminder requests, get rejected, and retry-loop
without ever creating the job.
Gate those surfaces on the resolved config so they are only advertised
when the runtime can actually accept them, and swap the trigger guidance
for a short unavailable notice so the model tells the user instead of
polling or silently degrading the request. Config-less construction
paths keep the full surface.
Related: #119455
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cron): restore config type import and regenerate schema contracts
Repairs rebase fallout and CI-contract consequences of the trigger gating:
- Re-import OpenClawConfig from config/types.openclaw.js; the rebase crossed
#121768 which removed the re-export from config/config.js, stranding
resolveCronTriggersEnabled's signature.
- Guard the destructured tool in the MCP gating test for
noUncheckedIndexedAccess.
- Collapse the hookContext merge object to one line: the added config
pass-through tipped openclaw-tools.ts over the max-lines ratchet (701/700).
- Regenerate prompt snapshots (cron description narrowed under
triggers-disabled harness config; ~3k chars per prompt) and the plugin-sdk
API baseline (CronToolOptions gained the optional config member, visible
via codex-mcp-projection).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(cron): align schema contracts with current main
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>
Per-requester OAuth for HTTP MCP servers: in shared channels each trusted sender connects their own account. New config: mcp.servers.<name>.oauth.identity ("shared" default, byte-identical behavior) and gateway.publicOrigin (HTTPS except loopback) for the new GET /oauth/mcp/callback served on the Gateway HTTP server. Requester tokens are isolated per (channel, account, sender) in mcp_oauth_stores rows (no schema bump); callbacks correlate through a durable state-keyed one-time index with a 10-minute TTL; per-requester servers are fail-closed out of static/scheduled runtimes; unauthenticated senders get a sign-in link with a portable URL button; MCP config mutations clear requester credentials at the canonical owner. mcp status --json keeps the legacy authStatus fields additively. Proven by a two-user self-hosted-Executor E2E through the real gateway callback. Part of #122034.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(ui): align effort picker after model switches
Publish ordered per-model effort profiles from the Gateway and reconcile new-session effort overrides so labels, slider positions, and create payloads stay consistent.
* fix(ci): align model profile contracts