Compare commits

..

1386 Commits

Author SHA1 Message Date
Patrick Erichsen
c4c364cd27 fix: mark approval gateway calls as runtime clients 2026-05-17 21:24:42 -07:00
Peter Steinberger
5980c0d807 fix: wrap Mac menu gateway errors 2026-05-18 05:21:19 +01:00
Ayaan Zaidi
1c778f7afb fix(telegram): repair desktop proof login 2026-05-18 09:49:21 +05:30
Peter Steinberger
84b34519a8 fix: preflight remote skill bin probes 2026-05-18 05:19:02 +01:00
Peter Steinberger
71ed6526b1 ci: reduce aggregate runner jobs 2026-05-18 04:53:40 +01:00
Peter Steinberger
8483d03375 fix(gateway): preserve spawned sessions in configured lists 2026-05-18 04:38:14 +01:00
Peter Steinberger
696b4863c3 chore: quiet autoreview default fallback 2026-05-18 04:37:19 +01:00
Vincent Koc
a642ca9a89 ci(qa-lab): schedule live token efficiency artifacts 2026-05-18 11:33:13 +08:00
Vincent Koc
1300b22630 fix(qa-lab): classify runtime token efficiency 2026-05-18 11:09:08 +08:00
Peter Steinberger
29653e4106 fix: harden Mac gateway transport selection 2026-05-18 04:06:17 +01:00
Peter Steinberger
1ba3368fa6 fix: clean up Mac settings sidebar controls 2026-05-18 04:06:17 +01:00
Vincent Koc
4dec9679e6 fix(qa-lab): gate missing runtime tool coverage 2026-05-18 11:00:20 +08:00
Ayaan Zaidi
1ab84b4327 docs(changelog): note telegram 421 retry (#48908) (thanks @MarsDoge) 2026-05-18 08:28:27 +05:30
Dongyan Qian
63b728de43 fix(telegram): retry 421 misdirected request responses
Treat Telegram HTTP 421 / Misdirected Request responses as retryable transport failures in both the default channel API retry policy and the strict outbound send retry path.

Wire the 421 handling into isSafeToRetrySendError so non-idempotent Telegram send operations can retry this edge-node rejection without enabling broad ambiguous network retries, and add regression coverage for the default retry path plus strict send predicate handling.
2026-05-18 08:28:27 +05:30
Vincent Koc
73ca3cf3c3 test: tolerate optional ACP cron live timeout 2026-05-18 10:55:13 +08:00
Peter Steinberger
11d7499db1 feat: extend autoreview fallback reviewers 2026-05-18 03:49:23 +01:00
Galin Iliev
ad55d486ce fix(github-copilot): sanitize unsafe reasoning replay ids (#83221)
Fixes #83220.
2026-05-17 19:48:27 -07:00
Gio Della-Libera
1b5bc33161 fix(doctor): archive legacy clawd browser profile residue (#83230)
* fix(doctor): archive legacy clawd browser profile residue

* Avoid browser cleanup load without residue

Doctor --fix now skips loading the browser doctor facade unless the legacy browser/clawd profile path exists, preventing broad config repair tests from paying the plugin load cost when there is nothing to archive.

* Use structured health check for browser residue

Register the legacy clawd browser profile residue cleanup through the modern doctor health-check contract so doctor --lint can report it and doctor --fix repairs it through structured effects.
2026-05-17 19:45:03 -07:00
Gio Della-Libera
bcbe8b6299 fix(codex): surface declined native tool replies (#83108) 2026-05-17 19:43:19 -07:00
Galin Iliev
bc4f27c89a ci: skip changelog-only workflow runs (#83215)
Summary
Problem: root CHANGELOG.md updates currently cause broad pull request and push workflow activity, including CI and workflow sanity fanout, even though changelog-only edits do not touch product, runtime, docs site, or workflow logic.
Why it matters: the PR workflow (review, prepare, and land) can add or adjust CHANGELOG.md entries while processing otherwise-ready PRs. Those changelog-only updates retrigger gates, delay landing, and create avoidable contention when several PRs are being landed close together.
What changed: CI now ignores pull requests whose only changed path is CHANGELOG.md; Workflow Sanity ignores changelog-only pull requests and main-branch pushes; Docs keeps its markdown/docs trigger but excludes root CHANGELOG.md from the push path set.
What did NOT change (scope boundary): metadata-only automation such as labelers, auto-response, real behavior proof, or external GitHub apps can still run on PR events because those workflows are event-driven rather than file-scope CI. Other markdown files, docs files, and workflow files still trigger their existing checks.
2026-05-17 19:29:45 -07:00
Ayaan Zaidi
6baa2b38b2 ci(mantis): make telegram proof skips public-safe 2026-05-18 07:54:11 +05:30
Peter Steinberger
48f7db23f0 fix: harden clawpatch-reported edge cases 2026-05-18 03:18:55 +01:00
Tak Hoffman
816fbe0cf0 chore(labels): cool label palette (#83374)
* chore(labels): cool label palette

* chore(labels): soften taxonomy colors

* chore(labels): finalize label palette

* chore(labels): harden final palette
2026-05-17 21:12:10 -05:00
Peter Steinberger
69cea57f69 fix(telegram): fail closed on missing topic threads (#83381)
* fix(telegram): fail closed on missing topic threads

* docs(changelog): reference telegram topic cleanup
2026-05-18 03:07:12 +01:00
Vincent Koc
58e1351863 fix(qa-lab): hard gate runtime tool coverage 2026-05-18 10:05:04 +08:00
Peter Steinberger
73f4657869 docs: require autoreview before PR landing 2026-05-18 03:02:48 +01:00
Gio Della-Libera
1768667374 fix(migrate): count hidden config conflicts in preview (#83314) 2026-05-17 18:50:22 -07:00
Gio Della-Libera
8855a4aa58 fix(update): require integer timeout values (#83310)
* fix(update): require integer timeout values

* fix(update): reject blank timeout values
2026-05-17 18:47:59 -07:00
Peter Steinberger
4b4048fd22 fix: guard xai oauth callback cors (#83322) (thanks @Jaaneek) 2026-05-18 02:43:12 +01:00
Jaaneek
5f1df99a9c xai: OAuth login fixes plus openclaw User-Agent attribution
OAuth login flow
----------------
- Hard-require refresh_token after the authorization-code exchange in
  xai-oauth.ts. Access-only responses persisted credentials that the
  downstream usability check later rejected; the new requireRefreshToken
  option fails the exchange instead. Error wording explains the missing
  refresh_token in OIDC scope terms (offline_access scope rejected),
  not a "grant".
- Derive token expiry from the access-token JWT exp claim when
  expires_in is missing. id_token exp is intentionally not used as a
  fallback because id_token lifetime tracks the OIDC session, not the
  access token, and would defer refresh past actual expiry.
- Handle CORS preflight OPTIONS on the loopback OAuth callback in
  src/plugin-sdk/provider-auth-runtime.ts. The previous handler treated
  any non-callback request as a failed GET, returned "Missing code or
  state", and tore the server down before the real GET arrived. The
  CORS allowlist is now an optional `corsOriginAllowlist` parameter on
  waitForLocalOAuthCallback so the SDK helper stays generic. The xAI
  plugin passes ["auth.x.ai", "accounts.x.ai"] from loginXaiOAuth.

Sidecar surfaces
----------------
- speech-provider.ts (POST /v1/tts) honors the xAI OAuth profile in
  addition to provider config and XAI_API_KEY. isConfigured now also
  reports true when an xAI auth profile is configured (via
  isProviderAuthProfileConfigured), so OAuth-only users are no longer
  silently filtered out by the selection layer. The bearer resolver
  threads req.cfg into resolveApiKeyForProvider so the right xAI auth
  profile is picked when a user has multiple.
- realtime-transcription-provider.ts (WSS /stt) gets the same
  isConfigured fix, and the lazy headers() resolver threads req.cfg
  into the OAuth bearer lookup. createSession stays sync per its
  plugin contract.
- stt.ts: drop the plugin-side OAuth fallback. The media-understanding
  core already resolves auth (cfg/agentDir-aware) via
  resolveProviderExecutionContext before calling transcribeAudio, so
  the wrapper was redundant. transcribeAudio is now the registered
  hook directly.

User-Agent attribution
----------------------
- New buildXaiAttributionPolicy in src/agents/provider-attribution.ts
  injects User-Agent: openclaw/<version>, originator, and version on
  /v1/responses and /v1/chat/completions traffic that goes through
  resolveProviderRequestHeaders. Gated to xai-native and default
  endpoint classes; custom proxy baseUrls remain withheld. reviewNote
  is honest about which headers are spec-verified vs mirrored.
- Shared extensions/xai/src/xai-user-agent.ts helper exports
  xaiUserAgentHeaderFor(baseUrl) which only emits the User-Agent when
  the resolved baseUrl points at the xAI-native API host. Threaded
  through TTS and realtime STT (WS upgrade headers) so user-configured
  proxy baseUrls do not receive the openclaw identity. OAuth discovery
  and token endpoints still send User-Agent unconditionally because
  isTrustedXaiOAuthEndpoint already restricts those URLs to *.x.ai.
- Image gen, batch STT, and video gen rely on the attribution policy
  alone (no manual User-Agent in defaultHeaders), so attribution
  withholding on user-configured proxy baseUrls is preserved
  end-to-end.
- UA is bearer-agnostic: same value whether the bearer comes from an
  xAI API key or the xAI OAuth flow.

Drop dead api.grok.x.ai alias
-----------------------------
- xAI retired the api.grok.x.ai alias; DNS now returns NXDOMAIN from
  xAI's own authoritative nameservers. Drop it from the xai-native
  endpoint host set in extensions/xai/openclaw.plugin.json,
  extensions/xai/api.ts, extensions/xai/tts.ts, and the
  openai-responses payload policy. Update the attribution test to
  classify api.grok.x.ai as "custom" (no live user can reach it; the
  classification keeps documenting the host's status).

Video generation now matches xAI's actual API behavior
------------------------------------------------------
Previously, real video generation requests failed with
"xAI video generation response malformed" because the poll-status
handler validated against a closed enum that did not match what the
xAI service actually returns. Four fixes:
- Loosen the poll-status handler. xAI returns intermediate strings
  outside `["queued", "processing", "done", "failed", "expired"]`
  (commonly `submitted`, `pending`, `in_progress`, ...). Treat `done`
  as terminal-success, `["failed", "error", "expired", "cancelled"]`
  as terminal-failure, and any other string (including empty) as
  continue-polling. Also accept `cancelled` as a terminal failure.
- Send default duration/aspect_ratio/resolution on every generate and
  reference-image submit. xAI rejects bodies that omit these fields.
  Defaults: duration=8s, aspect_ratio="16:9", resolution="720p".
- Accept lowercase resolution input ("480p"/"720p"/"1080p") in
  addition to uppercase, normalize to lowercase on the wire.
- Add an `x-idempotency-key` header (fresh `crypto.randomUUID()`) on
  every submit so a network retry does not double-charge the user.
  Polls intentionally reuse the unmodified `headers` without the key.

Ergonomics
----------
- All "missing xAI credentials" errors (code_execution, lazy
  code_execution fallback in extensions/xai/index.ts, x_search,
  web_search grok in web-search-provider.runtime.ts, TTS, batch STT,
  realtime STT) now mention `openclaw onboard --auth-choice xai-oauth`
  first.
- Dedupe the Grok model-id alias table: model-compat.ts re-exports
  normalizeXaiModelId from model-id.ts as normalizeNativeXaiModelId.

Test coverage
-------------
- src/plugin-sdk/provider-auth-runtime.test.ts: locks the new pure
  buildOAuthCallbackOriginResolver gate (allowlist match,
  case-normalization, https-only, non-allowlisted hosts dropped,
  multi-Origin handling).
- extensions/xai/xai-oauth.test.ts: locks
  XAI_OAUTH_CALLBACK_CORS_ORIGIN_ALLOWLIST so loginXaiOAuth keeps
  threading the right hosts to the SDK helper.
- extensions/xai/speech-provider.test.ts: OAuth-only auth profile
  flips isConfigured to true; cfg threads into the OAuth fallback
  resolver.
- extensions/xai/realtime-transcription-provider.test.ts: same +
  upgrade headers carry the OAuth bearer end-to-end.
- extensions/xai/stt.test.ts: explicit assertion that transcribeAudio
  trusts the core-resolved apiKey (no plugin-side wrapper).

Verification
------------
- pnpm install: clean
- 154/154 vitest tests pass across 13 touched test files
- pnpm check:changed: typecheck core/ext + tests, oxlint core/ext,
  runtime guards, dependency pin guard, package patch guard, runtime
  import cycles, sidecar loader guard - all green
- pnpm build: 0 errors, 0 [INEFFECTIVE_DYNAMIC_IMPORT] warnings
2026-05-18 02:43:12 +01:00
Peter Steinberger
b5046968f6 docs: clarify media completion handoff 2026-05-18 02:36:17 +01:00
Peter Steinberger
645ef817b6 test(channels): preserve thread origin contracts
Add core and hook mapper regression coverage for the thread-origin contract behind #83302.\n\nThe tests prove a flat reply target can coexist with a thread-addressable OriginatingTo, and hook canonical conversation mapping keeps following OriginatingTo.\n\nProof: focused Vitest, autoreview, Testbox check:changed tbx_01krwaztbwm13sx9e4sbyyz4c1, and CI run 26008670388 passed.
2026-05-18 02:30:24 +01:00
Peter Steinberger
9aa46843ec fix(telegram): preserve forum topic origin targets
Fix Telegram forum-topic OriginatingTo routing for inbound, audio-preflight, and skipped-message hook contexts.

Centralize Telegram inbound origin target construction so real forum topics stay encoded in the routing target while DM thread ids remain metadata-only.

Fixes #83302.
2026-05-18 02:19:46 +01:00
Josh Avant
73049d291b Fix transcript-only assistant rows in latest reply lookup (#83362)
* fix: skip transcript-only latest assistant rows

* chore: add changelog for transcript-only assistant fix
2026-05-17 20:13:34 -05:00
Tak Hoffman
7ff8323ed5 chore(labels): add label color sync policy (#83357)
* chore(labels): add label color sync script

* chore(labels): align future label colors
2026-05-17 20:09:47 -05:00
Peter Steinberger
5434769e47 fix(cron): suppress source replies for announce delivery 2026-05-18 01:41:16 +01:00
Peter Steinberger
428fc16ac8 ci: make Tideclaw alpha long gates advisory 2026-05-18 01:40:37 +01:00
compoodment
6ebe91d92b test: cover one-chunk progress final payload 2026-05-18 01:37:59 +01:00
Peter Steinberger
2d2c420ed2 test: speed up prompt snapshot checks 2026-05-18 01:37:31 +01:00
Peter Steinberger
3d85e84df3 test(ci): update prerelease runner expectation 2026-05-18 01:35:04 +01:00
Peter Steinberger
bb691a0d25 fix(ci): recognize gateway run command chunk 2026-05-18 01:35:04 +01:00
Peter Steinberger
9bdc183b7d fix(cli): keep subcommand help lightweight 2026-05-18 01:35:04 +01:00
Peter Steinberger
b0b18d1e4a fix: seed control UI origins for bind aliases 2026-05-18 01:21:33 +01:00
Peter Steinberger
17ab3b11cb ci: reduce main workflow queue time 2026-05-18 01:18:50 +01:00
Peter Steinberger
91266fa928 fix(telegram): bound isolated long-poll timeout 2026-05-18 01:05:27 +01:00
Peter Steinberger
47a2efe483 fix: hide display-hidden chat transcript messages 2026-05-18 01:04:48 +01:00
Peter Steinberger
9da0f80356 fix(openai): allow available Codex OAuth models 2026-05-18 01:04:14 +01:00
Peter Steinberger
77bbffb998 docs: run autoreview with full-access sandbox 2026-05-18 00:58:30 +01:00
Peter Steinberger
bef3356375 fix(macos): keep dashboard failures in window 2026-05-18 00:56:28 +01:00
Peter Steinberger
086d3d012e docs: add maintainer assignment triage workflow 2026-05-18 00:52:37 +01:00
VACInc
72e164a3fe fix: preserve recent Codex context projections 2026-05-18 00:41:36 +01:00
Josh Avant
06f4c97130 Keep legacy Codex OAuth sidecar profiles usable (#83312)
* fix legacy Codex oauth sidecar compatibility

* docs add changelog for legacy Codex oauth compatibility

* annotate legacy oauth hash compatibility
2026-05-17 18:41:07 -05:00
Peter Steinberger
9a936b3063 test: fix CI regressions 2026-05-18 00:37:48 +01:00
Peter Steinberger
691d62630f test: keep slow tests under duration cap 2026-05-18 00:26:44 +01:00
Peter Steinberger
7bcd5acc1a test(codex): type denied tool policy mocks (#82374) (thanks @VACInc) 2026-05-18 00:18:20 +01:00
VACInc
5f1d8a2ee4 fix(codex): fail closed restricted native tools 2026-05-18 00:18:20 +01:00
VACInc
dad3db40d3 fix(codex): honor denied app-server tool policy 2026-05-18 00:18:20 +01:00
wAngByg
d63c581dec fix(gemini-transport): validate thought_signature base64 before forwarding to Gemini (#82995)
Merged via squash.

Prepared head SHA: 8634757622
Co-authored-by: wAngByg <281221101+wAngByg@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-18 02:16:13 +03:00
Peter Steinberger
7afac6015f feat(browser): surface observed dialogs (#83099) 2026-05-18 00:05:29 +01:00
JC
57da466ecb Fix Discord verbose tool progress delivery (#80042)
Summary:
- The PR changes Discord reply delivery, sanitizer, and queued follow-up auto-reply paths so explicit verbose tool-progress payloads are delivered while final assistant replies still use the privacy sanitizer.
- Reproducibility: yes. source-level: current main strips tool-looking Discord payload text at the front-chann ... ds compaction events in queued follow-up runs. I did not run a live Discord repro in this read-only review.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix: gate queued follow-up progress when verbose is off
- Included post-review commit in the final squash: fix: preserve queued verbose progress under preview suppression
- Included post-review commit in the final squash: ci: rerun discord verbose progress PR
- Included post-review commit in the final squash: fix: preserve Discord verbose progress after rebase
- Included post-review commit in the final squash: fix: serialize discord queued progress
- Included post-review commit in the final squash: Fix Discord verbose tool progress delivery

Validation:
- ClawSweeper review passed for head fd845e773a.
- Required merge gates passed before the squash merge.

Prepared head SHA: fd845e773a
Review: https://github.com/openclaw/openclaw/pull/80042#issuecomment-4414121881

Co-authored-by: Clawsistant <clawsistant@users.noreply.github.com>
Co-authored-by: anyech <anyech@gmail.com>
Co-authored-by: OpenClaw Assistant <assistant@openclaw.local>
Co-authored-by: Shadow <hi@shadowing.dev>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: thewilloftheshadow
Co-authored-by: thewilloftheshadow <35580099+thewilloftheshadow@users.noreply.github.com>
2026-05-17 22:59:07 +00:00
Peter Steinberger
127f3f86d7 style(macos): align sessions settings padding 2026-05-17 23:56:52 +01:00
mjamiv
c93d6d8daa fix(gateway): keep unmanaged restarts in-process (#83138)
Summary:
- The PR changes ordinary unmanaged gateway restarts to return the existing in-process fallback instead of detached-spawning a replacement child, with focused tests, docs wording, and a changelog entry.
- Reproducibility: yes. at source level: current main and v2026.5.12 detach-spawn unmanaged ordinary restarts, ... e PR body also supplies after-fix terminal proof that the patched helper returns disabled without spawning.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 8c82df6c77.
- Required merge gates passed before the squash merge.

Prepared head SHA: 8c82df6c77
Review: https://github.com/openclaw/openclaw/pull/83138#issuecomment-4471071848

Co-authored-by: mjamiv <74088820+mjamiv@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 21:19:05 +00:00
VACInc
aa71f7fe15 Fix Telegram stop debounce bypass (#83248)
Summary:
- The PR adds a generic inbound debounce `cancelKey`, uses Telegram stop-like controls to cancel same-chat pen ... buffers and bypass debounce, and adds focused Telegram regression coverage plus updated channel test mocks.
- Reproducibility: yes. by source inspection: current main enqueues Telegram text through inbound debounce bef ... nly has flush semantics for pending keyed work. I did not run a live Telegram repro in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Fix Telegram stop debounce bypass

Validation:
- ClawSweeper review passed for head 19245a341d.
- Required merge gates passed before the squash merge.

Prepared head SHA: 19245a341d
Review: https://github.com/openclaw/openclaw/pull/83248#issuecomment-4472300906

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 21:10:53 +00:00
Kevin Lin
d85a7c6b67 docs: fix building plugin typebox import 2026-05-17 13:36:09 -07:00
Kevin Lin
d0736919aa docs: clean up building plugins guide
Refactor docs/plugins/building-plugins.md into the scoped plugin-author guide, preserving the legacy registering-agent-tools anchor and restoring the original Next steps section.
2026-05-17 13:32:15 -07:00
clawsweeper[bot]
bacc18a575 Log Telegram outbound delivery success (#83247)
Summary:
- The PR adds info-level Telegram outbound send success logs for text/media sends, tracks accepted threadless  ... s, and loads the OpenAI Codex external auth overlay for Codex plugin-harness runs with regression coverage.
- Reproducibility: yes. there is a source-level reproduction path: the branch adds focused tests for Telegram  ... mission/privacy and Codex auth overlay selection. I did not execute those tests in this read-only checkout.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Use Codex auth overlay for scoped Codex runs
- PR branch already contained follow-up commit before automerge: Add regression tests for Codex auth and Telegram send logs
- PR branch already contained follow-up commit before automerge: Log Telegram outbound delivery success

Validation:
- ClawSweeper review passed for head b860487aef.
- Required merge gates passed before the squash merge.

Prepared head SHA: b860487aef
Review: https://github.com/openclaw/openclaw/pull/83247#issuecomment-4472287527

Co-authored-by: jrwrest <jrwrest@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 19:46:31 +00:00
Gio Della-Libera
9a5f2f61e7 Doctor: add health-check contract and --lint validation (#80055)
* feat(doctor): add --lint mode + structured HealthFinding shape

Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.

Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
   types. Findings carry severity per-finding; checks return
   readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
   distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
   duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
   Catches throws into synthetic error findings (anchored at check id;
   message scrubbed of control chars, capped at 256 bytes). Sorts
   findings by severity desc, check id, path. Exports
   exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
   logic from existing legacy run*Health functions:
     core/doctor/gateway-config            (warning)
     core/doctor/command-owner             (info)
     core/doctor/workspace-status          (info)
     core/doctor/final-config-validation   (error)
   Each was audited safe per the proposal's adapter constraints
   (no writes, no repair calls, no prompts, no probes incl. local-bind).
   Legacy run*Health contributions in doctor-health-contributions.ts
   are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
   snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
   filters by --severity-min, emits human or JSON output, returns exit
   code from unfiltered set so --severity-min hides info findings
   without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
   --severity-min, --skip, --only flags to existing doctor command.
   --lint branches to runDoctorLintCli; without --lint, doctor runs
   unchanged.

LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.

* fix: avoid string spread in doctor errors

* chore: refresh plugin SDK API baseline

* docs: clarify doctor lint usage

* feat(doctor): prepare repairs for dry-run reporting
2026-05-17 12:29:57 -07:00
Tak Hoffman
0dc04fb926 ci(mantis): allow ClawSweeper telegram proof agent (#83243) 2026-05-17 14:26:15 -05:00
Gio Della-Libera
fb53c2d610 fix(doctor): detect stale session snapshot paths (#82867)
* fix(doctor): detect stale session snapshot paths

Warn when cached session snapshot metadata still references bundled skill paths from inactive OpenClaw runtime roots, while keeping workspace skill roots and current runtime paths quiet.

* fix(doctor): honor configured session stores

* fix(doctor): scan raw snapshot paths

Expand home-relative cached snapshot paths before stale bundled-skill classification and scan raw session-store JSON so persisted resolvedSkills are inspected before normal session-store normalization strips them.
2026-05-17 12:12:25 -07:00
clawsweeper[bot]
214f718be7 fix(agents): persist subagent registry before returning accepted (#83132) (#83238)
Summary:
- This PR adds a strict initial subagent registry persistence path, rolls back failed registrations, updates affected test seams, adds a regression test, and records the fix in the changelog.
- Reproducibility: yes. Source inspection on current main shows registry save failures are swallowed after the ... s added, and the linked source PR provides an ENOSPC-style after-fix terminal proof for the corrected path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): persist subagent registry before returning accepted (#83…

Validation:
- ClawSweeper review passed for head d564ef051d.
- Required merge gates passed before the squash merge.

Prepared head SHA: d564ef051d
Review: https://github.com/openclaw/openclaw/pull/83238#issuecomment-4472173642

Co-authored-by: yetval <yetvald@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 19:11:01 +00:00
clawsweeper[bot]
f36a1b0c81 fix(codex): preserve streamed command output (#83222)
Summary:
- The PR buffers Codex command-output deltas per command item and uses them as a fallback for transcripts, trajectory output, final tool output, and after-tool-call errors when `aggregatedOutput` is empty.
- Reproducibility: yes. A source-level reproduction is clear: send current-turn command-output delta notificat ... aggregatedOutput: null`; current main has no final transcript or trajectory fallback for the streamed text.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(codex): preserve streamed command output

Validation:
- ClawSweeper review passed for head 07393a304f.
- Required merge gates passed before the squash merge.

Prepared head SHA: 07393a304f
Review: https://github.com/openclaw/openclaw/pull/83222#issuecomment-4472054629

Co-authored-by: 0x505badc0de <32790662+rozmiarD@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 18:41:00 +00:00
clawsweeper[bot]
3e765263dd fix(agents): preserve run-mode keep subagents past session sweep TTL (#83132) (#83226)
Summary:
- The PR exempts run-mode `cleanup: "keep"` subagent registry entries from the session-mode sweep TTL, adds focused regression coverage, and records the fix in the changelog.
- Reproducibility: yes. Current main source shows a run-mode keep entry has no `archiveAtMs` and then matches  ... ; the linked source PR also provides before/after terminal proof against a real persisted `runs.json` path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): preserve run-mode keep subagents past session sweep TTL …

Validation:
- ClawSweeper review passed for head 32faf5cf32.
- Required merge gates passed before the squash merge.

Prepared head SHA: 32faf5cf32
Review: https://github.com/openclaw/openclaw/pull/83226#issuecomment-4472073823

Co-authored-by: yetval <yetvald@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 18:27:53 +00:00
clawsweeper[bot]
fb028cadc8 fix(codex): deliver Telegram verbose tool progress (#83214)
Summary:
- The branch updates Codex app-server tool-progress projection and auto-reply dispatch so Telegram direct mess ... l-only `/verbose` turns deliver concise tool summaries while filtering message-send and activity-log noise.
- Reproducibility: yes. Current-main source inspection shows `message_tool_only` suppression can drop verbose tool summaries before dispatch, and the linked source PR gives a live Telegram DM before/after path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(codex): deliver Telegram verbose tool progress

Validation:
- ClawSweeper review passed for head f6a79cb306.
- Required merge gates passed before the squash merge.

Prepared head SHA: f6a79cb306
Review: https://github.com/openclaw/openclaw/pull/83214#issuecomment-4471954529

Co-authored-by: Tyler Bea <43728897+kurplunkin@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 18:23:58 +00:00
Peter Steinberger
800a0d3166 test: stabilize live subagent steering 2026-05-17 18:45:44 +01:00
sandypockets
a5a5df67da Fix clipped usage chart tooltip (#82846)
Summary:
- The PR replaces per-bar absolute Usage chart tooltips with one viewport-fixed floating tooltip and adds focus/keyboard handling plus focused jsdom coverage.
- Reproducibility: yes. at source level. Current main renders an absolute `.daily-bar-tooltip` inside `.daily- ... ` overflow contexts, and the linked issue plus PR before screenshot demonstrate the tall-bar clipping case.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Merge branch 'main' into fix-usage-tooltip-clipping

Validation:
- ClawSweeper review passed for head edbb26a5be.
- Required merge gates passed before the squash merge.

Prepared head SHA: edbb26a5be
Review: https://github.com/openclaw/openclaw/pull/82846#issuecomment-4468967811

Co-authored-by: sandypockets <41454557+sandypockets@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 17:25:23 +00:00
Tak Hoffman
0f1f9525f3 fix(ci): clear Mantis command reactions (#83194)
* fix(ci): clear mantis command reactions

* fix(ci): clear Mantis command reactions

---------

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-17 12:22:01 -05:00
Peter Steinberger
c3308b9195 test: keep matrix subagent spawn opt-in 2026-05-17 18:20:54 +01:00
100menotu001
7c416950c6 fix(feishu): return subagent thread delivery origin (#83190)
Summary:
- The PR returns a Feishu/Lark deliveryOrigin from subagent_spawning after successful thread-bound session binding, adds DM/topic/sender-scoped topic hook assertions, and adds a changelog entry.
- Reproducibility: yes. by source inspection. Current main's Feishu subagent_spawning hook binds the child con ... eneric session-spawn path only directly routes the initial child run when result.deliveryOrigin is present.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(feishu): return subagent thread delivery origin

Validation:
- ClawSweeper review passed for head 44a6200a91.
- Required merge gates passed before the squash merge.

Prepared head SHA: 44a6200a91
Review: https://github.com/openclaw/openclaw/pull/83190#issuecomment-4471452247

Co-authored-by: OpenClaw Contributor <100menotu001@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-17 17:12:06 +00:00
Peter Steinberger
59b85d4eb9 test: stabilize release validation flakes 2026-05-17 18:04:35 +01:00
Gio Della-Libera
44c3d8ea2e fix(memory): preserve qmd lexical search for hyphenated queries (#81423) 2026-05-17 09:52:04 -07:00
clawsweeper[bot]
893f580072 fix(update): tailor gateway recovery hints by platform (#83191)
Summary:
- The PR updates the CLI post-update gateway recovery formatter and tests to show Linux, macOS, Windows, or generic service-manager guidance, plus a changelog entry.
- Reproducibility: yes. Source inspection gives a high-confidence reproduction path: current main reaches a fo ... hAgent recovery text, while the platform contract says Linux uses systemd and Windows uses Scheduled Tasks.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(update): tailor gateway recovery hints by platform

Validation:
- ClawSweeper review passed for head 0cf2a0c5a7.
- Required merge gates passed before the squash merge.

Prepared head SHA: 0cf2a0c5a7
Review: https://github.com/openclaw/openclaw/pull/83191#issuecomment-4471471293

Co-authored-by: Rubén Cuevas <hi@rubencu.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-17 16:48:08 +00:00
Peter Steinberger
af62fd45cd test: stabilize release qa gates 2026-05-17 17:45:58 +01:00
Peter Steinberger
6ebc5e4719 test: harden release qa edge scenarios 2026-05-17 17:26:37 +01:00
Tak Hoffman
f349fb82aa fix(mantis): remove ambiguous github trigger mention (#83179) 2026-05-17 11:24:23 -05:00
Vincent Koc
79212f9869 feat(qa-lab): select runtime parity tiers 2026-05-18 00:21:13 +08:00
Gavin Zeng
ea72414e1c fix(build): bundle zod inline to fix pnpm global install resolution (#78515)
Merged via squash.

Prepared head SHA: c925d1afab
Co-authored-by: ggzeng <20488795+ggzeng@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-17 19:20:42 +03:00
Chris Zhang
ac848d318d fix(agents): exclude tool result details from guard budget (#75525)
Merged via squash.

Prepared head SHA: 4efe094507
Co-authored-by: zqchris <4436110+zqchris@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-17 19:14:59 +03:00
Peter Steinberger
4c60ab3666 test: extend release qa wait windows 2026-05-17 17:05:15 +01:00
Vincent Koc
9249e13891 test(qa-lab): sync personal pack expectation 2026-05-17 23:56:18 +08:00
Vincent Koc
94c0d9ac81 docs(changelog): backfill qa-lab runtime coverage notes 2026-05-17 23:56:17 +08:00
Ayaan Zaidi
59efd95669 ci(mantis): add telegram proof label trigger 2026-05-17 21:16:00 +05:30
Vincent Koc
b764396dee fix(qa-lab): differentiate mock provider plans 2026-05-17 23:44:35 +08:00
Firas Alswihry
45a434fb23 test(qa-lab): add personal approval denial scenario 2026-05-17 23:33:09 +08:00
clawsweeper[bot]
1760881574 fix(plugins): default 15s timeout for before_agent_start hook (#48534) (#83147)
Summary:
- The PR adds a 15-second default timeout for legacy `before_agent_start` modifying hooks, regression tests for hung handlers, and a changelog fix entry.
- Reproducibility: yes. Registering a `before_agent_start` handler that returns a never-settling promise is en ... ts the hook and the runner awaits directly; the linked source PR also supplies before/after terminal proof.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(plugins): default 15s timeout for before_agent_start hook (#48534)

Validation:
- ClawSweeper review passed for head 8d2c5b8808.
- Required merge gates passed before the squash merge.

Prepared head SHA: 8d2c5b8808
Review: https://github.com/openclaw/openclaw/pull/83147#issuecomment-4471169756

Co-authored-by: Rahul <rahulnilvan43@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-17 15:31:32 +00:00
Peter Steinberger
a00e494992 test: harden matrix subagent spawn prompt 2026-05-17 16:21:31 +01:00
github-actions[bot]
428af92ac9 chore(ui): refresh fa control ui locale 2026-05-17 15:20:13 +00:00
github-actions[bot]
40d8e6eab7 chore(ui): refresh nl control ui locale 2026-05-17 15:20:08 +00:00
github-actions[bot]
1e0e2c0e2d chore(ui): refresh vi control ui locale 2026-05-17 15:19:59 +00:00
github-actions[bot]
303effce67 chore(ui): refresh th control ui locale 2026-05-17 15:19:55 +00:00
github-actions[bot]
96f2e1ae43 chore(ui): refresh pl control ui locale 2026-05-17 15:19:37 +00:00
github-actions[bot]
0ce7cb1b7f chore(ui): refresh id control ui locale 2026-05-17 15:19:24 +00:00
github-actions[bot]
364f8cd04f chore(ui): refresh uk control ui locale 2026-05-17 15:19:19 +00:00
github-actions[bot]
bcef46e63c chore(ui): refresh tr control ui locale 2026-05-17 15:19:13 +00:00
github-actions[bot]
747cfbbaad chore(ui): refresh it control ui locale 2026-05-17 15:19:00 +00:00
github-actions[bot]
f4776138c7 chore(ui): refresh ar control ui locale 2026-05-17 15:18:54 +00:00
github-actions[bot]
e536ce86bf chore(ui): refresh ko control ui locale 2026-05-17 15:18:38 +00:00
github-actions[bot]
ed6c46ec7e chore(ui): refresh es control ui locale 2026-05-17 15:18:33 +00:00
github-actions[bot]
4a6b50c789 chore(ui): refresh fr control ui locale 2026-05-17 15:18:27 +00:00
github-actions[bot]
b76fac10dc chore(ui): refresh ja-JP control ui locale 2026-05-17 15:18:22 +00:00
github-actions[bot]
1e446845fd chore(ui): refresh de control ui locale 2026-05-17 15:17:50 +00:00
github-actions[bot]
ce634337d2 chore(ui): refresh zh-TW control ui locale 2026-05-17 15:17:48 +00:00
github-actions[bot]
c2adcd0a36 chore(ui): refresh zh-CN control ui locale 2026-05-17 15:17:46 +00:00
github-actions[bot]
135005e3dd chore(ui): refresh pt-BR control ui locale 2026-05-17 15:17:42 +00:00
Vincent Koc
1926982c4c fix(qa-lab): refresh parity model targets 2026-05-17 23:12:26 +08:00
吴杨帆
019dbcc749 fix(failover): classify Moonshot balance 429 as billing (#83079)
Merged via squash.

Prepared head SHA: 9f70bf5935
Co-authored-by: leno23 <39647285+leno23@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-17 18:06:07 +03:00
Peter Steinberger
72eef85942 ci: raise qa live build heap 2026-05-17 16:05:16 +01:00
Gio Della-Libera
164c35da85 fix(cli): lower extra gateway advisory severity (#82922)
Merged via squash.

Prepared head SHA: abed98a5f3
Co-authored-by: giodl73-repo <235387111+giodl73-repo@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-17 18:03:03 +03:00
Peter Steinberger
543518bd43 test: accept runtime matrix dm approvals 2026-05-17 15:58:07 +01:00
Peter Steinberger
833f1ce735 test: finish fanout responses followup 2026-05-17 15:42:46 +01:00
Vincent Koc
66b8de9c83 fix(qa-lab): preflight live codex auth 2026-05-17 22:39:00 +08:00
Peter Steinberger
aaf85166de test: complete mock fanout followup 2026-05-17 15:31:59 +01:00
Peter Steinberger
7554deef30 test: retry transient Slack live timeouts 2026-05-17 15:11:41 +01:00
Peter Steinberger
f0fc8c27d3 test: settle Slack gateway handoff 2026-05-17 15:02:40 +01:00
Peter Steinberger
ef763d0f0b test: ignore unrelated Slack no-reply observations 2026-05-17 14:55:22 +01:00
Peter Steinberger
395346fe57 test: wait for stable Slack live readiness 2026-05-17 14:47:48 +01:00
Peter Steinberger
2ab76240d3 test: align qa docker UI dist assertion 2026-05-17 14:39:27 +01:00
Ayaan Zaidi
bb64223155 docs(changelog): add Android TLS prompt PR reference (#83077) (thanks @sliekens) 2026-05-17 19:02:29 +05:30
Ayaan Zaidi
30263f6d35 refactor(android): distill TLS fingerprint prompt flow 2026-05-17 19:02:29 +05:30
Steven Liekens
848e0486b7 fix(android): prompt on changed TLS thumbprint 2026-05-17 19:02:29 +05:30
Peter Steinberger
cdd817669a fix: preserve Slack presentation fallback 2026-05-17 14:18:23 +01:00
Peter Steinberger
9d85f05b01 fix: keep chunkable presentation text intact 2026-05-17 14:18:23 +01:00
Peter Steinberger
5dbc969b46 fix: preserve Telegram interactive precedence 2026-05-17 14:18:23 +01:00
Peter Steinberger
2a6ef5287b fix: align agent presentation button type 2026-05-17 14:18:23 +01:00
Peter Steinberger
a4210dbaee docs: refresh plugin sdk api baseline 2026-05-17 14:18:23 +01:00
Peter Steinberger
b78c2ee8c8 refactor: adopt presentation rendering in Mattermost 2026-05-17 14:18:23 +01:00
Peter Steinberger
f5090d2624 feat: render Teams presentations as Adaptive Cards 2026-05-17 14:18:23 +01:00
Peter Steinberger
fee1cd9867 docs: document presentation API surface 2026-05-17 14:18:23 +01:00
Peter Steinberger
ee72ce8cf7 refactor: deprecate legacy interactive reply APIs 2026-05-17 14:18:23 +01:00
Peter Steinberger
ad861d4c9d feat: add presentation capability limits 2026-05-17 14:18:23 +01:00
Peter Steinberger
868315aef0 test: fix install readdir mock typing 2026-05-17 14:12:55 +01:00
Peter Steinberger
798833140d test: fix WhatsApp live readiness typing 2026-05-17 14:08:06 +01:00
Vincent Koc
d3a7348ad9 fix(qa-lab): keep bootstrap tokens private 2026-05-17 21:05:01 +08:00
Peter Steinberger
5fc6b714a6 test: extend live transport stability windows 2026-05-17 13:54:57 +01:00
Peter Steinberger
e43a2efcdb test: harden wsl2 fixtures 2026-05-17 13:45:21 +01:00
Vincent Koc
c80cb5986f fix(update): use post-doctor plugin records 2026-05-17 20:41:59 +08:00
Peter Steinberger
22723b6f1e test: harden live transport gates 2026-05-17 13:41:04 +01:00
Vincent Koc
9bb5f5af0d fix(qa-lab): fail live parity without token usage 2026-05-17 20:38:10 +08:00
Peter Steinberger
3b39ff4318 test: tolerate degraded live transport state 2026-05-17 13:25:23 +01:00
Peter Steinberger
60fc982cb6 fix(macos): avoid cron settings crash 2026-05-17 13:22:32 +01:00
Vincent Koc
9b62a35760 fix(qa-lab): expose redacted qa bus tool traces 2026-05-17 20:18:48 +08:00
Peter Steinberger
f74b302dc2 test: harden live QA transport validation 2026-05-17 13:16:02 +01:00
Peter Steinberger
18ac38963d chore: remove old codex review skill path 2026-05-17 13:15:48 +01:00
Peter Steinberger
006ebe692d chore: rename codex review skill to autoreview 2026-05-17 13:15:30 +01:00
Peter Steinberger
4f4be666eb chore: update codex review fallback handling 2026-05-17 13:11:35 +01:00
Vincent Koc
e3621f5057 fix(cli): keep secret diagnostics off json stdout 2026-05-17 20:08:16 +08:00
Vincent Koc
f9c8cb7877 fix(feishu): refresh inbound session routes 2026-05-17 20:05:10 +08:00
Peter Steinberger
3c6ec521d5 fix(cli): resolve PowerShell completion profiles 2026-05-17 12:58:35 +01:00
Stellar鱼
fe68af5307 fix(cli): show concrete PowerShell completion profile path 2026-05-17 12:58:35 +01:00
Peter Steinberger
93db190308 fix(gateway): isolate hot reload channel failures
* fix(gateway): isolate hot reload channel failures

* fix(gateway): restore partial hot reload stops
2026-05-17 12:56:14 +01:00
Peter Steinberger
9897559e3f test: accept final-only Matrix quiet streaming replies 2026-05-17 12:56:01 +01:00
Alex Knight
8a060b2904 Release embedded session write lock before model I/O (#82891)
Summary:
- The PR narrows embedded PI session transcript write-lock scope, adds stale/max-hold config plumbing, and updates affected transcript, doctor, gateway, SDK, Codex mirroring, docs, and regression-test surfaces.
- Reproducibility: yes. Current main source still holds the embedded session write lock from early attempt set ... cksmith Testbox contention proof on unmodified main; I did not rerun the live repro in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): narrow context engine session lock
- PR branch already contained follow-up commit before automerge: fix session lock runner build types
- PR branch already contained follow-up commit before automerge: Release embedded session write lock before model I/O
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8289…

Validation:
- ClawSweeper review passed for head 4c6dd7ed6e.
- Required merge gates passed before the squash merge.

Prepared head SHA: 4c6dd7ed6e
Review: https://github.com/openclaw/openclaw/pull/82891#issuecomment-4469282923

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-17 11:54:03 +00:00
Peter Steinberger
3dd8bcb419 style(macos): polish settings panes 2026-05-17 12:41:27 +01:00
Peter Steinberger
5e1fde7c22 ci: serialize WhatsApp live QA jobs 2026-05-17 12:40:31 +01:00
Peter Steinberger
0aae5ba077 test: retry WhatsApp QA driver observation timeouts 2026-05-17 12:32:46 +01:00
Peter Steinberger
8f59a370aa test: harden live QA retry handling 2026-05-17 12:23:19 +01:00
Peter Steinberger
4aa671b71a fix: use declared crawl module paths (#83040) 2026-05-17 12:18:23 +01:00
Peter Steinberger
4ccd07718d chore: point crawl skills at openclaw repos 2026-05-17 12:18:23 +01:00
Peter Steinberger
09f7702b96 feat: add crawl archive skills 2026-05-17 12:18:23 +01:00
Jerry-Xin
3e9e1d6321 fix: route subagent announce to originating parent session instead of channel-bound peer session (#80242)
* fix: route subagent announce to originating parent session instead of channel-bound peer session

When a subagent is spawned from agent:main:main while a Telegram DM is active,
the completion announce was delivered to the parallel Telegram channel session
instead of the originating parent.

Two interacting bugs:

1. The spawn tool received the sandbox/policy session key (Telegram peer key)
   as the requester, instead of the real run session key. Fixed by passing
   runSessionKey to createSessionsSpawnTool so the registered requester
   points to the actual parent session.

2. resolveSubagentCompletionOrigin checked child session bindings before
   requester bindings. When both share the same channel+accountId (common
   for Telegram DMs), the child binding hijacked the delivery target.
   Fixed by checking requester binding first, with child as fallback.

Fixes #80201

* fix: drop subagent_announce from mediated completion set

The subagent_announce addition to AGENT_MEDIATED_COMPLETION_TOOLS was
unrelated to the routing fix and could cause group/channel completions
to fail silently when the subagent does not use the message tool.

This should be addressed separately with proper message-tool-only
guidance (tracked in #80223).

* fix: separate sandbox policy from completion owner in sessions_spawn

PR #80242 passed runSessionKey as agentSessionKey to createSessionsSpawnTool,
which caused spawnSubagentDirect to use the run session key for sandbox policy
checks (resolveSandboxRuntimeStatus). This could make a sandboxed channel run
appear unsandboxed.

Introduce completionOwnerKey as a separate field that is only used for
registerSubagentRun routing (requesterSessionKey), keeping agentSessionKey
for sandbox enforcement, callerDepth, activeChildren, and all other policy
checks.

* fix(agents): preserve subagent ownership routing

---------

Co-authored-by: 忻役 <xinyi@mininglamp.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 12:17:51 +01:00
Vincent Koc
d1cd74b243 fix(channels): scope dm last-route updates 2026-05-17 19:11:28 +08:00
Vincent Koc
7d6e45ef7c fix(qa-lab): clean orphaned gateway runtimes 2026-05-17 19:10:46 +08:00
Peter Steinberger
045d7aae50 docs: update obsidian skill for official cli 2026-05-17 12:09:34 +01:00
Peter Steinberger
7bf4dfeff3 test: harden live QA transport probes 2026-05-17 12:08:45 +01:00
Rui Xu
d41916b5c3 fix(memory): clarify vector degradation warning 2026-05-17 12:08:37 +01:00
Vincent Koc
9a50fe1497 changelog: note setTimeout yield for Responses stream abort timers 2026-05-17 19:02:51 +08:00
Kaspre
69a0c925b8 fix(codex): cover side-question native hooks (#82559)
* fix(codex): cover side-question native hooks

* fix(codex): enforce native approvals for app-server requests

* fix(codex): preserve approval fallback after native relay noop

* fix(codex): satisfy approval relay json typing

* fix(codex): run approval relay in report mode

* fix(codex): keep relay pre-tool decisions deny-only

* fix(codex): remove dead relay approval branch

* fix(codex): dedupe app-server relay approvals

* fix(codex): fail closed on native relay rewrites

* fix(codex): preserve side-question provider context

* fix(codex): route side-question replies to origin

* fix(codex): preserve native hook channel context

* test(codex): align native relay rewrite assertion

* fix(codex): align side-question hook config

* fix(codex): route side-question approvals safely

* test(codex): fix side-question hook typing

* fix(codex): preserve side-question hook policy context

* fix(codex): close native hook relay review gaps

* fix(codex): keep dynamic tool hook channel context

* fix(codex): preserve native finalize hook channel context

* fix(codex): scope dynamic tool result hooks by channel

* fix(codex): drop stale deadcode allowlist entry

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 12:02:17 +01:00
Peter Steinberger
3fcc8b19ba feat(skills): add python debugpy skill 2026-05-17 11:56:31 +01:00
Peter Steinberger
ee492092a7 fix: yield responses streams to abort timers 2026-05-17 11:53:48 +01:00
Ayaan Zaidi
9e0386563f docs(changelog): note telegram media group warning (#82987) (thanks @eldar702) 2026-05-17 16:23:06 +05:30
Ayaan Zaidi
be934c0347 fix(telegram): warn on all failed media groups 2026-05-17 16:23:06 +05:30
eldar702
066ca3926a fix(telegram): enable the media-group skip-warning guard [AI-assisted]
The warning branch added in the previous commit was committed with an
always-false guard (`if (false && skippedCount > 0 && ...)`), so the
notification never fired — flagged by review as [P1]. Remove the
`false &&` so partial-album media loss actually notifies the user, as
the accompanying tests already expect.

Refs #55216
2026-05-17 16:23:06 +05:30
eldar702
9a45c0701b fix(telegram): warn when a media group silently drops failed photos [AI-assisted]
Telegram albums where some photos failed to download were processed
silently: the agent received only the photos that resolved, and the
user was never told images had been lost.

processMediaGroup now tracks a skippedCount (incremented on a
recoverable per-photo fetch error and on a null resolveMedia result).
When at least one photo still resolved, it emits a single anchored
warning per album (never per photo) using the same
withTelegramApiErrorLogging wrapper + swallowed-send pattern as the
existing single-attachment "Failed to download media" notice. The
all-failed-album case is intentionally left silent (out of scope).

Fixes #55216
2026-05-17 16:23:06 +05:30
Peter Steinberger
decbd611a0 docs: refresh embedded skill guidance 2026-05-17 11:50:27 +01:00
Peter Steinberger
d8198c8c0e fix: use Codex runtime context budget for compaction 2026-05-17 11:46:17 +01:00
Peter Steinberger
084318b8c4 docs: add Codex app-server guard changelog 2026-05-17 11:45:59 +01:00
Peter Steinberger
403fbd7296 fix: address Codex guard review findings 2026-05-17 11:45:59 +01:00
Peter Steinberger
a6908fac16 fix: honor custom Codex home for rollout guards 2026-05-17 11:45:59 +01:00
Peter Steinberger
4008ba56fc test: fix Codex app-server budget guard types 2026-05-17 11:45:59 +01:00
Peter Steinberger
e8e4b93a94 fix: harden Codex rollout budget scanning 2026-05-17 11:45:59 +01:00
Peter Steinberger
8e9961a945 fix: tighten Codex app-server budget guards 2026-05-17 11:45:59 +01:00
Han Kim
f86a0c8c9a Guard Codex app-server context budgets 2026-05-17 11:45:59 +01:00
Peter Steinberger
156e86afa4 fix: load source tool plugin entries with SDK aliases 2026-05-17 11:45:18 +01:00
Peter Steinberger
3dbe37c694 docs: refresh llm-task generated manifest 2026-05-17 11:45:18 +01:00
Peter Steinberger
439612bf56 docs: refresh plugin SDK API baseline 2026-05-17 11:45:18 +01:00
Peter Steinberger
4d05008283 fix: preserve tool plugin manifest metadata 2026-05-17 11:45:18 +01:00
Peter Steinberger
ae172741e1 feat: dogfood tool plugin helpers 2026-05-17 11:45:18 +01:00
Peter Steinberger
b95c8a4d95 docs: add tool plugin authoring guide 2026-05-17 11:45:18 +01:00
Peter Steinberger
b17e4ed50c feat: add simple tool plugin authoring 2026-05-17 11:45:18 +01:00
Peter Steinberger
0e76dafe42 test: avoid telegram startup abort deadlock 2026-05-17 11:42:37 +01:00
Peter Steinberger
51e93669cb test: relax oc-path perf budget in ci 2026-05-17 11:37:11 +01:00
Vincent Koc
10dd9c5aee fix(e2e): follow scoped configure prompts 2026-05-17 18:30:07 +08:00
Peter Steinberger
0165560f70 test: align plugin metadata test snapshots 2026-05-17 11:29:39 +01:00
Peter Steinberger
9feca3e11e fix: stabilize release validation gates 2026-05-17 11:24:01 +01:00
Peter Steinberger
8dd91b14d3 fix(google): recover Gemini tool-call thought signatures
Fixes #72879.
Supersedes contributor PR #80358; fork push was blocked despite maintainer edits being enabled.

Co-authored-by: abnershang <abner.shang@gmail.com>
2026-05-17 11:16:47 +01:00
Vincent Koc
5aac7939db fix(gateway): drain replies during restart close 2026-05-17 18:12:52 +08:00
hcl
42435d110b fix(browser): derive Chrome launch readiness from a single CDP diagnostic (#82904) (#82986)
* fix(browser): derive Chrome launch readiness from a single CDP diagnostic (#82904)

The pre-fix launch path used `isChromeReachable` (a lightweight HTTP
`/json/version` probe) to decide failure, then called the stronger
`diagnoseChromeCdp` only to format the thrown error. On macOS cold
starts where the HTTP probe transiently fails *between* the polling
loop and the diagnostic call, the runtime would throw

    "Failed to start Chrome CDP on port ... { ok: true, wsUrl: ... }"

— a self-contradicting error containing a successful diagnostic
result. Per #82904 this is the actual user-visible bug.

Capture `diagnoseChromeCdp` ONCE after the polling loop and use it for
both the decision and the error text. The diagnostic helper already
includes the lightweight reachability check and adds a websocket
`Browser.getVersion` health command, so it is strictly stronger than
the HTTP probe; if `diagnoseChromeCdp` returns ok the launch
genuinely succeeded.

The existing `withMockChromeCdpServer` success test in
chrome.internal.test.ts still exercises this code path end-to-end
(real HTTP server + real websocket handshake), so the regression-safety
case is covered. The asymmetric `probe-fails-but-diagnostic-succeeds`
scenario is hard to mock without restructuring the existing test
harness; this commit ships the fix and relies on the upstream
ClawSweeper review criteria (manual managed-Chrome cold-start proof)
plus the standalone real-behavior probe in the PR body.

* fix(browser): import ChromeCdpDiagnostic type from chrome.diagnostics

The annotation `let finalDiagnostic: ChromeCdpDiagnostic | null` referenced
a type that was only re-exported (not imported) inside chrome.ts, causing
oxlint/tsc to read it as the implicit `error` type and fail check-lint,
check-prod-types, check-test-types, etc. Add the type to the existing
chrome.diagnostics.js import block.

* fix(browser): preserve Chrome launch diagnostic fallback

* test(browser): satisfy launch diagnostic lint

* fix(browser): keep Chrome launch readiness scoped

* test(browser): answer CDP launch mock probe

---------

Co-authored-by: hclsys <hclsys@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 11:11:15 +01:00
Peter Steinberger
bf51933358 docs(skills): use neutral skill wording 2026-05-17 11:04:04 +01:00
Peter Steinberger
7e2d6ef06f fix(skills): keep spike scratch inside workspace 2026-05-17 11:04:04 +01:00
Peter Steinberger
0591b31388 feat(skills): add debugger diagram and spike skills
# Conflicts:
#	CHANGELOG.md
2026-05-17 11:04:04 +01:00
Vincent Koc
b8a6a387ee changelog: note gateway secrets startup fast path 2026-05-17 18:02:28 +08:00
Vincent Koc
540a4a73d5 fix(ci): handle missing SwiftLint in Testbox changed checks 2026-05-17 18:00:19 +08:00
Josh Avant
903d9c13f3 Fix subagent completion announce delivery timing (#83039)
* fix subagent announce transcript delivery

* chore changelog for subagent announce delivery

* test align subagent retry suspension expectation
2026-05-17 04:59:58 -05:00
Peter Steinberger
0177a4b6c9 fix(gateway): speed up secrets startup
Summary:
- Split the lightweight secrets runtime state and auth-store cache from the full secrets runtime.
- Use the startup fast path whenever gateway startup has no SecretRef values, while preserving cleanup and refresh semantics.
- Add regression coverage for startup-only empty auth-store snapshots and update affected gateway/tool tests.

Verification:
- pnpm test src/secrets/runtime.fast-path.test.ts src/secrets/runtime-state.test.ts src/gateway/server-startup-config.secrets.test.ts src/gateway/server-import-boundary.test.ts src/gateway/server-aux-handlers.test.ts src/gateway/server-methods/config.shared-auth.test.ts src/agents/tools/web-tools.enabled-defaults.test.ts src/agents/tools/web-tool-runtime-context.test.ts -- --reporter=verbose
- pnpm build
- pnpm format:check -- src/agents/tools/web-tools.enabled-defaults.test.ts src/secrets/runtime-command-secrets.ts src/secrets/runtime-fast-path.ts src/secrets/runtime.fast-path.test.ts src/agents/auth-profiles/store.ts src/agents/auth-profiles/store-cache.ts src/secrets/runtime-state.ts src/secrets/runtime-state.test.ts src/gateway/server-startup-config.ts
- codex-review --mode branch
- isolated gateway token-auth smoke: openclaw gateway run + openclaw gateway health returned ok: true
- GitHub CI on PR #83031 green; newer Real behavior proof run passed on current SHA f27ed3f7ce.

Co-authored-by: samzong <samzong.lu@gmail.com>
2026-05-17 10:55:41 +01:00
Josh Avant
f29bcff4da fix(models): reuse plugin metadata snapshot (#83033)
* fix(models): reuse plugin metadata snapshot

* docs: add models performance changelog

* test: satisfy models metadata fixture types
2026-05-17 04:51:59 -05:00
Peter Steinberger
9616aa6e5a build(protocol): refresh gateway secrets models 2026-05-17 10:42:57 +01:00
Peter Steinberger
d66fe50a10 fix(cli): preserve optional web fallback secrets
Co-authored-by: wuyangfan <1102042793@qq.com>
2026-05-17 10:42:57 +01:00
Peter Steinberger
e3a248585e fix(cli): preserve scoped secret resolution
Co-authored-by: wuyangfan <1102042793@qq.com>

# Conflicts:
#	src/cli/capability-cli.test.ts
#	src/cli/capability-cli.ts
2026-05-17 10:42:57 +01:00
Peter Steinberger
fe680e47ce fix(cli): scope web command secret refs 2026-05-17 10:42:57 +01:00
wuyangfan
230806eaf2 fix(cli): resolve plugin web search SecretRefs for infer web search
Materialize agent-runtime plugin credentials through the shared command
secret resolution path before local web search/fetch runs, matching gateway
runtime behavior for plugins.entries.*.config.webSearch.apiKey refs.

Fixes openclaw/openclaw#82621

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 10:42:57 +01:00
Peter Steinberger
6f024293e0 fix: surface reply media failures
Co-authored-by: Jerry-Xin <jerryxin0@gmail.com>
2026-05-17 10:39:28 +01:00
Vincent Koc
5d4dac690c changelog: note qa-lab multipass temp root 2026-05-17 17:37:36 +08:00
Peter Steinberger
2df393886a test: align cron schema description assertions 2026-05-17 10:34:20 +01:00
Peter Steinberger
6eeba8cfb4 docs: note clean tool schema cleanup 2026-05-17 10:34:20 +01:00
Peter Steinberger
9b698ce0d6 refactor: shorten agent tool descriptions 2026-05-17 10:34:20 +01:00
Vincent Koc
cfb032797f fix(qa-lab): wake stale cursor long polls 2026-05-17 17:33:47 +08:00
Peter Steinberger
3e6902236c style(mac): refine settings panes 2026-05-17 10:31:04 +01:00
Vincent Koc
a4bea46a35 fix(codex): preserve nested tool-result middleware output 2026-05-17 17:30:58 +08:00
Vincent Koc
37dcf385e5 fix(qa): expose codex tools for runtime parity 2026-05-17 17:20:12 +08:00
Vincent Koc
2c9f68f42b changelog: note qa-lab tool coverage command 2026-05-17 17:17:55 +08:00
Vincent Koc
1f9d8c1e9d fix(qa-lab): wire tool coverage report command 2026-05-17 17:12:10 +08:00
Vincent Koc
00fc2950d9 chore(scripts): harden dev tooling diagnostics 2026-05-17 17:04:18 +08:00
Vincent Koc
54d063167e test: use platform spy helper in cli tests 2026-05-17 17:03:23 +08:00
Vincent Koc
673596013e changelog: note Together thinking format config support 2026-05-17 17:02:29 +08:00
Peter Steinberger
7b96109920 ci: include skill scripts in duplicate scan 2026-05-17 09:58:24 +01:00
Peter Steinberger
b7704b917e feat(skills): add meme maker skill 2026-05-17 09:58:24 +01:00
Vincent Koc
85f8fd0533 test: reuse platform spy helper in infra tests 2026-05-17 16:58:18 +08:00
Vincent Koc
9ca98a6d39 fix(config): accept together thinking format 2026-05-17 16:55:51 +08:00
Vincent Koc
d217fd7a92 test(qa-lab): add runtime tool fixtures 2026-05-17 16:55:50 +08:00
Vincent Koc
46061442e7 test: share process platform spy helper 2026-05-17 16:52:46 +08:00
samzong
3c1c850c02 fix(acpx): keep startup probe in runtime service
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-17 09:47:20 +01:00
Peter Steinberger
5425ecc1aa style(macos): apply SwiftFormat 2026-05-17 09:46:30 +01:00
Josh Avant
8ba2dfa76a Fix message tool session-key route drift (#83004)
* fix message tool session-key route drift

* docs changelog for message tool session-key route
2026-05-17 03:36:14 -05:00
Peter Steinberger
69d588cf2a fix(openai): remove GPT reply brevity cap 2026-05-17 09:29:11 +01:00
Vincent Koc
37806afd2d chore(plugins): bump tokenjuice to 0.7.1 2026-05-17 16:23:26 +08:00
Josh Avant
022723829a fix(agents): preserve suspended subagent final deliveries (#82999)
* fix: preserve suspended subagent final deliveries

* chore: update changelog for subagent delivery fix

* test: use valid killed subagent outcome fixture
2026-05-17 03:23:15 -05:00
Bob
80d03a1e5b fix: classify provider conversation state errors (#82616)
Classify provider conversation-state rejections and return a clear message-channel error instead of auto-resetting or falling back to a generic runner failure.

Local validation:
- pnpm docs:list
- pnpm build
- pnpm check
- node scripts/run-vitest.mjs src/auto-reply/reply/provider-request-error-classifier.test.ts src/auto-reply/reply/agent-runner-execution.test.ts src/auto-reply/reply/dispatch-from-config.test.ts
- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts

Co-authored-by: dutifulbob <261991368+dutifulbob@users.noreply.github.com>
2026-05-17 16:22:09 +08:00
Vincent Koc
2547e35b0a test(qa-lab): add harness sentinel scenarios 2026-05-17 16:19:58 +08:00
Peter Steinberger
3cfac6d430 fix(browser): harden CLI wait and option handling 2026-05-17 09:19:14 +01:00
Vincent Koc
3918d69587 fix(agents): skip malformed transcript state entries (#82624)
* fix(agents): skip malformed transcript state entries

* fix(agents): preserve repairable transcript tool calls

* fix(agents): preserve openclaw transcript content blocks

* fix(agents): preserve string tool call arguments

* fix(agents): keep repaired compaction markers on branch

* fix(agents): keep legacy assistant transcript text

* fix(agents): preserve null tool call arguments

* fix(agents): keep transcript repair chains

* fix(agents): drop labels for rejected transcript rows

* fix(agents): preserve legacy transcript compaction indexes

* fix(agents): drop unresolved transcript repair parents
2026-05-17 16:18:23 +08:00
Peter Steinberger
e53ba8fcf5 fix(provider): use Together video API endpoint
Route Together video generation through the v2 video API even when shared Together text config points at the v1 base URL.

Verification:
- pnpm test extensions/together/video-generation-provider.test.ts
- pnpm check:test-types
- git diff --check
- codex-review --parallel-tests "pnpm test extensions/together/video-generation-provider.test.ts"
- gh pr checks 82992 --watch --fail-fast=false
2026-05-17 09:16:44 +01:00
samzong
dd32c5307f fix(gateway): reuse startup auth preflight snapshot
Summary:
- Reuse the prepared gateway startup auth SecretRef snapshot when the startup config still matches the preflight source.
- Preserve fresh activation fallback for config mismatches and shared weak-token, warning, and recovery handling.
- Add focused regression coverage and changelog entry.

Verification:
- pnpm test src/gateway/server-startup-config.secrets.test.ts
- GitHub checks green on 72587758ee
2026-05-17 01:12:21 -07:00
Peter Steinberger
83e19ca469 fix: keep ACP turns on OpenClaw timeouts (#82997) 2026-05-17 09:10:42 +01:00
Peter Steinberger
e4ec1b3de8 test(cli): clarify gateway model-run session proof (#82861) (thanks @Kaspre) 2026-05-17 09:08:02 +01:00
Kaspre
820ec9d3be test(cli): assert fresh gateway model-run sessions 2026-05-17 09:08:02 +01:00
Kaspre
0fd152b286 fix(cli): isolate gateway model run sessions 2026-05-17 09:08:02 +01:00
Peter Steinberger
a5b1177b68 fix(codex): preserve agent scope for bound app-server sessions 2026-05-17 09:03:05 +01:00
Peter Steinberger
993fe3ef0f fix(mac): polish settings window chrome 2026-05-17 08:59:32 +01:00
Peter Steinberger
a985c99059 fix: steer song requests to music generation 2026-05-17 08:57:27 +01:00
Vincent Koc
327b0b8734 changelog: credit Codex native tool trajectory recording 2026-05-17 15:50:41 +08:00
Peter Steinberger
5d1f7bf058 fix: route image URL describes through MiniMax VLM
Summary:
- Preserve HTTP image describe inputs as remote media.
- Route MiniMax CN image understanding through MiniMax-VL-01.
- Cover CLI, media runtime, tools, Telegram stickers, docs, and changelog.

Verification:
- codex-review clean
- pnpm check:changed via Blacksmith Testbox tbx_01krtdekwak0mygxbw5z7cfb6z
- PR CI green on 516281448e
2026-05-17 08:45:50 +01:00
Peter Steinberger
9a36e897be fix: surface async media generation failures 2026-05-17 08:40:33 +01:00
Peter Steinberger
635b947e32 fix(acp): honor terminal turn results 2026-05-17 08:25:14 +01:00
Peter Steinberger
1f6ababb63 fix(mac): keep settings panes warm 2026-05-17 08:18:27 +01:00
Evgeny Yurchenko
592aae3696 fix: avoid idle Codex hook relay subprocesses
Avoid installing Codex native PostToolUse/Stop hook relays when OpenClaw has no matching local handlers. This keeps pre-tool safety and permission approval relays active while removing idle no-op subprocess fan-out.

Fixes #76552.

Co-authored-by: evgyur <evgyur@users.noreply.github.com>
2026-05-17 08:17:51 +01:00
Peter Steinberger
6b688ed614 docs: clarify ambient room recommendation 2026-05-17 08:16:58 +01:00
Peter Steinberger
77547226ce fix: improve progress draft truncation 2026-05-17 08:13:39 +01:00
Peter Steinberger
76da34760c fix(mac): speed up config settings 2026-05-17 08:03:10 +01:00
joshavant
c30c8cb471 test(tasks): expect classified ACP stall update 2026-05-17 01:50:07 -05:00
Peter Steinberger
d1638f1185 fix(codex): record native tool trajectories
Co-authored-by: vyctorbrzezowski <krzyszchweski@gmail.com>
2026-05-17 07:43:28 +01:00
Josh Avant
7d99f8b021 fix(gateway): allow trusted-proxy local-direct password fallback (#82953)
* fix(gateway): restore trusted-proxy local password fallback

* docs(changelog): note trusted-proxy password fallback fix

* docs(changelog): clarify trusted-proxy fallback policy
2026-05-17 01:35:59 -05:00
Galin Iliev
8dc213227b docs: add prompt cache changelog 2026-05-17 06:30:50 +00:00
Galin Iliev
a656f887c8 fix: satisfy OpenAI tool payload lint 2026-05-17 06:30:50 +00:00
Galin Iliev
afdb8705e9 fix: stabilize OpenAI tool payload ordering 2026-05-17 06:30:49 +00:00
YB0y
6720aa9c42 fix(cli): add sessions list alias (#81163) (thanks @YB0y) 2026-05-17 07:27:53 +01:00
Galin Iliev
aca258a8a9 fix: explain memory compaction tool allowlist warnings
Fixes #82941.
2026-05-16 23:25:00 -07:00
Peter Steinberger
aaadf721e3 fix(agents): classify ACP no-output stalls 2026-05-17 07:23:41 +01:00
Peter Steinberger
a46d2e2b06 docs: add ambient room events guide 2026-05-17 07:20:15 +01:00
Vincent Koc
684a6303b3 changelog: add OpenAI Codex runtime routing and OAuth bootstrap (#82864) 2026-05-17 14:15:56 +08:00
Peter Steinberger
669786595d fix(logging): avoid false liveness backlog warnings 2026-05-17 07:15:17 +01:00
Josh Avant
9a063e38d1 Fix TTS supplement delivery across live previews (#82935)
* fix: avoid duplicated tts supplement replies

* chore: add changelog for tts supplement fix
2026-05-17 01:15:12 -05:00
Zavian Wang
9a11e76458 fix(plugins): surface configured runtime plugin doctor warnings (#81674)
Fixes #81326.

Summary:
- Warn from `openclaw plugins doctor` when configured runtime owner plugins are missing, disabled, or blocked.
- Share configured-runtime plugin install mapping with `openclaw doctor --fix`, including ACP/acpx.
- Keep implicit OpenAI Codex preferences quiet to avoid false-positive plugin doctor warnings.

Verification:
- `pnpm test src/agents/harness-runtimes.test.ts src/cli/plugins-cli.list.test.ts src/commands/doctor/shared/missing-configured-plugin-install.test.ts -- --reporter=verbose`
- `pnpm exec oxfmt --check CHANGELOG.md src/agents/harness-runtimes.ts src/agents/harness-runtimes.test.ts src/cli/plugins-cli.runtime.ts src/cli/plugins-cli.list.test.ts src/commands/doctor/shared/configured-runtime-plugin-installs.ts src/commands/doctor/shared/missing-configured-plugin-install.ts`
- `pnpm build:plugin-sdk:dts`
- `codex-review --mode branch`
- Testbox-through-Crabbox `pnpm check:changed`: provider `blacksmith-testbox`, id `tbx_01krt8vte22m7ht6wfss4jkeaa`, Actions run https://github.com/openclaw/openclaw/actions/runs/25983150787, exit 0

Co-authored-by: Zavian Wang <36817799+Zavianx@users.noreply.github.com>
2026-05-17 07:13:55 +01:00
Vincent Koc
826c2f4517 test(qa-lab): add codex read vocabulary canary 2026-05-17 14:12:50 +08:00
ragesaq
58f1db1bc8 Fix OpenAI Codex runtime provider routing (#82864)
* fix: route Codex OpenAI runtime through Codex provider

* docs: add Codex routing evidence collection

* fix(agents): bootstrap OAuth credentials for Codex harness with openai/* model refs

When a plugin harness (e.g. Codex) owns its transport but the runtime
plan resolved to openai-codex via agentRuntime.id: codex, the auth
profile store was left empty because pluginHarnessOwnsTransport short-
circuited initializeAuthProfile(). This caused 'No API key found for
openai-codex' at runtime even though the OAuth profile existed in OpenClaw's
store.

- Add pluginHarnessNeedsOpenClawAuthBootstrap flag when harness owns
transport but the provider is openai-codex and the API is openai-codex-
responses
- Populate authStore and attemptAuthProfileStore from OpenClaw's profile
store in this case
- Run initializeAuthProfile() to forward the OAuth token into the harness
- Update overflow-compaction tests to expect 'openai-codex' provider
  and add dedicated test for OAuth bootstrap path

* fix(agents): refresh Codex OAuth credentials on profile rotation

---------

Co-authored-by: PsiClawOps <267826480+PsiClawOps@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 07:06:18 +01:00
Peter Steinberger
451563b950 ci: allow Tideclaw alpha release workflows 2026-05-17 07:00:53 +01:00
Vincent Koc
e66a6c8c8d test(qa-lab): add runtime parity depth scenarios 2026-05-17 13:50:18 +08:00
Peter Steinberger
16ef041b5d fix(channels): preserve implicit default accounts 2026-05-17 06:42:28 +01:00
Peter Steinberger
71b79f008d fix: sanitize Codex image payload replay (#82931) 2026-05-17 06:42:21 +01:00
Gio Della-Libera
b7f3d01633 fix(mcp): inline local refs in bundled tool schemas (#81238) 2026-05-16 22:41:11 -07:00
Peter Steinberger
ad155fbbd7 fix(gateway): restore v4 message action protocol 2026-05-17 06:35:39 +01:00
Peter Steinberger
c3e2b3c323 docs: sync plugin generated references 2026-05-17 06:34:58 +01:00
Peter Steinberger
1ceebf8a01 ci: harden release publish evidence 2026-05-17 06:34:58 +01:00
Peter Steinberger
c4d8e0be18 ci: harden release validation flow 2026-05-17 06:34:58 +01:00
Peter Steinberger
a535978352 fix(gateway): explain protocol mismatches 2026-05-17 06:34:04 +01:00
Peter Steinberger
06ec6b0fca fix(mac): speed up channels settings 2026-05-17 06:34:04 +01:00
Peter Steinberger
38b3e73622 fix: improve gateway protocol mismatch diagnostics (#82908)
* fix: improve gateway protocol mismatch diagnostics

* test: cover daemon deep connection diagnostics

* fix: normalize mapped loopback gateway clients
2026-05-17 06:33:34 +01:00
Peter Steinberger
926a5a825f test(channels): avoid catalog scan in command account tests 2026-05-17 06:30:09 +01:00
Gio Della-Libera
9ac7773b7f fix(node): hide Windows task launcher (#81267) 2026-05-16 22:25:33 -07:00
Gio Della-Libera
5817e478d1 fix(agents): clear poisoned claude cli sessions (#81247) 2026-05-16 22:25:09 -07:00
Jesse Merhi
7c2425a518 Support HTTPS managed proxy CA trust (#79171)
* fix: support HTTPS managed proxy CA trust

* fix: strip IP SNI for HTTPS proxy dispatchers

* fix: harden managed proxy undici dispatchers

* docs: refresh proxy baselines

* fix: drop stale whatsapp undici dependency

* fix: satisfy proxy dispatcher lint and tests

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 06:23:30 +01:00
Peter Steinberger
421b9e2819 fix: restore Codex snapshot tool progress (#82917)
# Conflicts:
#	CHANGELOG.md
2026-05-17 06:20:59 +01:00
Peter Steinberger
3fad770510 fix: update PI runtime packages 2026-05-17 06:12:09 +01:00
Peter Steinberger
6a8a6551fc test(discord): cover durable chunk retry delivery (#82898)
* test(discord): cover durable chunk retry delivery

* test(discord): use plugin sdk test runtime

* fix(telegram): satisfy message cache strict checks

* test(discord): include durable delivery in changed lane
2026-05-17 06:11:58 +01:00
github-actions[bot]
df23b0f86c chore(ui): refresh fa control ui locale 2026-05-17 05:11:38 +00:00
github-actions[bot]
5a350431bd chore(ui): refresh nl control ui locale 2026-05-17 05:11:29 +00:00
github-actions[bot]
cb71ad5a60 chore(ui): refresh vi control ui locale 2026-05-17 05:11:13 +00:00
github-actions[bot]
7899e99852 chore(ui): refresh pl control ui locale 2026-05-17 05:11:05 +00:00
github-actions[bot]
f12b6fa67c chore(ui): refresh th control ui locale 2026-05-17 05:11:00 +00:00
github-actions[bot]
1d2aa4db61 chore(ui): refresh id control ui locale 2026-05-17 05:10:56 +00:00
github-actions[bot]
a16150c7e2 chore(ui): refresh uk control ui locale 2026-05-17 05:10:35 +00:00
github-actions[bot]
09c8f972eb chore(ui): refresh it control ui locale 2026-05-17 05:10:24 +00:00
github-actions[bot]
6c5f97d0d0 chore(ui): refresh tr control ui locale 2026-05-17 05:10:22 +00:00
github-actions[bot]
17109bc253 chore(ui): refresh ar control ui locale 2026-05-17 05:10:15 +00:00
Agustin Rivera
9b96f81327 fix(skills): honor tool policy for inline dispatch (#78525)
* fix(skills): honor tool policy for inline dispatch

* fix(skills): cover inline dispatch policy gaps

* fix(skills): align inline dispatch policy

* fix(skills): add inline dispatch policy changelog
2026-05-16 22:09:53 -07:00
github-actions[bot]
6da6abdb55 chore(ui): refresh es control ui locale 2026-05-17 05:09:50 +00:00
github-actions[bot]
10a0c43872 chore(ui): refresh fr control ui locale 2026-05-17 05:09:48 +00:00
github-actions[bot]
743ad4f296 chore(ui): refresh ja-JP control ui locale 2026-05-17 05:09:45 +00:00
github-actions[bot]
f5d0345feb chore(ui): refresh ko control ui locale 2026-05-17 05:09:40 +00:00
github-actions[bot]
8901cf8625 chore(ui): refresh zh-TW control ui locale 2026-05-17 05:09:13 +00:00
github-actions[bot]
5fddcfaefe chore(ui): refresh pt-BR control ui locale 2026-05-17 05:09:09 +00:00
github-actions[bot]
b7893fc158 chore(ui): refresh de control ui locale 2026-05-17 05:09:05 +00:00
github-actions[bot]
6ca0cd4337 chore(ui): refresh zh-CN control ui locale 2026-05-17 05:09:02 +00:00
Peter Steinberger
84ec0c27bf [codex] Add Control UI sidebar session shortcuts (#82810)
* feat(control-ui): add sidebar session shortcuts

* fix(control-ui): simplify cron job creation

* style(control-ui): pad settings config chrome

* fix(control-ui): fold cron advanced creation into dialog

* fix(control-ui): filter sidebar recent sessions

* fix(control-ui): clean up chat loading state

* fix(control-ui): add settings nav content gutter

* fix(control-ui): inherit settings nav icon color

* fix(control-ui): polish sidebar new session button

* fix(control-ui): speed up communications settings load

* fix(control-ui): add cron strings to locale maps

* fix(control-ui): refresh i18n metadata for sidebar strings

* fix(control-ui): refresh chat raw-copy baseline
2026-05-17 06:07:14 +01:00
hcl
f2d8f38315 fix(followup): route CLI runtime drains through CLI runner (#82847) (#82857)
* fix(followup): route CLI runtime drains through CLI runner

* fix(followup): route queued CLI runtimes

---------

Co-authored-by: hclsys <hclsys@openclaw.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 06:07:08 +01:00
Josh Avant
422a1374e0 Fix silent success for non-deliverable Bedrock Telegram turns (#82905)
* fix: handle non-deliverable terminal turns

* chore: add changelog for non-deliverable turns

* fix: align telegram message cache types
2026-05-16 23:57:52 -05:00
Ayaan Zaidi
ee10fe17f0 fix(telegram): preserve reply-chain context (#82863) 2026-05-17 10:04:02 +05:30
Ayaan Zaidi
741eafea5f fix(telegram): distinguish partial reply snapshots 2026-05-17 10:04:02 +05:30
Ayaan Zaidi
8880a5827a test(telegram): prove bot reply-chain context 2026-05-17 10:04:02 +05:30
Ayaan Zaidi
440e7d2a87 fix(telegram): preserve reply-chain context 2026-05-17 10:04:02 +05:30
Peter Steinberger
abb06c6e40 fix(gateway): require auth for exposed startup
Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
2026-05-17 05:32:26 +01:00
Galin Iliev
18812bfc03 fix(process): clarify lane wait diagnostics (#82792)
Merged via squash.

Prepared head SHA: 1a09b724a5
Co-authored-by: galiniliev <5711535+galiniliev@users.noreply.github.com>
Reviewed-by: @galiniliev
2026-05-16 21:26:31 -07:00
Galin Iliev
150179def7 fix(ui): track gateway protocol constants
Fixes #82882.

Browser Control UI connect frames now advertise the shared Gateway protocol constants instead of stale protocol 4 literals, and the node UI gateway test asserts the emitted protocol range.
2026-05-16 21:25:05 -07:00
Gio Della-Libera
bd51d8f2dd Deduplicate preview-streamed final replies (#82625)
Track the latest partial-preview reply text during reply-agent runs and suppress matching final text-only payloads so Telegram partial streaming does not resend already-previewed blocks when block streaming is disabled.

Keep the dedupe exact-match based to avoid dropping unrelated short finals, preserve errors, and keep unsent media while stripping duplicate caption text.
2026-05-16 21:24:34 -07:00
Gio Della-Libera
5c02b72413 Preserve authored config metadata in doctor (#82687)
* Preserve authored config metadata in doctor

* Preserve legacy default model during doctor repair

Keep defaultModel out of the public schema while allowing doctor repair writes to preserve the legacy root metadata key.
2026-05-16 21:23:32 -07:00
Peter Steinberger
6a1b167472 fix: improve mac settings performance 2026-05-17 05:21:47 +01:00
Josh Avant
7d1317634e fix(agents): use current assistant final payloads (#82850) 2026-05-16 23:20:43 -05:00
Peter Steinberger
b77077f4d1 fix(github-copilot): request identity-encoded API responses 2026-05-17 05:20:06 +01:00
Peter Steinberger
5d81c29cc4 fix: reconcile subagent wait timeouts
Fixes #82787 by keeping session-backed parent subagent runs active when agent.wait only hits a poll timeout before the child session settles. Refactors terminal session-store reconciliation into a shared helper and rejects stale terminal rows from reused child sessions.

Verification:
- CodexReview clean
- pnpm test src/agents/subagent-registry.test.ts src/agents/subagent-registry.lifecycle-retry-grace.e2e.test.ts src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts -- --reporter=dot
- git diff --check
- pnpm check:changed via Blacksmith Testbox tbx_01krt1rxpkb7vj53mkaqwfserq
- GitHub CI/CodeQL/OpenGrep/Workflow Sanity green; proof gate covered by maintainer proof: override label
2026-05-17 05:16:36 +01:00
Peter Steinberger
06e85d5eaf fix: honor explicit message tool allowlists (#82889) 2026-05-17 05:11:49 +01:00
Neerav Makwana
2c549ae205 fix(cli): support image describe urls (#82854)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 05:06:27 +01:00
Josh Avant
ab2943e2ff fix(update): repair configured plugins during legacy upgrades (#82859)
* fix(update): repair configured plugins during legacy upgrades

* docs(changelog): note legacy plugin upgrade repair

* test(update): use valid whatsapp upgrade fixture
2026-05-16 22:49:07 -05:00
Galin Iliev
91ae1a6c03 fix(agents): split embedded attempt dispatch timing
Split embedded-run startup diagnostics into attempt-workspace, attempt-prompt, attempt-runtime-plan, and final attempt-dispatch subspans. Adds focused timing formatter coverage and a changelog entry. Fixes #82782.
2026-05-16 20:44:24 -07:00
Alex Knight
9bb4d1377a Fix brew-only skill installs in Docker (#82845)
Summary:
- The branch hides brew-only skill dependency installers during Linux-container onboarding when Homebrew is unavailable, adds container-specific missing-brew guidance, and updates docs, tests, i18n, and changelog text.
- Reproducibility: yes. Current main source inspection shows onboarding can offer a brew-only missing skill su ... ric missing-brew failure; the PR body also includes Testbox container output for before and after behavior.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head a4842f3a7d.
- Required merge gates passed before the squash merge.

Prepared head SHA: a4842f3a7d
Review: https://github.com/openclaw/openclaw/pull/82845#issuecomment-4468958593

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-17 03:37:51 +00:00
Alex Knight
46fde2bde2 Fix isolated best-effort cron delivery with active subagents (#82843)
Summary:
- The branch gates isolated cron descendant waits and active-descendant delivery suppression on non-best-effort delivery, adds focused regression coverage, and records an unreleased changelog fix.
- Reproducibility: yes. Source inspection on current main shows the best-effort path reaches the full descenda ... nt suppression without checking deliveryBestEffort; the PR body also records before/after Testbox evidence.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 1a4680126f.
- Required merge gates passed before the squash merge.

Prepared head SHA: 1a4680126f
Review: https://github.com/openclaw/openclaw/pull/82843#issuecomment-4468954163

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-17 03:37:31 +00:00
Peter Steinberger
549a0ea313 fix(discord): recover truncated progress finals
Summary:
- Add shared SDK helpers for transcript-backed recovery of ellipsis-truncated final text.
- Use the helper in Discord progress preview delivery so long answers fall through to normal chunked delivery with the full transcript text.
- Refactor Telegram to reuse the shared helper.

Verification:
- node scripts/run-vitest.mjs src/plugin-sdk/channel-streaming.test.ts extensions/discord/src/monitor/message-handler.process.test.ts
- pnpm exec oxfmt --check --threads=1 src/plugin-sdk/channel-streaming.ts src/plugin-sdk/channel-streaming.test.ts extensions/telegram/src/lane-delivery-text-deliverer.ts extensions/telegram/src/lane-delivery.ts extensions/telegram/src/bot-message-dispatch.ts extensions/discord/src/monitor/message-handler.process.ts extensions/discord/src/monitor/message-handler.process.test.ts
- node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo
- git diff --check
- pnpm check:changed via Blacksmith Testbox tbx_01krsy80a5qgfw790nm45770xt
- GitHub PR checks green on #82862
- codex-review --mode local: clean, no accepted/actionable findings

Fixes #82807.
2026-05-17 04:26:35 +01:00
Josh Avant
39a9a3478f Fix heartbeat runner failure copy (#82848)
* fix: scope heartbeat runner failures

* chore: add heartbeat failure changelog
2026-05-16 22:23:22 -05:00
Peter Steinberger
45d9a09485 fix: preserve Signal group session IDs (#82853) 2026-05-17 04:20:51 +01:00
WhatsSkiLL
f562690612 fix(sessions): prune malformed missing transcript rows (#82745)
Fix sessions cleanup pruning for malformed missing transcript rows and preserve metadata-only session rows.

Thanks @IWhatsskill.

Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@users.noreply.github.com>
2026-05-17 04:19:56 +01:00
Peter Steinberger
b29152e3b9 fix(cron): track claimed reply hooks as execution 2026-05-17 04:12:40 +01:00
Peter Steinberger
b328f57bc3 fix(channels): show missing external channel config (#82849) 2026-05-17 04:10:26 +01:00
Marcus Castro
5040eb5d84 fix: add WhatsApp document fallback extensions (#82851) 2026-05-17 00:09:51 -03:00
Vincent Koc
9b5f5b8651 changelog: add memory startup catch-up and telegram default account preservation 2026-05-17 11:00:37 +08:00
Peter Steinberger
d887eb8dc2 fix(agents): harden subagent completion delivery
Co-authored-by: Galin Iliev <galini@microsoft.com>
Co-authored-by: Ava Daigo <theavadaigo@gmail.com>
Co-authored-by: Moeed Ahmed <moeedahmed@users.noreply.github.com>
2026-05-17 03:48:25 +01:00
Vincent Koc
d801d27dbc fix(qa-lab): add gateway log sentinels 2026-05-17 10:45:14 +08:00
Peter Steinberger
ca236d098d fix: harden gateway launchd and configure sections 2026-05-17 03:44:05 +01:00
Peter Steinberger
524185a68e fix(exec): bind approval trust to realpaths (#82825) 2026-05-17 03:41:50 +01:00
Gio Della-Libera
8af2af24a5 fix(memory): catch up stale sessions on startup (#82341)
* fix(memory): catch up stale sessions on startup

Add a startup catch-up scan for memory session source files so clean gateway restarts compare on-disk transcripts against persisted index file state and mark only missing/newer/resized session files dirty for a normal incremental sync.

* fix(memory): catch up sessions for cli indexing

Ensure one-shot memory index managers also compare session transcripts against persisted source state before no-force CLI syncs, so openclaw memory index can recover stale session rows without requiring --force.

* chore: refresh CI after main repairs
2026-05-16 19:39:55 -07:00
hcl
845da0ed8f fix(gateway): avoid blocking usage cache refreshes (#82778)
- Refresh selected session usage summaries with bounded background work instead of blocking Gateway responses on full transcript scans.
- Persist transcript-level usage metadata so cached full and ranged summaries preserve totals, model usage, tool usage, latency, and time buckets.
- Add regression coverage for background refresh, range derivation, cache-version invalidation, append-only upgrades, and untimestamped usage.

Fixes #82773.

Co-authored-by: hclsys <hclsys@openclaw.ai>
2026-05-17 03:35:15 +01:00
Gio Della-Libera
c4f20b656e fix(telegram): preserve implicit default account (#82794)
Keep the top-level Telegram default account in the account list when named accounts or bindings are added alongside top-level credentials. This preserves default polling while still allowing named-only configs to resolve to their single configured account.
2026-05-16 19:30:52 -07:00
Youssef Hemimy
94ed68bc76 fix(whatsapp): honor forceDocument flag end-to-end (#79272)
Merged via squash.

Prepared head SHA: faaff35f1e
Co-authored-by: itsuzef <53057646+itsuzef@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-05-16 23:29:01 -03:00
Peter Steinberger
c1bc6adfaa fix: restore busy TUI draft 2026-05-17 03:26:33 +01:00
Harry Xie
2c7200f542 fix(tui): preserve draft while chat is busy 2026-05-17 03:26:33 +01:00
Peter Steinberger
ab595dec0f fix: normalize malformed assistant replay content (#82748) 2026-05-17 03:26:04 +01:00
IWhatsskill
ad8ae05f37 fix(agents): normalize malformed assistant content 2026-05-17 03:26:04 +01:00
Peter Steinberger
1896f8a330 fix: resolve installed plugin facade dist surfaces 2026-05-17 03:25:42 +01:00
Vincent Koc
c8e12ca01d changelog: add browser MCP redaction and MEMORY/TTS guidance (#81930) 2026-05-17 10:23:47 +08:00
gleb
9f112a1a7a fix: include checked credential source in missing auth errors
Include the checked credential source in missing API key errors so users can see which env var, profile, or config path to fix.

Fixes #82785.

Co-authored-by: gleb <116607327+loeclos@users.noreply.github.com>
2026-05-17 03:21:57 +01:00
Gio Della-Libera
6821fbcfba Clarify MEMORY guidance over generic TTS hints (#81930) 2026-05-16 19:21:24 -07:00
Peter Steinberger
9e67f53b91 fix(cli): resolve web command SecretRefs
Fix CLI web search/fetch command SecretRef resolution for provider-scoped plugin credentials.

- Carry command provider overrides through gateway and local secret resolution.
- Mark the selected web provider targets active and unrelated plugin refs inactive.
- Cover Tavily, DuckDuckGo, legacy Firecrawl fetch, protocol overrides, and runtime command-secret behavior.
- Add public plugin-sdk test mock exports needed by existing plugin tests after CI boundary enforcement.

Fixes #82621.
Replacement for #82699.

Co-authored-by: 吴杨帆 <39647285+leno23@users.noreply.github.com>
2026-05-17 03:00:39 +01:00
Vincent Koc
ecb9028f9f fix(browser): redact chrome mcp attach details 2026-05-17 09:53:18 +08:00
Vincent Koc
55e4b76bb2 fix(browser): preserve raw chrome launch diagnostics 2026-05-17 09:53:18 +08:00
Vincent Koc
82e8b5232d fix(browser): redact chrome bridge diagnostics 2026-05-17 09:53:17 +08:00
Vincent Koc
d183fa3095 changelog: note Copilot replay item-id collision fix 2026-05-17 09:52:35 +08:00
Vincent Koc
d62e443b36 fix(plugins): honor parent bundled source overlays 2026-05-17 09:52:19 +08:00
Vincent Koc
817f0cd6c8 fix(plugins): preserve bundled source overlay probes 2026-05-17 09:52:19 +08:00
Vincent Koc
c8b5757303 fix(plugins): try runtime package-state probes before source 2026-05-17 09:52:19 +08:00
Vincent Koc
634a766347 docs(changelog): note bundled plugin path hardening 2026-05-17 09:52:19 +08:00
Vincent Koc
662fcb81c9 fix(plugins): align bundled runtime surface roots 2026-05-17 09:52:19 +08:00
Vincent Koc
77f7c8df8d fix(plugins): contain bundled entry paths 2026-05-17 09:52:19 +08:00
Peter Steinberger
2a7f9f3546 fix: avoid Copilot replay item ID collisions 2026-05-17 02:43:44 +01:00
Peter Steinberger
f2a0b3d2e2 chore(crabbox): warn about raw aws runtime commands 2026-05-17 02:38:10 +01:00
Vincent Koc
d350ac3feb test: use platform spy helpers 2026-05-17 09:24:42 +08:00
Peter Steinberger
7ee5fe011b refactor(agents): share model manifest context 2026-05-17 02:24:07 +01:00
Vincent Koc
da8afe359d feat(qa-lab): add scenario pack selector 2026-05-17 09:23:48 +08:00
Peter Steinberger
dcb4160909 docs: clarify Crabbox scenario proof 2026-05-17 02:23:12 +01:00
Galin Iliev
4537b89da6 fix(agents): normalize Copilot replay tool IDs
Normalize GitHub Copilot Responses replay tool-call IDs before dispatch so resumed sessions with historical overlong item IDs no longer fail Copilot schema validation.

Closes #82749.
2026-05-16 18:22:10 -07:00
Josh Avant
e50927b6c9 fix(telegram): keep streamed text when tts arrives (#82820)
* fix(telegram): keep streamed text when tts arrives

* docs(changelog): note telegram tts stream fix
2026-05-16 20:20:05 -05:00
Peter Steinberger
ffc7bda443 fix(qwen): honor chat-template thinking level 2026-05-17 02:19:26 +01:00
Peter Steinberger
f453904165 feat: add fal and OpenRouter music generation (#82789)
* feat: add fal and OpenRouter music generation

* fix: repair music generation CI gates

* chore: refresh proof gate
2026-05-17 02:05:22 +01:00
Josh Avant
562d460d75 fix(codex): guard post-tool raw assistant terminal gaps (#82816)
* fix(codex): guard post-tool raw assistant terminal gaps

* docs(changelog): note codex terminal guard fix
2026-05-16 20:04:39 -05:00
Peter Steinberger
a6225060f1 fix(memory): abort timed-out embedding requests (#82770)
* fix(memory): abort timed-out embedding requests

* test: stabilize gateway ci shards

* test: pin control ui origin fixture

* test: stabilize gateway ci fixtures

* test: isolate forged origin fixture

* test: decouple setup code from gateway net mocks

* test: repair run-node and config preaction CI

* test: fix run-node progress fixture typing

* test: remove unused pairing setup helper

* fix: stabilize embedding timeout errors
2026-05-17 02:04:17 +01:00
Vincent Koc
b77b3a7ade changelog: add Anthropic-messages reasoning_content thinking-block extraction 2026-05-17 09:02:35 +08:00
Vincent Koc
54c9820ed9 test(agents): cover anthropic reasoning replay 2026-05-17 08:58:52 +08:00
Sunnyone2three
5fe4e09b68 fix: extract reasoning_content from thinking blocks instead of non-existent top-level field 2026-05-17 08:58:52 +08:00
Sunnyone2three
8e21b3c9a6 fix: preserve reasoning_content in anthropic-messages transport for proxy providers 2026-05-17 08:58:52 +08:00
Vincent Koc
e0c3c80ebc test: share Windows platform spy helpers 2026-05-17 08:56:56 +08:00
Josh Avant
2416de1421 Fix infer SecretRef resolution for provider-backed commands (#82798)
* fix infer secretref resolution

* chore changelog for infer secretrefs
2026-05-16 19:55:39 -05:00
Shakker
f5904392e9 fix: scope plugin metadata to workspace context 2026-05-17 01:55:16 +01:00
Shakker
193bfd3a4d fix: avoid stale workspace metadata reuse 2026-05-17 01:55:16 +01:00
Shakker
bfceb0d7f9 fix: keep metadata reuse scoped to agent turns 2026-05-17 01:55:16 +01:00
Shakker
121cd054ef fix: reuse plugin metadata in local agent turns 2026-05-17 01:55:16 +01:00
Shakker
c90e42aaa7 fix: reuse plugin metadata during runtime loading 2026-05-17 01:55:16 +01:00
Shakker
20704ffab7 fix: reuse manifest metadata in model selection 2026-05-17 01:55:16 +01:00
Josh Avant
fba250d4a2 Fix Discord reply context at LLM boundary (#82801)
* fix: preserve current reply context

* docs: add reply context changelog
2026-05-16 19:54:24 -05:00
Vincent Koc
5911b5bf2d fix(qa-lab): stabilize mock qa-channel regressions 2026-05-17 08:50:31 +08:00
Peter Steinberger
0fdc280cdd fix(codex): keep native hook relay config final
Co-authored-by: Solomon Neas <me@solomonneas.dev>
2026-05-17 01:49:56 +01:00
Galin Iliev
7c151b212b fix(agents): prioritize manual session turns (#82765)
* fix(agents): prioritize manual session turns

* docs: update changelog for session priority

---------

Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
2026-05-16 17:49:48 -07:00
Josh Avant
ad450a7dfb Fix Windows image model event loop stalls (#82799)
* fix media image model event loop stalls

* changelog for image event loop fix

* fix async auth test mock
2026-05-16 19:49:11 -05:00
Peter Steinberger
89532d3a92 fix(codex): satisfy shared-client state typing 2026-05-17 01:46:39 +01:00
Peter Steinberger
c6ffacd1db fix(codex): surface app-server close failures 2026-05-17 01:46:39 +01:00
Peter Steinberger
191bd7dc9a fix(codex): scope app-server migration cleanup 2026-05-17 01:46:39 +01:00
Peter Steinberger
b30face031 fix(codex): migrate legacy app-server state 2026-05-17 01:46:39 +01:00
Peter Steinberger
4cbf616d30 fix(codex): premark terminal app-server turns 2026-05-17 01:46:39 +01:00
Peter Steinberger
c65801c5a9 fix(codex): preserve completed app-server turns 2026-05-17 01:46:39 +01:00
Peter Steinberger
06b902e33f fix(codex): abort work when app-server closes 2026-05-17 01:46:39 +01:00
Peter Steinberger
ea4ee23fa2 chore: keep codex changelog scoped 2026-05-17 01:46:39 +01:00
Peter Steinberger
84d3b7a389 fix(codex): isolate shared app-server clients 2026-05-17 01:46:39 +01:00
Peter Steinberger
80848fc040 fix(config): accept gateway remote port 2026-05-17 01:41:17 +01:00
Peter Steinberger
e98ebb5739 fix(ci): format macOS Swift sources 2026-05-17 01:36:12 +01:00
Peter Steinberger
f0513221d7 fix: improve mac menu status errors 2026-05-17 01:35:20 +01:00
Peter Steinberger
a70459d10b fix: keep telegram floor replays dispatchable 2026-05-17 01:34:54 +01:00
Peter Steinberger
b09e11bc69 fix: harden telegram routing edge cases 2026-05-17 01:34:54 +01:00
Peter Steinberger
983e8d39da docs: add telegram routing changelog 2026-05-17 01:34:54 +01:00
Miya
5239b20089 fix telegram polling and message action scopes 2026-05-17 01:34:54 +01:00
Josh Avant
8d3027dffa Remove OAuth sidecar credential runtime support (#82777)
* fix(auth): remove oauth sidecar runtime support

* docs(changelog): note oauth sidecar removal
2026-05-16 19:33:30 -05:00
Ayaan Zaidi
d1280a3de9 fix(discord): preserve room event history until delivery (#82573)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 01:24:33 +01:00
Peter Steinberger
d7ad12dde4 test(cli): force progress test out of CI mode 2026-05-17 01:13:24 +01:00
Peter Steinberger
e19f05b79b test(cli): repair startup progress checks 2026-05-17 01:13:24 +01:00
Peter Steinberger
97abe0f0c0 fix(gateway): harden origin and pairing checks 2026-05-17 01:13:24 +01:00
Peter Steinberger
388b7456d2 docs: add telegram captionless media changelog (#82756) 2026-05-17 01:13:24 +01:00
JARVIS-Glasses
2c4287b6df fix(telegram): honor catch-all mentions for captionless media 2026-05-17 01:13:24 +01:00
Peter Steinberger
ff8d3dc591 fix: improve mac pairing approval prompt 2026-05-17 01:12:59 +01:00
Peter Steinberger
c2e90914b7 fix: simplify talk options panel 2026-05-17 00:42:54 +01:00
Peter Steinberger
12debcb05e fix(cli): improve config startup progress 2026-05-17 00:37:39 +01:00
Peter Steinberger
c528f36507 fix(feishu): reject numeric wiki space ids (#82769) (thanks @hyspacex) 2026-05-17 00:34:58 +01:00
Harry Xie
3411a481f7 fix(feishu): reject numeric wiki space ids 2026-05-17 00:34:58 +01:00
Peter Steinberger
32f7481787 docs: route long coding agent builds through issues 2026-05-17 00:15:02 +01:00
吴杨帆
cc9117f729 fix: validate limit edge cases and voicecall numeric flags (#82679)
Fix diagnostics/session usage limit handling and voice-call numeric CLI validation.

- Treat explicit zero, negative, and non-finite diagnostics/session limits as empty results instead of falling back to defaults.
- Reject invalid, non-finite, and fractional voice-call numeric flags.
- Add focused tests and a live repro proof for the canonical edge cases.

Fixes #82646, #82650, #82651, #82653.

Co-authored-by: wuyangfan <1102042793@qq.com>
2026-05-17 00:11:46 +01:00
Josh Avant
99a269f8b4 fix(agents): mark interrupted sessions before restart (#82772)
* fix(agents): mark interrupted sessions before restart

* docs: add restart recovery changelog

* fix(agents): satisfy restart recovery type checks
2026-05-16 18:11:35 -05:00
Peter Steinberger
0190f4ae1e fix: finish inbound event rebase (#82606) 2026-05-17 00:10:29 +01:00
Peter Steinberger
d0efaceb97 fix: keep room events quiet across legacy helpers 2026-05-17 00:10:29 +01:00
Peter Steinberger
cdf8121a04 chore: refresh plugin sdk api baseline 2026-05-17 00:10:29 +01:00
Peter Steinberger
1d22578c6c chore: drop generated artifacts from refactor branch 2026-05-17 00:10:29 +01:00
Peter Steinberger
07f05e972e refactor: move inbound event classification into core 2026-05-17 00:10:29 +01:00
XING
6b4d371723 fix(secrets): treat env refs as audit-safe auth values
Fix secrets audit env-ref classification and document supported auth SecretRef shorthand.\n\nCo-authored-by: XING <wxinxings@gmail.com>
2026-05-17 00:05:10 +01:00
Gio Della-Libera
3b2cd0dd1a Honor cwd for native subagent spawns (#81896)
* Honor cwd for native subagent spawns

Thread sessions_spawn cwd through the native subagent path, use the resolved child workspace for attachment materialization, and keep workspace metadata internal to the gateway boundary.

* Refresh checks after proof update
2026-05-16 15:56:13 -07:00
Peter Steinberger
d533a65f56 fix: default music generation timeout to five minutes 2026-05-16 23:50:58 +01:00
Peter Steinberger
5b383af736 feat: add native mac dashboard window 2026-05-16 23:49:18 +01:00
100menotu001
21244d9793 fix(tasks): make delegated completions review-ready
Co-authored-by: Craig <froelich@craigs.mac.studio.froho>
2026-05-16 23:47:47 +01:00
Gio Della-Libera
a136cafe98 Default bootstrap truncation warnings to always (#81918)
* Default bootstrap truncation warnings to always

Make bootstrap truncation warnings surface on every affected run by default while preserving explicit off and once configuration.

* Refresh checks after proof formatting fix

* Refresh checks after live proof update

* docs: align bootstrap warning default reference

Update the public agent config reference to match the new default bootstrapPromptTruncationWarning mode and recommended example.
2026-05-16 15:46:44 -07:00
Peter Steinberger
bea4f0d2f4 fix(gateway): defer heartbeats during active replies
* fix(gateway): defer heartbeats during active replies

* fix(gateway): bind heartbeat reply run fallback
2026-05-16 23:43:52 +01:00
Peter Steinberger
77ca3dc99c fix: allow direct media failure summaries 2026-05-16 23:41:48 +01:00
Peter Steinberger
7abae15a6b fix: keep music generation timeouts internal 2026-05-16 23:41:48 +01:00
Peter Steinberger
6e2e63a983 fix: suppress equivalent OpenAI Codex fallback notices 2026-05-16 23:41:48 +01:00
Vincent Koc
8bef5d0d62 fix(qa-lab): stabilize threaded memory parity 2026-05-17 06:41:21 +08:00
Peter Steinberger
41777fb0fa fix(cli): prioritize auth provider picker 2026-05-16 23:40:49 +01:00
Peter Steinberger
ac2a1e5b50 fix(minimax): declare CN provider auth aliases
Co-authored-by: kamusis <kamusis@gmail.com>
2026-05-16 23:37:03 +01:00
Josh Avant
045a581069 fix(sandbox): honor explicit docker env (#82763)
* fix(sandbox): honor explicit docker env

* docs(changelog): note sandbox env fix
2026-05-16 17:36:05 -05:00
Peter Steinberger
54619d4033 fix(discord): keep progress drafts for tool-only replies 2026-05-16 23:29:55 +01:00
Vincent Koc
81a578fd6b fix(qa-lab): validate capture saved views 2026-05-17 06:28:57 +08:00
Peter Steinberger
36e88f5ddd docs: clarify file-backed secret refs 2026-05-16 23:28:39 +01:00
Peter Steinberger
ac99494e44 test: tighten session_status run-session proof (#82696) 2026-05-16 23:27:19 +01:00
wuyangfan
361dc69029 chore: add live repro for session_status runSessionKey proof
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 23:27:19 +01:00
wuyangfan
75a9293bf9 fix(session_status): prefer runSessionKey for implicit no-arg lookups
No-arg session_status calls now resolve against the live run session when
runSessionKey is available, so thinking level and other session state match
the active run instead of a stale sandbox/policy key.

Fixes openclaw/openclaw#82669

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 23:27:19 +01:00
Peter Steinberger
c1c67306fd fix(openai): restore Codex xhigh thinking metadata (#82761) 2026-05-16 23:25:21 +01:00
Gio Della-Libera
82ab8a8785 fix(config): materialize subagent archive default (#81998) 2026-05-16 15:19:36 -07:00
Peter Steinberger
7b38ac9749 fix(channels): contain draft stream flush failures (#82713)
Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
Co-authored-by: opencode <opencode@users.noreply.github.com>
2026-05-16 23:12:39 +01:00
吴杨帆
9791957cd5 fix(qqbot): treat false-like QQBOT_DEBUG values as disabled (#82697)
Fix QQBot debug logging so only explicit truthy `QQBOT_DEBUG` values (`1`, `true`, `yes`, `on`) enable debug output. False-like values such as `0`, `false`, `off`, and `no` now keep debug logs disabled, preventing accidental message-text logging.

Also add the release changelog entry and remove a stale unused daemon inspection helper that failed current `tsgo:prod` after rebasing onto latest main.

Fixes #82644.
Thanks @leno23.

Co-authored-by: wuyangfan <1102042793@qq.com>
2026-05-16 23:10:09 +01:00
Peter Steinberger
93bc99460e fix(daemon): remove unused service marker helper 2026-05-16 23:06:12 +01:00
100menotu001
a1d0b2709a Add security audit suppressions (#76949)
* Add security audit suppressions

* docs: list audit suppression dangerous flag

* fix(security): keep audit suppressions visible

* docs(changelog): thank audit suppression contributor

---------

Co-authored-by: Craig <froelich@craigs.mac.studio.froho>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-16 22:57:04 +01:00
Peter Steinberger
3536c927da fix(doctor): avoid launchagent false positives 2026-05-16 22:56:48 +01:00
Gio Della-Libera
2762d9abbe fix(agents): allow workspace-only reads for skills (#82397) 2026-05-16 14:54:19 -07:00
Peter Steinberger
37a1ab0c0b fix: document image generation timeout defaults (#75337) 2026-05-16 22:53:07 +01:00
Peter Steinberger
e485640da4 fix: raise hosted image generation timeouts 2026-05-16 22:53:07 +01:00
Peter Steinberger
fd8afc1dce refactor: unify async media generation
Summary:
- Refactor image/music/video generation onto the shared async media-generation scheduler and task lifecycle.
- Make session-backed image generation async with status, duplicate guarding, active-task prompt context, and message-tool completion delivery.
- Update docs/changelog and add /tasks coverage for image-generation task visibility.

Verification:
- Codex review: no accepted/actionable findings.
- pnpm test src/auto-reply/reply/commands-tasks.test.ts src/agents/tools/image-generate-tool.test.ts src/agents/tools/image-generate-background.test.ts src/agents/image-generation-task-status.test.ts -- --reporter=dot
- Previous focused media suite: 12 files / 169 tests passed.
- Crabbox aws check:changed run run_fbd1b62c7472 passed.
- Crabbox aws live openclaw infer run run_c17929e0e224 passed with OpenAI gpt-image-2.
- GitHub CI for rebased head 74d1cda6a6 completed with no non-success code gates.
2026-05-16 22:50:06 +01:00
Peter Steinberger
fff8e79afb docs: credit subagent handoff PR (#82724) 2026-05-16 22:48:19 +01:00
OpenClaw Contributor
8040f28bc5 fix(subagents): make completion handoff review-first 2026-05-16 22:48:19 +01:00
Vincent Koc
25090f64b3 changelog: add edit-tool file_path alias recovery (#81909) 2026-05-17 05:43:20 +08:00
Gio Della-Libera
9e31b9d344 Fix edit recovery for file_path workspace edits (#81909)
* Recover edit tool failures for file_path

Honor file_path and related aliases when resolving edit recovery paths so post-write errors do not surface false edit failures after the file changed.

* Refresh checks after proof formatting fix

* Refresh checks after live proof update
2026-05-16 14:40:44 -07:00
Zennn
91f45d9c8a fix(gateway): dedupe exec followup continuations (#82717)
Co-authored-by: Miya <miya@Miyas-Mac-mini.local>
2026-05-16 22:39:26 +01:00
Peter Steinberger
842e6f1643 fix(slack): preserve assistant thread context 2026-05-16 22:31:22 +01:00
Peter Steinberger
80eeb688c1 feat(slack): add assistant thread lifecycle 2026-05-16 22:31:22 +01:00
Peter Steinberger
c4fb12ee8d fix: preserve xAI Grok 4.3 default reasoning (#81227) 2026-05-16 22:25:43 +01:00
Jason O'Neal
4f886e7334 fix(xai): gate reasoning effort to supported models 2026-05-16 22:25:43 +01:00
Marcus Castro
6e70d9e4b6 fix: accept WhatsApp group-prefixed targets (#82738) 2026-05-16 18:18:18 -03:00
Peter Steinberger
e9d283e13a fix(setup): summarize gateway config 2026-05-16 22:16:32 +01:00
Peter Steinberger
3934849550 fix: document OpenRouter thinking replay fix (#82380) 2026-05-16 22:14:57 +01:00
hclsys
9fa78718c7 fix(openai-completions): skip non-JSON thinkingSignature provenance tags
The openai-completions OpenRouter passthrough records the response field
name ("reasoning", "reasoning_details", "reasoning_content",
"reasoning_text", "content") as the assistant block's
`thinkingSignature`. Those values are provenance tags rather than
JSON-encoded reasoning items, so replaying them in the next request body
breaks providers that expect a structured signature (OpenRouter returns
HTTP 500 on the 2nd turn for Anthropic Claude and xAI Grok via the
openai-completions API).

Stop persisting the provenance tag (only keep replayable JSON signatures)
and harden the responses replay path with a matching JSON guard so
existing transcripts with poisoned signatures recover cleanly.

Fixes #82335
2026-05-16 22:14:57 +01:00
Peter Steinberger
4b0f16d496 fix(agents): announce auto model fallback transitions (#82676)
* fix(agents): announce model fallback transitions

* docs(agents): explain model fallback notices

# Conflicts:
#	docs/concepts/model-failover.md

* fix(agents): use five minute fallback probe cadence

* fix(agents): keep fallback notices out of ACP transcripts
2026-05-16 21:56:31 +01:00
Peter Steinberger
66c64a29ee fix(gateway): capture opt-in memory pressure snapshots (#82674)
* fix(gateway): persist critical memory pressure bundles

* docs(gateway): add memory pressure troubleshooting

* feat(gateway): gate memory pressure bundles

* feat(gateway): flatten memory pressure bundle config

* feat(gateway): rename memory pressure snapshot config

* fix(gateway): make memory pressure snapshots opt in

* docs(config): refresh config baseline

* fix(config): simplify memory pressure migration default
2026-05-16 21:52:09 +01:00
Peter Steinberger
532e42213d fix: preserve bundle activation metadata (#75133)
Co-authored-by: 100menotu001 <64228916+100menotu001@users.noreply.github.com>
2026-05-16 21:31:56 +01:00
Agustin Rivera
f7977fb102 fix(gateway): reject malformed request targets (#82686)
* fix(gateway): reject malformed request targets

* fix(gateway): document malformed request target rejection
2026-05-16 13:25:49 -07:00
Vincent Koc
55edadf86f fix(qa-lab): ignore heartbeat parity transcripts 2026-05-17 04:24:17 +08:00
Peter Steinberger
6369bf64cd fix(gateway): trace restart intent reasons 2026-05-16 21:23:06 +01:00
hcl
c421be6c90 fix(docs): use lowercase MCP search tool (#82704)
Fixes #82702.

Summary:
- Use the canonical lowercase docs MCP search tool name.
- Keep docs and changelog aligned for the CLI fix.

Verification:
- node scripts/run-vitest.mjs src/commands/docs.test.ts
- pnpm lint -- src/commands/docs.ts src/commands/docs.test.ts
- pnpm exec oxfmt --check CHANGELOG.md docs/cli/docs.md src/commands/docs.ts src/commands/docs.test.ts
- pnpm docs:list
- git diff --check
- HOME=$(mktemp -d) pnpm openclaw docs "browser existing-session"
- Codex review local + branch: clean
- GitHub CI 25971835163, CodeQL Critical Quality 25971835154, Real behavior proof 25971834239: green

Co-authored-by: hclsys <hclsys@users.noreply.github.com>
2026-05-16 21:21:58 +01:00
Vincent Koc
640735cebe fix(qa): serialize runtime parity cells 2026-05-17 04:19:05 +08:00
Peter Steinberger
6d844c5900 fix(webchat): trust ACP TTS media tails (#82701) (thanks @leno23) 2026-05-16 21:18:13 +01:00
wuyangfan
35e1c7ac41 fix(webchat): keep trustedLocalMedia internal to reply payloads
Restore Omit on public plugin-sdk ReplyPayload; set trustedLocalMedia via
runtime assertion in speech-core and explicitly on dispatch TTS-only finals.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 21:18:13 +01:00
wuyangfan
eec18fccb4 fix(webchat): forward trustedLocalMedia on accumulated block TTS tail
Avoid per-block final-mode synthesis (duplicate with dispatch tail). Mark
TTS output as trusted local media and pass the flag through the TTS-only
final payload WebChat consumes after block streaming.

Fixes #82628

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 21:18:13 +01:00
wuyangfan
f8323f8636 chore: add live repro script for WebChat auto-TTS proof
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 21:18:13 +01:00
wuyangfan
14117c303d fix(webchat): enable auto-TTS for block replies with trusted local media
WebChat streaming uses kind=block for assistant text; final-mode TTS skipped
those payloads. Mark synthesized audio as trustedLocalMedia and export the
full ReplyPayload type so the gateway can serve local TTS files.

Fixes #82628

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 21:18:13 +01:00
KateWilkins
03012ac5a1 fix: update xai image generation model (#81399)
Updates the xAI image model catalog and docs to use `grok-imagine-image-quality` after `grok-imagine-image-pro` retirement.

Co-authored-by: Kate <kate@trantor.dev>
2026-05-16 21:09:21 +01:00
Peter Steinberger
cc8c0d4ecb fix(config): quiet config write output 2026-05-16 21:08:36 +01:00
Manzojunior
a9e0a897a1 fix: handle xai video pending status (#82610)
Treats xAI video `pending` poll status as in-flight processing and keeps polling until `done`.

Co-authored-by: Chase Young <manzo0924@gmail.com>
2026-05-16 21:03:34 +01:00
Vincent Koc
5db30ab47d fix(extensions): satisfy runtime boundary checks 2026-05-17 03:48:46 +08:00
Vincent Koc
6a12134489 test(release): tolerate package validation drift 2026-05-17 03:33:54 +08:00
Vincent Koc
f345b54d04 test(qa-lab): add runtime parity axis 2026-05-17 03:32:50 +08:00
Peter Steinberger
6e4cc222cb fix(xai): refresh oauth and model catalog 2026-05-16 20:25:07 +01:00
Vincent Koc
7d09ff89ee fix(gateway): honor env token for remote interactive auth 2026-05-17 03:15:54 +08:00
Vincent Koc
ca1fd1b140 test: share spy lifecycle helpers 2026-05-17 03:13:46 +08:00
Peter Steinberger
1a956b6ba1 fix: require message tool for generated media completions 2026-05-16 20:12:42 +01:00
Vincent Koc
df3f983d96 fix(ci): keep unauthorized Mantis commands neutral 2026-05-17 03:11:45 +08:00
Peter Steinberger
c8782d18eb fix(agents): probe primary after auto fallback pin (#82707) 2026-05-16 20:03:09 +01:00
Peter Steinberger
500d282340 docs: record memory-core dreaming cron cleanup (#82389) 2026-05-16 19:58:01 +01:00
Neerav Makwana
ccdcdc7d1b fix(memory-core): retry disabled dreaming cron cleanup 2026-05-16 19:58:01 +01:00
Vincent Koc
440333125c test(qa-lab): add personal agent scenarios 2026-05-17 02:56:53 +08:00
Vincent Koc
1586085c7f test: share node eval helpers 2026-05-17 02:51:20 +08:00
Vincent Koc
11745de9d9 test(e2e): wait for launcher child metadata 2026-05-17 02:48:35 +08:00
Vincent Koc
db4ce1f506 changelog: add config unset --dry-run (#81895) 2026-05-17 02:42:36 +08:00
Vincent Koc
e1061a8b46 test(live): tolerate provider drift in release checks 2026-05-17 02:36:48 +08:00
Vincent Koc
a171600d1d test: isolate broad unit state 2026-05-17 02:32:58 +08:00
Vincent Koc
b19b7539a8 test: fix Codex live Docker api key permissions 2026-05-17 02:32:58 +08:00
Vincent Koc
b6b33ad6d3 test: harden broad qa timing 2026-05-17 02:32:57 +08:00
Vincent Koc
3a13d1e0be test: bind Codex live API key lane through OpenAI 2026-05-17 02:32:57 +08:00
Vincent Koc
f0105939bf test: pass Codex API key into Docker bind lane 2026-05-17 02:32:57 +08:00
Vincent Koc
11a31e476b test: align Codex bind live model 2026-05-17 02:32:57 +08:00
Vincent Koc
3df6499fb8 test: harden sparse Testbox scans 2026-05-17 02:32:57 +08:00
Vincent Koc
09db0892dd test: tolerate sparse Testbox file scans 2026-05-17 02:32:34 +08:00
Vincent Koc
8330582493 test: repair broad qa surface regressions 2026-05-17 02:32:14 +08:00
Vincent Koc
b5b193076e test: share repo file helpers 2026-05-17 02:29:55 +08:00
Vincent Koc
ffd8fcd598 docs(crabbox): note explicit macOS runners 2026-05-17 02:26:51 +08:00
Peter Steinberger
8178a6c949 feat: show provider quota in control ui overview (#82647)
* feat: show provider quota in control ui overview

* feat: show provider quota in chat header

* fix: recover stale control ui chat runs

* fix: polish control ui quota refresh
2026-05-16 19:24:02 +01:00
Vincent Koc
0b03b902be Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  test(agents): tolerate Anthropic cache tool drift
2026-05-17 02:23:24 +08:00
Vincent Koc
ac2e3a23b9 fix(qa): preserve RTT samples with Convex credentials 2026-05-17 02:17:35 +08:00
Vincent Koc
ec38e96884 test(agents): tolerate Anthropic cache tool drift 2026-05-17 02:15:11 +08:00
Vincent Koc
d5035bad62 fix(google): keep auth fallback logs quiet 2026-05-17 02:10:11 +08:00
Jason O'Neal
e8b4003933 fix(google): keep first login identity errors 2026-05-17 02:10:11 +08:00
Jason O'Neal
995c702b07 fix(google): wrap Gemini CLI refresh credentials
# Conflicts:
#	CHANGELOG.md
2026-05-17 02:10:11 +08:00
Jason O'Neal
b34454f5b3 fix(google): refresh Gemini CLI OAuth tokens 2026-05-17 02:10:11 +08:00
Gio Della-Libera
489cab2738 fix(config): add --dry-run support to unset (#81895)
* Add config unset dry-run

Add --dry-run support to config unset, including JSON output and allow-exec validation parity with config set/patch dry-run handling.

* Refresh checks after proof update

* fix(config): address unset dry-run review

Return structured JSON when config unset dry-run misses a path and validate broad secret provider/default unsets against affected SecretRefs.
2026-05-16 11:09:42 -07:00
Vincent Koc
e06782d5e7 fix(gateway): land linked diagnostics fixes
Fix logs.tail credential-header redaction and JSON-mode gateway transport errors.\n\nFixes #66832.\nFixes #79108.\nSupersedes #67041.\nSupersedes #79233.\n\nCo-authored-by: Mil Wang <mingjwan@microsoft.com>\nCo-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
2026-05-17 02:05:02 +08:00
Peter Steinberger
d77c4bbb2d fix(gateway): harden startup restart queue (#82660) (thanks @samzong) 2026-05-16 18:57:58 +01:00
samzong
9b53a95d8e fix(gateway): queue startup restart signals
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-16 18:57:58 +01:00
Vincent Koc
cd1846a313 test(agents): fix embedded runner test config types 2026-05-17 01:56:58 +08:00
Vincent Koc
df9f29caef test(agents): stabilize embedded runner release checks 2026-05-17 01:54:36 +08:00
Vincent Koc
ffcbb89b7e changelog: add gateway diagnostics redaction and Telegram #81229 2026-05-17 01:42:53 +08:00
Vincent Koc
05123db93c fix(agents): redact overlapping auth secrets 2026-05-17 01:42:19 +08:00
Vincent Koc
c818a9fb4e fix(agents): redact oauth refresh errors 2026-05-17 01:42:19 +08:00
Vincent Koc
43c53174c5 fix(agents): harden spawn cleanup and patch paths 2026-05-17 01:42:19 +08:00
Vincent Koc
cb313d5378 test: share fs scan assertions 2026-05-17 01:35:39 +08:00
Vincent Koc
c277138959 test(plugins): share archive fixture packing 2026-05-17 01:35:39 +08:00
Gio Della-Libera
4003a955ee fix(telegram): normalize announce group targets (#81229) 2026-05-16 10:32:58 -07:00
Vincent Koc
61ee9755ad fix(update): preserve channel config across package repair
Preserve channel config across package-swap doctor and post-core repair.\n\nFixes #82533.
2026-05-17 01:32:37 +08:00
Vincent Koc
50508b1d0c fix(gateway): redact credential-bearing diagnostics
Redact credential-bearing gateway target URLs and client diagnostics while preserving raw connection URLs for programmatic use.

Verification:
- node scripts/run-vitest.mjs src/gateway/client.test.ts -- --reporter=verbose -t "connect failure logs"
- node scripts/run-vitest.mjs src/gateway/call.test.ts src/gateway/client.test.ts -- --reporter=dot
- git diff --check
- Testbox check:changed tbx_01krrwjvepsj3458ybk6bk1k6j https://github.com/openclaw/openclaw/actions/runs/25968066889
- codex review --base origin/main
2026-05-17 01:30:55 +08:00
Gio Della-Libera
f22c26a6cd Fix chat session picker agent switching (#81858)
* Fix chat session picker agent switching

Reset the chat session picker to the selected agent main session when switching agents and hide inactive sub-agent sessions from the normal picker options.

* fix(ui): preserve dashboard session on agent switch

Choose the most recent eligible normal/dashboard session for the selected agent while excluding subagent/internal rows; fall back to main only when no eligible session exists.

* fix(ui): avoid mutating session option sort
2026-05-16 10:25:15 -07:00
Vincent Koc
ba103c56a2 changelog: add #82225 Discord identify and #82237 ACP runtime handle refresh 2026-05-17 01:22:49 +08:00
Gio Della-Libera
37cd82913f fix(discord): bind delayed identify to socket generation (#82225)
* fix(discord): bind delayed identify to socket generation
* chore: refresh CI after main repairs
2026-05-16 10:19:35 -07:00
Peter Steinberger
97d1f5fd15 fix: bypass npm freshness filters during updates
Bypass npm min-release-age/before quarantine for OpenClaw-managed package installs and update installer scripts/tests/docs.\n\nFixes #82630.
2026-05-16 18:17:18 +01:00
Vincent Koc
d13749b2fc test(codex): keep dynamic tool helper tests fast 2026-05-17 01:12:20 +08:00
Gio Della-Libera
2640244d35 fix(acp): refresh runtime handles on config changes (#82237)
* fix(acp): refresh runtime handles on config changes
* chore: refresh CI after main repairs
2026-05-16 10:09:36 -07:00
Vincent Koc
28fdc34543 changelog: rewrite #81386 bullet from commit-style to user-facing prose 2026-05-17 01:02:25 +08:00
Pavan Kumar Gondhi
6a12c6f799 fix(gateway): scope session data lookups by agent [AI] (#81386)
* fix: scope gateway session lookups by agent

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing ci

* addressing ci

* fix: complete root-cause handling

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* Fix Swift protocol optional initializer defaults

* Stabilize node command lookup in approval test

* Fix browser proxy approval test node lookup

* Trim unrelated changes from issue 642 fix

* Remove unrelated formatting churn from issue 642 fix

* Fix Swift protocol generator lint

* docs: add changelog entry for PR merge
2026-05-16 22:31:02 +05:30
Vincent Koc
9a204008ba test(extensions): stabilize plugin prerelease shards 2026-05-17 01:00:43 +08:00
Peter Steinberger
bdfc078487 test(matrix): add state-after E2EE QA coverage
Adds Matrix QA coverage for the state_after E2EE regression fixed by #82631.
2026-05-16 17:59:30 +01:00
Peter Steinberger
a3e7fc7de7 test(telegram): fix cache invalidation test contexts 2026-05-16 17:56:01 +01:00
Peter Steinberger
b11f67964c test(telegram): cover bot info cache invalidation 2026-05-16 17:56:01 +01:00
Peter Steinberger
95741daeb4 fix(telegram): cache startup bot info 2026-05-16 17:56:01 +01:00
Agustin Rivera
5774517fce Fix exec allowlist wildcard target normalization (#75723)
* fix(exec): normalize allowlist wildcard targets

Co-authored-by: zsx <git@zsxsoft.com>

* fix(exec): canonicalize executable path candidates

* docs(changelog): credit exec allowlist dot-segment fix

Adds the user-facing Unreleased Fixes entry for the exec allowlist
wildcard target normalization and absolute executable path
canonicalization landed in this PR.

---------

Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-05-16 09:54:26 -07:00
Shakker
405535d4ce fix: polish gateway restart diagnostics 2026-05-16 17:50:36 +01:00
samzong
92fe2a8f5f fix(gateway): improve restart readiness diagnostics 2026-05-16 17:50:36 +01:00
Peter Steinberger
597b7b0628 fix(skills): default codex review to yolo mode 2026-05-16 17:47:38 +01:00
Peter Steinberger
38cf54593e fix: accept device identity dashboard probes 2026-05-16 17:42:16 +01:00
Vincent Koc
6ed16d9356 test(extensions): harden plugin prerelease shards 2026-05-17 00:40:42 +08:00
Vincent Koc
deaf46a07d fix(auth): avoid keychain creation for oauth profile secrets 2026-05-17 00:31:30 +08:00
Vincent Koc
fddac1c507 test(extensions): align mocks with runtime contracts 2026-05-17 00:23:23 +08:00
Peter Steinberger
4526b44778 fix: preserve generated media completion attachments 2026-05-16 17:13:30 +01:00
Peter Steinberger
15b0d43412 docs: clarify plugin gateway auto-restart 2026-05-16 17:11:53 +01:00
Peter Steinberger
777d289979 fix(matrix): avoid state-after sync opt-in 2026-05-16 16:57:24 +01:00
Peter Steinberger
2fcaab0010 fix: clean up approval handler PR landing (#82482) 2026-05-16 16:41:07 +01:00
Feelw00
a2f1f73107 docs(plugins): note cancelDelivered in channel plugin interactions list
ClawSweeper R3 flagged that the previous follow-up added the
`cancelDelivered` hook to the public approval-handler runtime interaction
surface but left the channel plugin docs describing `interactions` as
only bind/unbind/clear-action hooks. Extend the bullet so plugin authors
whose `deliverPending` registers in-process or persistent state know
when to implement the cancellation hook.

AI-assisted: drafted with claude code (claude-opus-4-7).
2026-05-16 16:41:07 +01:00
Feelw00
ea9793b2e1 fix(approvals): release Matrix reaction target on mid-flight cancel
Address the ClawSweeper R2 finding that the pre-bind stopped guard
introduced in this PR drops a delivered entry without any cleanup. The
prior PR comment block was correct only for adapters whose deliverPending
has no in-process side effects; Matrix registers a reaction target in
both an in-memory Map and a persistent store inside deliverPending, so
the entry would leak until the 24h TTL (or process restart) every time
stop() landed between deliverPending and bindPending.

Add an optional cancelDelivered interaction hook on the runtime types,
forward it through both the spec-to-adapter wrapper
(createChannelApprovalNativeRuntimeAdapter) and the lazy adapter wrapper
(createLazyChannelApprovalNativeRuntimeAdapter), and invoke it from the
two stopped guards in deliverTarget: the pre-bind guard always calls it,
and the post-bind guard calls it on the branch where bindPending
returned no handle (so unbindPending cannot run). Matrix implements the
hook by calling unregisterMatrixApprovalReactionTarget on the entry's
roomId + reactionEventId, which is the exact key
registerMatrixApprovalReactionTarget uses inside deliverPending.

The other native runtime adapters (Slack, Discord, Telegram, qqbot)
leave the hook unimplemented because their deliverPending paths only
emit remote messages and keep no in-process state to drop.

Regression coverage:
- invokes cancelDelivered when stop() fires between deliverPending and
  bindPending (Deferred-gated deliverPending, asserts bindPending /
  unbindPending never run and cancelDelivered receives the entry)
- invokes cancelDelivered when stop() fires after bindPending returned
  null (asserts unbindPending stays uncalled while cancelDelivered fires)

AI-assisted: drafted with claude code (claude-opus-4-7).
2026-05-16 16:41:07 +01:00
Feelw00
851b9271a5 fix(infra): skip unbindPending without a binding handle (dts build)
The previous commit invoked unbindPending in the deliverPending→bindPending
race path before any binding existed; nativeRuntime.interactions.unbindPending
requires a binding, so the dts build failed with TS2345. In production the
race window that PROOF-CAND-040 measured is always after bindPending (3/3
trials had bindPending=1), so dropping the pre-bindPending unbindPending
call does not change observed cleanup behavior: that branch now just nulls
out the in-flight delivery. The post-bindPending branch keeps the
unbindPending call (binding handle present) and remains the only path
required to fix the leak.

The regression test is updated to park bindPending (not deliverPending)
before invoking stop(), matching the production race window.

AI-assisted: drafted with claude code (claude-opus-4-7).
2026-05-16 16:41:07 +01:00
Feelw00
06dfa6f160 fix(infra): drop in-flight approval delivery after onStopped
createChannelApprovalHandlerFromCapability shares a closure-scoped
activeEntries Map across deliverTarget / finalizeResolved /
finalizeExpired / onStopped, with no synchronization primitives in the
file. deliverTarget's two awaits (transport.deliverPending then
interactions.bindPending) bracket a read-modify-write on activeEntries;
if onStopped clears the map between those awaits, the wrapped entry is
inserted into an already-cleared map and never reaches unbindPending —
the native side keeps its listener / channel binding open forever.
Production-faithful e2e measured this 3/3 trials: bindPending=1,
unbindPending=0 per request.

Track a closure-scoped `stopped` flag set by onStopped, and have
deliverTarget call unbindPending and bail to null on each await when
stopped becomes true. nativeRuntime contracts (transport / interactions
signatures) are untouched.

AI-assisted: drafted with claude code (claude-opus-4-7).
2026-05-16 16:41:07 +01:00
Gio Della-Libera
2c59ea8a2e fix(sessions): estimate local transcript usage
Fixes #73990.\n\nAdds a transcript-derived token estimate for local/OpenAI-compatible session transcripts that have real content but no provider usage telemetry, preserving provider-reported usage when available and gating estimation on assistant model identity.\n\nVerification:\n- CI run 25965717279: success\n- Real behavior proof run 25965716561: success\n- Azure Crabbox clean-clone proof: pnpm test src/gateway/session-utils.fs.test.ts src/status/status-message.test.ts; pnpm check:changed; pnpm exec tsx /tmp/openclaw-transcript-proof.mts; git diff --check origin/main...HEAD
2026-05-16 08:40:09 -07:00
Peter Steinberger
575936473d fix(auto-reply): log suppressed message-tool-only finals (#82609)
* fix(auto-reply): fallback group finals when message tool is missed

* fix(auto-reply): log suppressed message-tool finals

* docs(auto-reply): clarify message-tool finals stay private

# Conflicts:
#	CHANGELOG.md

* docs(auto-reply): fix group visible reply examples
2026-05-16 16:30:07 +01:00
Kagura
ffdc7aa7a6 fix(slack): route DM thread replies to main session instead of thread-scoped session (#82418)
* fix(slack): route DM thread replies to main session instead of thread-scoped session

DM thread replies (user replies inside a thread under a bot message in a
DM) were routed to a thread-specific session key instead of the user's
main DM session.  This caused the agent to never receive the inbound on
the expected session, making the bot appear unresponsive.

The root cause was in prepare-routing.ts: canonicalThreadId for
isDirectMessage was set to threadTs when isThreadReply was true, creating
a session key like agent:main:slack:direct:u3🧵<ts>.  DM threads
are a UI affordance — not a session boundary — so all DM messages should
route to the main DM session regardless of thread_ts.

Also adds a diagnostic logVerbose warning when assistant_app_thread
message_changed events fail sender resolution (Case 2 of #82390),
which was previously completely silent.

Fixes #82390

* chore(slack): polish DM thread routing PR

* test(slack): update DM thread routing contract

* test(slack): flatten non-main DM thread expectations

* fix(slack): preserve bound DM thread routes

* test(slack): align DM thread session fixtures

* fix(slack): keep flattened DM thread metadata scoped

* fix(slack): preserve DM thread delivery routes

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-16 16:24:36 +01:00
Peter Steinberger
cc22c2ad79 docs: add codex app-server timeout changelog 2026-05-16 16:21:34 +01:00
Peter Steinberger
2074cde6cf test: stabilize codex app-server waits 2026-05-16 16:21:34 +01:00
Peter Steinberger
18cbc7bc48 test: repair current CI drift 2026-05-16 16:21:34 +01:00
Peter Steinberger
21c5f8dc6d fix(codex): keep run lane timeout progress-aware 2026-05-16 16:21:34 +01:00
Peter Steinberger
a641a27bd4 fix(codex): harden app-server progress watchdog 2026-05-16 16:21:34 +01:00
Peter Steinberger
efe3790dd3 fix(codex): preserve queued turn-start progress 2026-05-16 16:21:34 +01:00
Peter Steinberger
6778da05d6 fix(codex): scope app-server request watchdog progress 2026-05-16 16:21:34 +01:00
Eva (agent)
d7d597cfd8 fix: scope codex attempt watchdog to turn progress 2026-05-16 16:21:34 +01:00
Eva (agent)
722161271e fix: make codex app-server timeout progress-aware 2026-05-16 16:21:34 +01:00
Peter Steinberger
55439fe34b fix(agents): observe detail-less responses failures 2026-05-16 16:21:01 +01:00
Gio Della-Libera
0eca3a92e3 fix(auto-reply): preserve session model display for heartbeat usage (#82267)
* Preserve session model display for heartbeat usage
* Refresh checks after proof update
* chore: refresh CI after main repairs
2026-05-16 08:09:49 -07:00
Peter Steinberger
1769e6a2f0 fix(agents): log detail-less responses failures (#82593)
* fix(agents): log detail-less responses failures

* fix(github-copilot): guard device login fetches

* test(ci): refresh stale cron and session expectations

* test(ci): keep cron legacy string fixture

* fix(agents): redact array response failure ids

* fix(agents): classify empty response failures
2026-05-16 15:57:40 +01:00
Gio Della-Libera
0b708a2574 OC Path: restore YAML support (#81436)
* OC Path: restore YAML support
* fix(oc-path): guard yaml writes and empty sequences
* fix(oc-path): guard yaml insertion keys
* fix(oc-path): guard yaml object key
* fix(oc-path): classify yaml root insertions
* style(oc-path): format yaml branch after rebase
* fix(oc-path): reject malformed yaml edits
* docs(oc-path): clarify yaml file support
* fix(ci): refresh yaml branch after rebase
* fix(ci): clean shared blockers for yaml path PR
* fix(changelog): keep yaml path note scoped
* fix(ci): preserve current shared contracts


---------

Co-authored-by: Gio Della-Libera <giodl73@gmail.com
2026-05-16 07:52:08 -07:00
Gio Della-Libera
f7b1148bed Strip inbound metadata from replayed user turns (#82614) 2026-05-16 07:45:56 -07:00
Vincent Koc
fa9a22b960 changelog: credit TUI fallback model display fix (#82296) 2026-05-16 22:42:43 +08:00
Vincent Koc
0b24ffb91f fix(ci): keep performance artifacts on report publish failure 2026-05-16 22:41:34 +08:00
Gio Della-Libera
22858769e4 fix(tui): update model display during fallback (#82296)
* fix(tui): update fallback model display

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refresh checks after proof update

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: refresh CI after main repairs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-16 07:35:43 -07:00
Peter Steinberger
9dedc4d95c fix: honor Codex auth order for OpenAI PI (#82605)
* fix: honor Codex auth order for OpenAI PI

* docs: add PR reference for OpenAI PI auth fix
2026-05-16 15:26:27 +01:00
Peter Steinberger
16e5d6692d fix(gateway): bound traced channel startup handoff (#82592)
* fix(gateway): bound traced channel startup handoff

* fix(github-copilot): guard device login fetches

* fix(gateway): skip stopped traced channel handoffs

* test(net): keep guarded fetch mocks hermetic
2026-05-16 15:15:57 +01:00
吴杨帆
eebdbabae9 fix: omit Ollama think for non-reasoning models
Preserve native Ollama thinking controls for supported models and explicit think=false, but avoid sending truthy think payloads for models marked reasoning=false.\n\nCo-authored-by: 吴杨帆 <85487201+leno23@users.noreply.github.com>
2026-05-16 15:10:12 +01:00
Gio Della-Libera
caf8fa2ebf Revert "Fix bundled channel dist-runtime setup roots" (#82612)
This reverts commit 1bd10cfee6.

Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-16 07:07:39 -07:00
Peter Steinberger
58083866d0 fix: sync codex app-server protocol drift 2026-05-16 15:03:51 +01:00
Kyzcreig
8092761d7b fix: mark codex compaction events completed 2026-05-16 15:03:51 +01:00
njuboy11
6a65ea8c3a fix: preserve post-compaction session token freshness (#82578)
Fixes #82576.

Keeps post-compaction token totals fresh across stale usage updates and adds regression coverage for the repeated auto-compaction loop. Also includes maintainer fixups needed to keep the touched CI lanes green: guarded GitHub Copilot device-flow fetches, dead-session metadata recreation, and current cron stale-data expectations.

Co-authored-by: njuboy11 <njuboy11@users.noreply.github.com>
2026-05-16 14:47:57 +01:00
Ayaan Zaidi
b9921e21b9 docs(changelog): note telegram final reply fix 2026-05-16 18:50:10 +05:30
Ayaan Zaidi
0fb0b5197e test(telegram): cover truncated progress finals 2026-05-16 18:50:10 +05:30
Ayaan Zaidi
20c3580394 fix(telegram): deliver transcript-backed final replies 2026-05-16 18:50:10 +05:30
Gio Della-Libera
1bd10cfee6 Fix bundled channel dist-runtime setup roots
* Fix bundled channel dist-runtime setup roots

Resolve bundled channel generated entries from dist-runtime before falling back to source paths, and select the dist-runtime plugin root as the boundary root for packaged setup modules. This keeps the fs-safe module open boundary check intact while preventing packaged bundled setup entries from being checked against the source extensions root.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Repair session store validation test fixtures

Update current-main tests that wrote persisted session entries without valid session IDs after session store loading started filtering invalid entries. Keep the fixture-only repair separate from the bundled channel loader fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Repair pairing and cron validation fixtures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-16 06:05:34 -07:00
Vincent Koc
394f61b8ce changelog: broaden auth-profile lock bullet to cover all providers 2026-05-16 21:02:58 +08:00
Vincent Koc
a93283337e test(auth): mock locked auth profile upserts 2026-05-16 20:59:57 +08:00
Peter Steinberger
e71d10fd4d fix(webchat): show manual compaction progress
Add first-class session.operation start/end events for manual compaction and render the existing WebChat compaction indicator from those events.

Co-authored-by: Conan Scott <271909525+Conan-Scott@users.noreply.github.com>
2026-05-16 13:58:44 +01:00
Vincent Koc
f410a95081 fix(providers): lock auth setup profile writes 2026-05-16 20:52:12 +08:00
Vincent Koc
bede89dba6 fix(auth): lock cli provider auth writes 2026-05-16 20:52:12 +08:00
Vincent Koc
b0daf992b2 fix(auth): preserve locked profile upsert semantics 2026-05-16 20:52:12 +08:00
Peter Steinberger
605a2c87ae fix: carry gateway restart trace across respawn (#82396) (thanks @samzong) 2026-05-16 13:42:50 +01:00
Peter Steinberger
661362c89c docs: document gateway restart trace (#82396) (thanks @samzong) 2026-05-16 13:42:50 +01:00
samzong
587b06768f feat(gateway): add restart trace instrumentation
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-16 13:42:50 +01:00
Peter Steinberger
862be9fb3d fix: normalize Xiaomi array tool schemas (#82575) 2026-05-16 13:34:52 +01:00
Peter Steinberger
68a4c77f5b docs: update changelog for Slack mention hints (#82152) 2026-05-16 13:33:58 +01:00
Neerav Makwana
6b8f3fd206 fix(slack): clarify mention prompt guidance 2026-05-16 13:33:58 +01:00
Peter Steinberger
1426112f95 fix: finalize memory slot warning 2026-05-16 13:26:51 +01:00
Gio Della-Libera
0204c522bb fix(config): keep blocked memory slots fatal
Preserve hard validation failures for official external memory slot plugins that are blocked by registry diagnostics, while keeping missing uninstalled official memory plugins warning-only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-16 13:26:51 +01:00
Gio Lodi
d1787b73db fix(config): warn for missing official memory slot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-16 13:26:51 +01:00
Peter Steinberger
d16efadc00 fix(gateway): quiet startup retry closes
Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@users.noreply.github.com>
Co-authored-by: WhatsSkiLL <284126683+IWhatsskill@users.noreply.github.com>
2026-05-16 13:26:00 +01:00
Peter Steinberger
7593ba8623 test: type daemon gateway auth mock 2026-05-16 13:22:24 +01:00
Peter Steinberger
c4d65e45da docs: update changelog for #81112 2026-05-16 13:22:24 +01:00
Eva (agent)
60b4105665 fix: migrate plugin tool contracts in doctor 2026-05-16 13:22:24 +01:00
Frank Yang
e6d04550ca fix(gateway): route WebChat images through imageModel
Route WebChat image attachments through the configured imageModel when the active session model cannot accept images, while keeping one-turn image auth and fallback state ephemeral.

Thanks @frankekn.
2026-05-16 20:12:02 +08:00
Peter Steinberger
e0870473b2 docs: update changelog for Android links (#82392) 2026-05-16 13:06:26 +01:00
Neerav Makwana
07d2043081 fix(android): make chat links tappable 2026-05-16 13:06:26 +01:00
Peter Steinberger
03a7b19228 fix: recover gateway dashboard startup in stripped shells 2026-05-16 13:06:19 +01:00
Vincent Koc
7c70954892 docs(agents): clarify crabbox testbox routing 2026-05-16 20:05:50 +08:00
Vincent Koc
192caba631 fix(export): report malformed transcript rows (#82553) 2026-05-16 20:03:28 +08:00
Vincent Koc
d32b2a4771 changelog: note Codex attribution scoped to local transcripts 2026-05-16 20:02:28 +08:00
Alex Knight
c438dadc5c Fix Claude CLI runtime migration for gateway turns (#82546)
Summary:
- The PR adds model-scoped `claude-cli` runtime policy to Anthropic CLI migration/default backfill, updates the gateway CLI live-smoke config, tests, and changelog.
- Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main writes `clau ... del/provider-scoped runtime policy. I did not run a live Telegram/Dashboard repro in this read-only review.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 62cf54484f.
- Required merge gates passed before the squash merge.

Prepared head SHA: 62cf54484f
Review: https://github.com/openclaw/openclaw/pull/82546#issuecomment-4466676206

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-16 11:54:48 +00:00
Alex Knight
f8b7008f7c Fix Kimi Coding tool-call replay (#82550)
Summary:
- The PR preserves Kimi Coding reasoning_content replay for OpenAI-compatible tool-call follow-up turns, extends replay model-id matching, adds Kimi wrapper/tests, and updates the changelog.
- Reproducibility: yes. at source level: current main drops or fails to synthesize reasoning_content for kimi- ... es a concrete Kimi 400 after tool-call history. I did not run a live Kimi request in this read-only review.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 9a4605ee38.
- Required merge gates passed before the squash merge.

Prepared head SHA: 9a4605ee38
Review: https://github.com/openclaw/openclaw/pull/82550#issuecomment-4466701075

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-16 11:54:46 +00:00
Peter Steinberger
9558b2c222 fix(channels): install externalized same-id adds 2026-05-16 12:53:31 +01:00
Mason Huang
5a14b1c5c5 fix(maintainer): gate body notifications after redaction (#81993)
* feat(secret-scanning): advise delete-and-recreate for issue/PR body leaks

* fix(maintainer): gate body notifications
2026-05-16 19:50:26 +08:00
Peter Steinberger
9b560b8a41 fix: limit Codex attribution to local transcripts
Summary:
- Limit canonical OpenAI Codex app-server attribution rewrites to local transcript and trajectory records.
- Keep runtime/tool routing on the selected OpenAI model metadata, including OpenAI API-key backup profiles.
- Fix the current gateway-readiness lint blocker that was red on main.

Verification:
- codex-review branch helper clean with focused Codex app-server tests.
- pnpm lint --threads=8
- pnpm test src/commands/gateway-readiness.test.ts
- GitHub CI run 25960997256 green.

Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
2026-05-16 12:45:11 +01:00
Peter Steinberger
c6af9908e7 fix: hide decorative emoji on unsupported terminals (#82556)
* fix: hide decorative emoji on unsupported terminals

* chore: fix gateway readiness lint
2026-05-16 12:39:13 +01:00
Peter Steinberger
67fb1df352 docs: prefer clean refactors over compat shims 2026-05-16 12:38:51 +01:00
Peter Steinberger
87592fdfe2 ci: fix gateway readiness lint 2026-05-16 12:38:26 +01:00
Eva (agent)
beb3311a62 test: cover retained repair backup cleanup failures 2026-05-16 12:38:26 +01:00
Eva (agent)
e1d7ba5915 fix(agents): remove transient session-repair backups
Adapts @tynamite's fix from the abandoned #77945 to current main (which
moved to replaceFileAtomic after that PR was opened), and adds the docs +
changelog updates clawsweeper flagged plus a regression test for the
field condition from #80960.

When repairSessionFileIfNeeded writes a cleaned transcript, the sibling
*.bak-<pid>-<ts> snapshot is deleted after the atomic replace succeeds.
It is only retained — and only then reported via backupPath — when the
cleanup itself fails. This prevents the unbounded accumulation observed
in #80960, where a stuck operations-agent session with a persistently
malformed JSONL line caused 2,180 ~1.8 MB backup files to pile up over
~25 hours inside two gateway processes (PIDs 1220 and 2640).

Test changes:
- Replace requireBackupPath helper with expectNoRetainedBackup that
  also asserts no .bak-* siblings remain on disk.
- Update the four call sites that used to read the retained backup.
- Add a regression test that drives repair five times against a file
  with a recurring malformed tail and asserts zero retained backups.

Docs:
- docs/reference/transcript-hygiene.md: describe backup as transient,
  retained only on cleanup failure.

Fixes #80960. Supersedes #77945. Co-authored by @tynamite — credit for
the original approach.

Co-authored-by: tynamite <35367599+tynamite@users.noreply.github.com>
2026-05-16 12:38:26 +01:00
Peter Steinberger
bf0141a753 docs: use take-control for webvnc handoffs 2026-05-16 12:37:43 +01:00
Peter Steinberger
210ff7d318 fix(agents): yield during model stream bursts 2026-05-16 12:18:36 +01:00
WhatsSkiLL
f50c65f124 fix(codex): release raw assistant app-server completions [AI-assisted] (#82403)
* fix(codex): release raw assistant app-server completions

* refactor(codex): simplify raw assistant release guard

* fix(codex): ignore commentary raw assistant completions

* docs: add codex app-server completion changelog

---------

Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-16 12:13:54 +01:00
Peter Steinberger
b5ba210fd5 fix: gate dashboard on gateway readiness 2026-05-16 12:11:56 +01:00
Peter Steinberger
9dea44ef6d fix: simplify empty status sections 2026-05-16 12:03:33 +01:00
Vincent Koc
3e339cde89 fix(tts): honor preferred provider aliases 2026-05-16 18:59:06 +08:00
Vincent Koc
01eb56e45a fix(backup): hide manifest parser internals (#82539) 2026-05-16 18:56:45 +08:00
Peter Steinberger
225e48f632 fix(agents): fall back to PI when Codex harness is unavailable (#82532)
* fix(agents): fall back to pi when codex harness is unavailable

* fix(agents): align codex fallback auth routing
2026-05-16 11:42:06 +01:00
Vincent Koc
97e86fb2da Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix(providers): honor cleared video provider options
2026-05-16 18:38:18 +08:00
Vincent Koc
a85cd65775 fix(plugins): deprecate deactivate hook alias 2026-05-16 18:36:27 +08:00
Vincent Koc
625ff8531f fix(providers): honor cleared video provider options 2026-05-16 18:34:30 +08:00
Vincent Koc
7e4929e004 fix(sessions): validate replayed transcript rows 2026-05-16 18:29:30 +08:00
Vincent Koc
310c8530eb fix(agents): preserve trusted tool media provenance 2026-05-16 18:28:59 +08:00
Vincent Koc
856076079e fix(gateway): require trusted local audio display media 2026-05-16 18:20:24 +08:00
Vincent Koc
863069e2c6 fix(trajectory): stop cyclic transcript exports 2026-05-16 18:09:40 +08:00
Vincent Koc
46a67d30af fix(ci): restore persisted state guards 2026-05-16 18:06:13 +08:00
Peter Steinberger
b05fcad7a7 docs: add Crabbox human handoff preflight 2026-05-16 11:02:08 +01:00
Vincent Koc
9eeb17fa82 fix(providers): harden search tool response schemas 2026-05-16 18:00:31 +08:00
Vincent Koc
33be0fbea7 fix(plugins): accept deactivate hook alias 2026-05-16 17:47:14 +08:00
Peter Steinberger
6171b4254d fix(model-picker): show effective runtime choices 2026-05-16 10:34:49 +01:00
Peter Steinberger
7e0e29ef17 fix(cron): preserve current session identity after compaction 2026-05-16 10:34:48 +01:00
Sahil Satralkar
6d25ae5e0c fix(cli): finalize context engine turns (#81869)
* fix(cli): finalize context engine turns

* fix(cli): avoid context engine prepare leak

* fix(cli): keep context engine alive after turns

* fix(cli): complete context engine lifecycle

* fix(cli): preserve context engine maintenance contracts

* fix(cli): align context engine transcript finalization

* fix(cli): close context engine lifecycle gaps

* fix(cli): keep context engine snapshots current

* fix(cli): preserve context snapshot entry types

* fix(cli): clean up failed context engine prepare

* fix(cli): detect resolved session transcripts

* fix(cli): preserve context engine lifecycle ownership

* docs(changelog): credit CLI context engine fix

---------

Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-05-16 17:28:36 +08:00
Peter Steinberger
9c5acb7ea3 chore: release 2026.5.17 2026-05-16 10:11:41 +01:00
Ted Li
832b65ccea fix(agents): preserve Pi tool result error flags (#81546) (#81564)
Merged via squash.

Prepared head SHA: 320c867fda
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-05-16 17:09:22 +08:00
Frank Yang
adca6e9c55 fix(gateway): abort gmail watcher reload starts (#82499)
* fix(gateway): abort gmail watcher reload starts

* fix(gateway): prevent stopped gmail reload starts
2026-05-16 16:41:43 +08:00
Ayaan Zaidi
aedcf0f897 fix(telegram): recover polling spool after restart (#82256) (thanks @VACInc) 2026-05-16 14:04:47 +05:30
Ayaan Zaidi
89a3b9a07e fix(telegram): avoid stealing live spool claims 2026-05-16 14:04:47 +05:30
Ayaan Zaidi
784ee94108 test(telegram): align spooled claim expectations 2026-05-16 14:04:47 +05:30
Ayaan Zaidi
494517a990 refactor(telegram): simplify spool claim recovery 2026-05-16 14:04:47 +05:30
VACInc
59d2f88e41 fix(telegram): recover restart spool claims 2026-05-16 14:04:47 +05:30
Josh Avant
c5b3352326 Fix doctor repair for disabled Codex runtime plugin (#82502)
* fix doctor codex plugin runtime repair

* add changelog for codex doctor repair

* avoid implicit codex repair without agent routes

* expect codex repair in doctor config flow
2026-05-16 03:16:54 -05:00
Josh Avant
e57b137aef fix(codex): enforce native tool policy (#82496)
* fix(codex): enforce native tool policy

* docs: add changelog for codex native policy fix

* fix(codex): satisfy native hook relay lint
2026-05-16 03:02:28 -05:00
Josh Avant
23f73b3ecf Fix session reset files and ACPX orphan reaping (#82459)
* fix gateway reset transcript rotation

* fix acpx orphan adapter reaping

* add changelog for pr 82459

* fix ci session metadata normalization

* preserve canonical session ids in store reads

* fix session metadata id normalization
2026-05-16 02:43:30 -05:00
samzong
2fa853dce5 fix(gateway): isolate gmail watcher restart and abort handling (#82395)
Merged via squash.

Prepared head SHA: 61502846df
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-05-16 15:31:51 +08:00
Vincent Koc
e975c3b212 changelog: dedupe MCP cancellation bullets, add Fixes #82424 2026-05-16 15:02:41 +08:00
joshavant
df0d061c7a docs: add mcp tool cancellation changelog 2026-05-16 01:53:47 -05:00
Josh Avant
916234977a fix(telegram): drop expired approval callbacks (#82455)
* fix(telegram): drop expired approval callbacks

* changelog: credit expired Telegram callback fix
2026-05-16 01:43:17 -05:00
Vincent Koc
e650f8930d fix(test): avoid scanning plugin archive entries 2026-05-16 14:35:24 +08:00
Vincent Koc
31bbe6eb0f fix(test): avoid scanning status runtime bundles 2026-05-16 14:31:43 +08:00
Vincent Koc
f1f7525aa5 fix(test): avoid scanning legacy config ownership 2026-05-16 14:29:06 +08:00
Vincent Koc
2be9abfb1c fix(test): avoid scanning bundled plugin metadata 2026-05-16 14:26:17 +08:00
Vincent Koc
4b940f66fd fix(test): avoid scanning live shard roots 2026-05-16 14:22:48 +08:00
Vincent Koc
41b4eb97f0 fix(test): avoid scanning prompt snapshots 2026-05-16 14:20:17 +08:00
Vincent Koc
8b7d28354e fix(test): avoid scanning plugin docs examples 2026-05-16 14:16:28 +08:00
Vincent Koc
2bcb0abbb8 fix(test): avoid scanning channel docs examples 2026-05-16 14:13:40 +08:00
Vincent Koc
f9d015346e fix(test): avoid scanning fs-safe import boundary 2026-05-16 14:10:45 +08:00
Vincent Koc
2bdbf240a9 fix(media): avoid staged image extensions for containers 2026-05-16 14:09:39 +08:00
Vincent Koc
6920ec6c54 fix(config): preserve include writes with plugin validation skip 2026-05-16 14:08:36 +08:00
Vincent Koc
5d4166a368 fix(test): avoid scanning publishable plugin packages 2026-05-16 14:07:47 +08:00
Vincent Koc
306ca4d3eb fix(media): distrust image hints for container bytes 2026-05-16 14:05:02 +08:00
Vincent Koc
3bc7d4061b fix(gateway): preserve partial transcript branches 2026-05-16 14:04:41 +08:00
Vincent Koc
ae42768e5f fix(test): avoid scanning bundled plugin naming guardrails 2026-05-16 14:04:14 +08:00
Vincent Koc
c98ccbe513 fix(test): avoid scanning runtime registry boundary 2026-05-16 14:01:15 +08:00
Vincent Koc
bd81a0323c fix(pairing): skip malformed pending pairing requests 2026-05-16 13:59:23 +08:00
Vincent Koc
bf7bd8dcd1 fix(test): avoid scanning outbound threading guardrails 2026-05-16 13:56:59 +08:00
Josh Avant
4159a11ea3 Fix stale bootstrap file breaking channel agent turns (#82463)
* fix agents stale bootstrap context

* docs changelog stale bootstrap fix
2026-05-16 00:55:18 -05:00
Vincent Koc
da8c11aaae fix(test): avoid scanning tool boundary modules 2026-05-16 13:53:17 +08:00
Gio Della-Libera
dccf5f6842 fix(gateway): fire session:patch hooks for model changes (#82257)
* Fire session patch hooks for model changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: refresh CI after main repairs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 22:44:01 -07:00
Gio Della-Libera
8c9ec0724e fix(agents): honor disabled reasoning in thinking policy (#81454)
* fix(agents): honor disabled reasoning in thinking policy

* test: refresh thinking policy CI fixtures

* test: align thinking policy CI guardrails

---------

Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-15 22:33:43 -07:00
Gio Della-Libera
9aec9200f1 fix(agents): honor OPENCLAW_WORKSPACE_DIR fallback (#81447)
Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-15 22:32:02 -07:00
Gio Della-Libera
d7d85d1eb6 fix(cron): bootstrap external channel delivery targets (#82371)
* fix(cron): bootstrap external channel delivery targets

Allow isolated cron delivery target resolution to opt into outbound channel plugin bootstrap when the loaded-plugin fast path misses. Thread the explicit allowBootstrap flag through resolveOutboundTarget so externalized non-startup channel plugins can be resolved without changing default hot-path behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: refresh proof for cron bootstrap PR

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 22:31:56 -07:00
Vincent Koc
0642b0033b fix(plugins): reject omitted package runtime files 2026-05-16 13:18:54 +08:00
Vincent Koc
7f46876a5d fix(auth): sanitize persisted device auth tokens 2026-05-16 13:18:22 +08:00
Vincent Koc
29951fdbb7 fix(test): avoid scanning bundled channel shape roots 2026-05-16 13:17:53 +08:00
Josh Avant
51f4a5e8a0 Fix Telegram presentation-only payload sends (#82449)
* fix telegram presentation payload fallback

* changelog telegram presentation payload fallback

* fix telegram presentation reply delivery
2026-05-16 00:16:51 -05:00
Vincent Koc
92492ecdd0 fix(gateway): skip malformed compaction checkpoints 2026-05-16 13:14:56 +08:00
Vincent Koc
ba48d162af fix(commitments): sanitize persisted reminder metadata 2026-05-16 13:12:01 +08:00
Vincent Koc
af8d2f2948 fix(config): harden persisted boundary repair 2026-05-16 13:12:00 +08:00
Vincent Koc
37ba583b05 fix(sessions): validate persisted entry metadata 2026-05-16 13:12:00 +08:00
Vincent Koc
05b3774c28 fix(cron): skip malformed persisted jobs 2026-05-16 13:11:59 +08:00
Vincent Koc
24e4dc68b7 fix(tasks): validate persisted requester origins 2026-05-16 13:11:59 +08:00
Vincent Koc
2c9f284c1e fix(test): avoid walking plugin boundary invariant scans 2026-05-16 13:09:36 +08:00
Vincent Koc
80a563b4e4 changelog: credit MCP plugin tool abort-signal forwarding (#82443) 2026-05-16 13:03:15 +08:00
Josh Avant
b7d61c8daf fix: forward MCP tool abort signals (#82443)
* fix: forward MCP tool abort signals

* test: repair doctor health context fixtures
2026-05-16 00:01:10 -05:00
Gio Della-Libera
c8bec51869 OC Path: add dry-run diff output (#81437)
* OC Path: add dry-run diff output

* fix(oc-path): require dry-run for diff output

* fix(oc-path): show final newline diff

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(oc-path): show line-ending-only dry-run diffs

---------

Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 21:54:06 -07:00
hcl
f436b4310a fix(feishu): stream CardKit text deltas (#82419)
Fixes #82417.

Co-authored-by: hclsys <7755017+hclsys@users.noreply.github.com>
2026-05-16 12:51:55 +08:00
Vincent Koc
f78434985a fix(update): skip plugin validation during package repair reads 2026-05-16 12:44:33 +08:00
Vincent Koc
33685e1474 fix(codex): remove redundant context text coercion 2026-05-16 12:30:59 +08:00
Vincent Koc
abf78a0727 fix(providers): reject malformed poll status payloads 2026-05-16 12:20:42 +08:00
Vincent Koc
bc81d243ba fix(providers): harden model catalog response schemas 2026-05-16 12:16:42 +08:00
Vincent Koc
c8c6df73a9 fix(providers): harden embedding response schemas 2026-05-16 12:16:42 +08:00
Vincent Koc
202dd7590d fix(providers): harden audio response schemas 2026-05-16 12:16:41 +08:00
Josh Lehman
639107b7db fix: repair codex context-engine test typing 2026-05-15 21:14:54 -07:00
Vincent Koc
872e068470 fix(test): avoid walking plugin sdk subpath scans 2026-05-16 12:14:10 +08:00
Vincent Koc
0eef4c49f6 fix(plugins): ignore malformed catalog metadata 2026-05-16 12:09:22 +08:00
Josh Avant
0240cc578c fix: repair source-only official plugin installs (#82425)
* fix: repair source-only official plugin installs

* docs: add changelog for official plugin repair
2026-05-15 23:04:15 -05:00
Josh Lehman
80ca48418a feat(codex): bind context-engine projections to codex threads (#82351)
* feat(codex): bind context-engine projections to codex threads

* fix: harden Codex context-engine projection

* fix: remove unused Codex projection helper

* fix(codex): adopt compacted context-engine transcripts
2026-05-15 20:59:38 -07:00
Shadow
90ae151154 fix: prevent barnacle vetoing clawsweeper proof 2026-05-15 22:25:29 -05:00
Brad
372a8e4d22 Add wait mode for manual cron runs (#81929)
Adds wait mode for manual cron runs, exact run-id lookup for cron run logs, protocol/Swift schema support, and docs/changelog coverage.
2026-05-15 20:17:48 -07:00
Vincent Koc
9aa9c57625 fix(doctor): skip unsupported group allowFrom repair 2026-05-16 11:14:53 +08:00
Vincent Koc
c2c7396ffb fix(test): avoid walking core extension facade scans 2026-05-16 11:10:54 +08:00
Vincent Koc
bb07cbc2be fix(config): sanitize plugin session hydration 2026-05-16 11:02:41 +08:00
Vincent Koc
72581fbb48 fix(test): avoid walking package boundary scans 2026-05-16 11:02:08 +08:00
Vincent Koc
9c7e36bf81 fix(test): avoid walking extension runtime dependency scans 2026-05-16 10:54:25 +08:00
Vincent Koc
aa6ea4510c fix(test): avoid walking channel import guardrails 2026-05-16 10:46:44 +08:00
Vincent Koc
445c7d050a changelog: note foreign-bot /stop topic-lane scoping in #82162 entry 2026-05-16 10:43:05 +08:00
Vincent Koc
f1c8570e0c fix(test): avoid walking plugin sdk package guardrails 2026-05-16 10:38:13 +08:00
Vincent Koc
069ad23b74 fix(test): avoid scanning bundled capability metadata 2026-05-16 10:28:59 +08:00
Vincent Koc
81d1424ad9 fix(update): allow plugin convergence after package doctor repairs 2026-05-16 10:28:26 +08:00
Peter Steinberger
0d21524736 test(telegram): type targeted stop metadata stubs 2026-05-16 03:25:21 +01:00
Peter Steinberger
84c9286434 fix(telegram): keep foreign stop commands on topic lanes 2026-05-16 03:25:21 +01:00
Peter Steinberger
12d3ebe1e0 chore: credit stop abort fix 2026-05-16 03:25:21 +01:00
Peter Steinberger
9f7d606469 fix(gateway): match stop by stored session id 2026-05-16 03:25:21 +01:00
VACInc
b72f008183 fix: stop active turns reliably 2026-05-16 03:25:21 +01:00
Vincent Koc
37799a1437 fix(test): avoid walking plugin tool contracts 2026-05-16 10:23:28 +08:00
Vincent Koc
d095319b08 fix(test): avoid walking provider catalog guard 2026-05-16 10:15:49 +08:00
Vincent Koc
dc6528f9ff fix(config): warn for stale web search providers
Downgrade stale optional web search provider plugin installs to validation warnings so Gateway and doctor repair paths keep running after startup provider selection landed in #82376.

Refs #82313.
2026-05-16 10:14:57 +08:00
Vincent Koc
111b65a6fb fix(providers): harden video response schemas 2026-05-16 10:10:38 +08:00
Peter Steinberger
e01a885d18 fix: complete ended subagent cleanup after helper failures
Fixes #82306.
Supersedes #75462.

Co-authored-by: Sebastien Tardif <SebTardif@ncf.ca>
2026-05-16 03:09:29 +01:00
Vincent Koc
b3ede77528 fix(test): avoid walking provider family scans 2026-05-16 10:08:28 +08:00
Vincent Koc
6c8f49386c fix(config): sanitize pending delivery hydration 2026-05-16 10:06:49 +08:00
Peter Steinberger
cf7c46dff6 docs(codex): clarify raw tool output watchdog 2026-05-16 03:06:39 +01:00
joshavant
5d93fb3ed8 docs(changelog): add codex watchdog entry 2026-05-16 03:06:39 +01:00
joshavant
44a3301e50 fix(codex): keep raw tool output watchdog armed 2026-05-16 03:06:39 +01:00
Vincent Koc
3b663ad1c1 fix(gateway): harden image-named attachment sniffing 2026-05-16 10:02:28 +08:00
Peter Steinberger
7206811b80 fix: start xai oauth callback before browser 2026-05-16 03:02:07 +01:00
Peter Steinberger
8bc927b294 docs: clarify xai oauth coverage 2026-05-16 03:02:07 +01:00
Peter Steinberger
6208f2b678 fix: satisfy xai oauth lint 2026-05-16 03:02:07 +01:00
Peter Steinberger
af2b313194 feat: add xai grok oauth 2026-05-16 03:02:07 +01:00
Vincent Koc
2754dc895a fix(update): skip plugin schema validation during package doctor writes 2026-05-16 10:00:46 +08:00
Vincent Koc
ae594d263c fix(test): avoid walking message turn guardrails 2026-05-16 09:59:58 +08:00
Vincent Koc
48b3a1cff9 fix(plugins): reject escaped package metadata 2026-05-16 09:59:11 +08:00
joshavant
1ea48edeff docs: add changelog for web search startup fix 2026-05-16 02:56:04 +01:00
joshavant
37f8507b4b fix: start configured web search providers 2026-05-16 02:56:04 +01:00
Vincent Koc
4d2acd9481 fix(test): avoid walking lint suppression scans 2026-05-16 09:53:28 +08:00
Vincent Koc
5f85e6b692 fix(lint): remove redundant unknown unions 2026-05-16 09:48:28 +08:00
Vincent Koc
89a9b4e75a fix(plugins): preserve host package during managed peer repair 2026-05-16 09:48:28 +08:00
Vincent Koc
56aec53dde fix(test): avoid walking extension boundary scans 2026-05-16 09:47:50 +08:00
Vincent Koc
e403af2d85 fix(test): avoid walking CI node shard plans 2026-05-16 09:43:03 +08:00
Vincent Koc
31cc26230f fix(test): avoid scanning bundled source metadata 2026-05-16 09:36:04 +08:00
Vincent Koc
721fb6570e fix(agents): skip malformed gap-fill tails 2026-05-16 09:33:42 +08:00
Vincent Koc
a70c90a52b test(plugins): cover malformed npm package metadata 2026-05-16 09:29:40 +08:00
Vincent Koc
3064d6181d fix(test): avoid scanning static asset metadata 2026-05-16 09:26:56 +08:00
Vincent Koc
783ef1d22b Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix(msteams): sniff inline image attachments
  fix(providers): harden image response schemas
2026-05-16 09:17:22 +08:00
Vincent Koc
eb7a082b77 fix(providers): harden image response schemas 2026-05-16 09:16:01 +08:00
Vincent Koc
859286b95e Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix(msteams): sniff inline image attachments
2026-05-16 09:15:20 +08:00
Vincent Koc
b436927a43 fix(msteams): sniff inline image attachments 2026-05-16 09:14:52 +08:00
Vincent Koc
46b0fc0c0a fix(providers): harden image response schemas 2026-05-16 09:14:51 +08:00
Vincent Koc
14142a927d fix(msteams): sniff inline image attachments 2026-05-16 09:14:37 +08:00
Vincent Koc
7d96d1f41a fix(test): avoid scanning bundled build entries 2026-05-16 09:14:07 +08:00
Vincent Koc
c320da79ed fix(test): avoid scanning extension ids 2026-05-16 09:08:19 +08:00
Vincent Koc
c8cee2dce4 fix(voice-call): persist rejected replay keys 2026-05-16 09:02:28 +08:00
Vincent Koc
7ccd3b8e8e fix(test): avoid walking extension test plans 2026-05-16 09:02:10 +08:00
Vincent Koc
c4810e33f4 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix(test): avoid walking contract shard plans
2026-05-16 08:59:42 +08:00
Vincent Koc
f553dad560 Reapply "chore(release): set 2026.5.16 version"
This reverts commit 73aab6abd8.
2026-05-16 08:59:33 +08:00
Vincent Koc
2ee856985b fix(test): avoid walking contract shard plans 2026-05-16 08:57:57 +08:00
Vincent Koc
0e7cc1ca53 fix(test): avoid walking gateway suite targets 2026-05-16 08:47:05 +08:00
Vincent Koc
efabae2f9b fix(test): avoid walking unit-fast candidate roots 2026-05-16 08:39:00 +08:00
Josh Avant
a1e208ee26 Recover stale embedded tool calls during gateway diagnostics (#82369)
* fix(gateway): recover stale embedded tool calls

* chore(changelog): note stale embedded tool recovery
2026-05-15 19:36:59 -05:00
Vincent Koc
9a1fa5f23f fix(test): defer unit coverage source discovery 2026-05-16 08:32:56 +08:00
Vincent Koc
9c2044d3b7 changelog: credit reporters for #82239, #65867, #82167, #47444 follow-up 2026-05-16 08:28:13 +08:00
Vincent Koc
73aab6abd8 Revert "chore(release): set 2026.5.16 version"
This reverts commit b7e8f6da6a.
2026-05-16 08:20:19 +08:00
Josh Avant
1dac68c0bb fix fallback provenance across reloads (#82363) 2026-05-15 19:12:34 -05:00
Peter Steinberger
b08e0da25b fix: clarify provider timeout ceiling 2026-05-16 01:08:07 +01:00
Peter Steinberger
49e9382cc0 fix(configure): unify OpenAI auth provider picker (#82324) 2026-05-16 01:02:33 +01:00
Peter Steinberger
8d3b5c2d19 fix(auto-reply): abort active text stop runs 2026-05-16 01:00:52 +01:00
Josh Avant
64b94daf92 Fix gateway auth logout aborting active runs (#82346)
* fix gateway auth logout aborts active runs

* docs changelog for auth logout abort fix

* test fix auth logout typecheck

* test fix auth profile mock shape
2026-05-15 18:36:49 -05:00
Josh Avant
ea16a5e9e1 fix(codex): yield app-server notification projection (#82333)
* fix(codex): yield app-server notification projection

* docs(changelog): note codex notification yield fix
2026-05-15 16:53:10 -05:00
Jesse Merhi
6921d9072e Adopt Proxyline for managed proxy routing
Route managed HTTP/WebSocket/fetch interception through Proxyline 0.3.0, preserving Gateway loopback bypass behavior and root undici hardening.

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
2026-05-15 22:51:36 +01:00
Peter Steinberger
0ad3d25fb7 refactor(agents): share subagent cron fallback selection (#82328)
* fix(agents): honor subagent cron fallbacks

* refactor(agents): share cron subagent model selection

* test(agents): align subagent fallback policy expectation

* fix(agents): keep subagent fallbacks on selected model

* fix(agents): preserve subagent fallback-only overrides
2026-05-15 22:36:15 +01:00
Peter Steinberger
b7e8f6da6a chore(release): set 2026.5.16 version 2026-05-15 22:06:19 +01:00
Peter Steinberger
a87fcefe31 fix: repair release validation type and lint gates 2026-05-15 22:06:12 +01:00
Peter Steinberger
e22a7e45a4 fix(cron): honor subagent model fallbacks 2026-05-15 21:54:48 +01:00
Peter Steinberger
d0218d3e59 fix(telegram): retain transcript-backed truncated finals 2026-05-15 21:53:14 +01:00
Peter Steinberger
25a8f5f3f8 fix: surface stalled telegram ingress backlog 2026-05-15 21:52:43 +01:00
Peter Steinberger
a6dd9fdf08 fix(cron): separate failure notification delivery 2026-05-15 21:51:25 +01:00
Peter Steinberger
cce12697a1 fix(doctor): materialize group allowFrom fallback (#82316)
* fix(doctor): materialize group allowFrom fallback

* fix: normalize doctor account records
2026-05-15 21:47:49 +01:00
Peter Steinberger
903e246e87 fix: satisfy room-event strict smoke types 2026-05-15 21:47:46 +01:00
Peter Steinberger
2e5a86adfe fix: finish room event gating 2026-05-15 21:47:46 +01:00
Peter Steinberger
b9ffc0eda9 fix: cancel queued room event followups 2026-05-15 21:47:46 +01:00
Peter Steinberger
fa9c7ddadf fix: abort superseded telegram room events 2026-05-15 21:47:46 +01:00
Peter Steinberger
ad29f089e4 fix: preserve overlapping telegram room event correlations 2026-05-15 21:47:46 +01:00
Peter Steinberger
f1351bcbcf fix: carry room event gateway actions 2026-05-15 21:47:46 +01:00
Peter Steinberger
ddcfde1489 fix: propagate room event tool context 2026-05-15 21:47:46 +01:00
Peter Steinberger
4b11d65ada fix: keep telegram room events fully quiet 2026-05-15 21:47:46 +01:00
Peter Steinberger
b5fde36c41 fix: isolate telegram room event delivery correlation 2026-05-15 21:47:46 +01:00
Peter Steinberger
8c8241140e fix: keep room events quiet without breaking delivery fallback 2026-05-15 21:47:46 +01:00
Peter Steinberger
46409d30b7 fix: isolate telegram room event dispatch fence 2026-05-15 21:47:46 +01:00
Peter Steinberger
ba8b4377f7 fix: keep explicit quiet replies suppressed 2026-05-15 21:47:46 +01:00
Peter Steinberger
d4b98f0dc9 fix: gate ambient room event turns 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
61f3a4d71d fix(telegram): keep room events quiet 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
4db5cb1a20 fix(agents): submit quiet room events to model 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
7ed4ad8d17 fix(reply): clarify room event turn context 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
6f7897d8ad fix(channels): isolate inbound turn kind type 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
fb0f29b9cc fix(telegram): keep room events fully quiet 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
c2e659472a docs(channels): describe room events 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
56cc150771 feat(telegram): route ambient chatter as room events 2026-05-15 21:47:46 +01:00
Ayaan Zaidi
503c3d139c feat(reply): add room event turn semantics 2026-05-15 21:47:46 +01:00
Radek Sienkiewicz
3de97057d0 fix(codex): use stable hooks feature flag (#82078) 2026-05-15 13:35:49 -07:00
Peter Steinberger
445ed9b0b4 fix(auto-reply): clean up no-reply migration paths 2026-05-15 21:29:39 +01:00
Peter Steinberger
f0ceb3c5aa fix(auto-reply): restrict no-reply to automatic groups 2026-05-15 21:29:39 +01:00
Peter Steinberger
68e0cdc478 fix(auto-reply): avoid NO_REPLY prompt in direct chats 2026-05-15 21:29:39 +01:00
Peter Steinberger
333f65fc8a fix: tighten release tooling checks 2026-05-15 21:21:17 +01:00
Vincent Koc
0b7ff665f6 fix(trajectory): report malformed export rows 2026-05-16 04:07:22 +08:00
Vincent Koc
ba8a6499f0 fix(providers): harden malformed success responses 2026-05-16 03:57:25 +08:00
Vincent Koc
ea4e3cd4fa fix(config): harden persisted store shapes 2026-05-16 03:52:22 +08:00
Peter Steinberger
628c753f3b docs: document wildcard runtime policy 2026-05-15 20:50:49 +01:00
Peter Steinberger
638bfc0bf5 fix: honor wildcard model runtime policy 2026-05-15 20:50:49 +01:00
Vincent Koc
dd4613a268 fix(test): reduce changed-target import graph IO 2026-05-16 03:47:47 +08:00
Vincent Koc
9e9a825aa5 fix(media): sniff input file payloads 2026-05-16 03:47:08 +08:00
Vincent Koc
d2e0a8231f fix(plugins): reject malformed package entries 2026-05-16 03:44:33 +08:00
Peter Steinberger
4add9bab77 fix(discord): harden read message results (#82276)
Validate Discord read message results before normalizing channel history, preserving the Discord array contract while replacing opaque map crashes with a clear boundary error.

Fixes #82252.
2026-05-15 20:42:41 +01:00
Peter Steinberger
8ba0bb2a8a fix: scope local agent gateway dispatch (#82284) 2026-05-15 20:35:53 +01:00
Vincent Koc
947c9512c3 changelog: add codex MCP agent scoping and Responses cached usage clamp 2026-05-16 03:24:53 +08:00
Peter Steinberger
d5eec80e34 fix(agents): honor completions token aliases (#82278) 2026-05-15 20:19:41 +01:00
Vincent Koc
f964b1c3aa fix(agents): clamp Responses cached usage 2026-05-16 03:17:31 +08:00
Sergio Cadavid
472523360d fix(codex): scope user MCP servers by agent (#82180) 2026-05-15 20:17:16 +01:00
Vincent Koc
dea6207dd1 fix(plugins): require npm package compatibility metadata 2026-05-16 03:08:42 +08:00
Peter Steinberger
532759e1ab docs: add streaming usage changelog entry 2026-05-15 20:07:53 +01:00
Peter Steinberger
b376780715 fix(openai): keep volcengine streaming usage opt-in 2026-05-15 20:07:53 +01:00
Rui Xu
ff6b38750e fix(openai): honor streaming usage compat 2026-05-15 20:07:53 +01:00
Peter Steinberger
66f89540c2 fix(gateway): raise lifecycle hook timeout defaults
Raise bounded gateway lifecycle hook wait budgets to 5 seconds for shutdown and 10 seconds for pre-restart, keeping the fix to defaults only instead of adding config surface.

Includes regression coverage, hook docs, changelog credit for @bryanbaer, and replaces #82186 with the narrower maintainer fix.
2026-05-15 19:53:58 +01:00
Vincent Koc
a6aa48350b fix: route crabbox proof through brokered aws 2026-05-16 02:44:06 +08:00
Vincent Koc
b21d3e5362 fix(plugins): verify packaged setup runtime entries 2026-05-16 02:32:32 +08:00
Vincent Koc
317f1f8b5b fix(media): reject malformed sniff base64 2026-05-16 02:29:18 +08:00
Vincent Koc
4e10969ade fix(codex): ignore empty rate-limit buckets 2026-05-16 02:15:36 +08:00
Peter Steinberger
48b4e5b361 docs: add cron delivery mirror changelog 2026-05-15 18:49:30 +01:00
Peter Steinberger
c3a211993c fix(cron): preserve isolated delivery awareness policy 2026-05-15 18:49:30 +01:00
Cavit Erginsoy
5ce1e45848 test: isolate media provider registry mocks 2026-05-15 18:49:30 +01:00
Cavit Erginsoy
f3403708a3 fix(cron): tighten session mirror classification 2026-05-15 18:49:30 +01:00
Cavit Erginsoy
7de413499f fix(cron): mirror announce delivery into destination session 2026-05-15 18:49:30 +01:00
Peter Steinberger
7270bb95b7 fix(telegram): drain outbound queue after polling reconnect (#82227)
* fix(telegram): drain outbound queue after polling reconnect

* fix(telegram): keep reconnect drain backoff-safe
2026-05-15 18:46:18 +01:00
Peter Steinberger
6ca9de1e0a refactor: deprecate legacy reply history helpers (#82236) 2026-05-15 18:44:04 +01:00
Peter Steinberger
fc9798a788 refactor: resolve cron delivery from context (#82241) 2026-05-15 18:42:52 +01:00
Peter Steinberger
daef8e73fc fix: prune replay control messages 2026-05-15 18:35:46 +01:00
Ayaan Zaidi
c7dcf79585 fix(qa): emit Discord trigger timing artifacts 2026-05-15 23:00:09 +05:30
Peter Steinberger
1bcc071385 ci(release): harden beta validation gates 2026-05-15 18:28:52 +01:00
Zach Knickerbocker
7715b29aa2 fix(codex): preserve inbound sender metadata
Summary:
- Preserve inbound sender metadata and source-channel provenance in Codex app-server prompt mirrors.
- Reuse the shared prompt-mirror builder for normal and `turn/start` failure snapshots.
- Add regression coverage for provider variants such as `discord-voice` while keeping `sourceChannel` on the originating channel.

Verification:
- `pnpm test extensions/codex/src/app-server/event-projector.test.ts extensions/codex/src/app-server/run-attempt.test.ts`
- `pnpm exec oxfmt --check extensions/codex/src/app-server/transcript-mirror.ts extensions/codex/src/app-server/event-projector.test.ts extensions/codex/src/app-server/run-attempt.test.ts`
- `git diff --check temp/landpr-82184..HEAD`
- `/Users/steipete/Projects/agent-scripts/skills/codex-review/scripts/codex-review --parallel-tests "pnpm test extensions/codex/src/app-server/event-projector.test.ts extensions/codex/src/app-server/run-attempt.test.ts"`
2026-05-15 18:12:16 +01:00
Peter Steinberger
011f98c5a6 docs: note file-transfer lazy policy loading (#82211) 2026-05-15 18:12:10 +01:00
samzong
c746ec5fc7 fix(file-transfer): avoid eager invoke policy load
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-15 18:12:10 +01:00
Peter Steinberger
dd457474b3 fix(ui): avoid inline image previews in webchat send (#82228) 2026-05-15 18:08:52 +01:00
Peter Steinberger
e51d27d2ac chore(release): refresh plugin SDK API baseline 2026-05-15 18:00:31 +01:00
Peter Steinberger
ae5591eca9 test(release): use usage accumulator fixture 2026-05-15 17:57:14 +01:00
Peter Steinberger
8ac30279b3 fix: strip delivery function response leaks
Strip adjacent plural function-call/function-response XML on delivery paths while preserving prose examples.
2026-05-15 17:47:27 +01:00
Peter Steinberger
30e2654ac2 fix: route active-memory Telegram recall through provider 2026-05-15 17:46:42 +01:00
Peter Steinberger
f1708f8b14 fix(release): satisfy preflight lint 2026-05-15 17:46:26 +01:00
Peter Steinberger
69eb76b9bb docs: require regression provenance in PR reviews 2026-05-15 17:40:15 +01:00
Peter Steinberger
4859edd9f8 test(release): align hosted runner assertions 2026-05-15 17:34:29 +01:00
Peter Steinberger
55c275b00a ci(release): require full validation before npm publish 2026-05-15 17:33:28 +01:00
Peter Steinberger
3e5070efbf fix(release): preserve provider reasoning replay 2026-05-15 17:33:28 +01:00
Peter Steinberger
6330fe607d fix(release): verify npm tarball before publish 2026-05-15 17:33:28 +01:00
Peter Steinberger
e79e5dbbdf test(release): align plugin contract assertions 2026-05-15 17:33:28 +01:00
Peter Steinberger
1ba75463a5 test(release): align plugin prerelease contracts 2026-05-15 17:33:28 +01:00
Peter Steinberger
4e6c85d930 refactor: route remaining channel history through window (#82220) 2026-05-15 17:27:00 +01:00
Peter Steinberger
c96795d272 ci(release): use hosted runners for manual release gates 2026-05-15 17:25:32 +01:00
Peter Steinberger
8338412b54 fix: cover per-peer LINE cron recovery (#81704) 2026-05-15 17:22:58 +01:00
許元豪
f3f2c784c4 fix(line): reject lowercased LINE-shaped recipients before push (#81628)
Defense-in-depth safety net for #81628: even with the cron-tool fix in
place, any other code path that ever produces a 33-char LINE-shaped
recipient missing its leading capital (C/U/R) would otherwise hit the
LINE API and return HTTP 400 with no permanent-error signal, causing
delivery-recovery to retry five times before moving the entry to
failed/.

normalizeTarget now throws "Recipient is not a valid LINE id ..." when
the post-strip value looks like a LINE id but the case was lost. The
message matches the existing /recipient is not a valid/i pattern in
delivery-queue-recovery's PERMANENT_ERROR_PATTERNS, so recovery moves
the entry to failed/ on the first attempt instead of silently retrying.

Short fixtures (length < 33) are left alone so existing tests using
"U123", "line:user:1", etc. keep working.
2026-05-15 17:22:58 +01:00
許元豪
ac2e72a8e6 fix(cron): refuse LINE session-key recipient fallback (#81628)
LINE chat ids are case-sensitive (push requires capital C/U/R) but the
session key holds the peer id lowercased for canonical routing. When
cron-tool runs without currentDeliveryContext (delivery-recovery, queue
replay after reply-token expiry), inferDeliveryFromSessionKey was
lifting the lowercased fragment straight into delivery.to, producing a
value LINE rejects with HTTP 400 — the job retried five times silently
and the dashboard reported "delivered" while the LINE group received
nothing.

Refuse the session-key fallback for channel === "line" so the missing
target surfaces explicitly instead of scheduling an undeliverable job.
2026-05-15 17:22:58 +01:00
Peter Steinberger
d5b87672f8 fix(mac): disarm legacy update launchd jobs 2026-05-15 17:20:41 +01:00
Peter Steinberger
cf79689ca1 fix: strip attributed final tags
Fix Gemini/Gemma attributed and self-closing <final> tag leaks across sanitizer, reasoning cleanup, and embedded Pi streaming enforcement.\n\nProof posted in PR body: focused Vitest, formatting, diff check, real Google Gemini/OpenRouter/local Gemma live output.
2026-05-15 17:18:24 +01:00
Peter Steinberger
65dd71d42d fix: preserve cron session transcript rotation (#82200)
* fix: preserve cron session transcript rotation

* chore: refresh pr checks
2026-05-15 17:00:42 +01:00
Peter Steinberger
c6ddb1afb7 fix: preserve media completion message-tool delivery (#82206)
* fix: preserve message-tool media completion delivery

* chore: update generated protocol models
2026-05-15 16:49:52 +01:00
Peter Steinberger
29b5563ccd fix: strip adjacent function response scaffolding (#82155)
Summary:
- Strip adjacent function_response workflow output after stripped XML tool-call scaffolding.
- Cover multiline, compact, dangling, chained, prose-like, and same-line-tail response forms.
- Add regression coverage for the production sanitizeUserFacingText path and the shared assistant-visible-text sanitizer.

Verification:
- node scripts/run-vitest.mjs src/shared/text/assistant-visible-text.test.ts src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts -- --reporter=verbose
- git diff --check origin/main...HEAD
- /Users/steipete/Projects/agent-scripts/skills/codex-review/scripts/codex-review --mode branch --base origin/main --full-access --output /tmp/codex-review-82155-rerun.txt --parallel-tests "node scripts/run-vitest.mjs src/shared/text/assistant-visible-text.test.ts src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts -- --reporter=verbose"
- GitHub Real behavior proof: https://github.com/openclaw/openclaw/actions/runs/25926897171
2026-05-15 16:48:33 +01:00
Peter Steinberger
9c38948700 docs: note sharp libvips install workaround 2026-05-15 16:32:04 +01:00
Peter Steinberger
9ee93e8ea7 docs: document channel turn guardrails
Document the channel-turn media/history guardrails and add a focused regression test for migrated message paths.
2026-05-15 16:27:34 +01:00
Peter Steinberger
4718e6272c docs: add tts command changelog (#82174) 2026-05-15 16:23:38 +01:00
Rui Xu
2204b25f1d fix(tts): preserve command voice delivery decision 2026-05-15 16:23:38 +01:00
Peter Steinberger
5aefc9dda4 refactor: centralize channel turn media facts
Centralize channel-turn media fact shaping in core and route Discord/Slack through the shared helper.
2026-05-15 16:21:06 +01:00
Vincent Koc
1b62168a3a fix(media): reject malformed generated base64 2026-05-15 23:20:46 +08:00
Peter Steinberger
06ec35452f test: deduplicate OpenRouter reasoning replay regression 2026-05-15 16:17:50 +01:00
Peter Steinberger
c5a4d7af41 fix: avoid OpenRouter DeepSeek V4 empty reasoning replay 2026-05-15 16:17:50 +01:00
luyao618
25864ee540 fix(transport): strip empty-string reasoning_content from OpenRouter assistant replay
DeepSeek V4 via OpenRouter injects reasoning_content: "" on assistant
messages that contain tool_calls.  The sanitizer only deleted the field
when it was not a string, so empty strings slipped through and were
replayed on follow-up turns.  OpenRouter rejects the field with an
HTTP 500 Internal Server Error instead of a descriptive 4xx, breaking
every subsequent tool-call turn for the session.

Also strip empty-string reasoning for the same reason.

Closes #82150
2026-05-15 16:17:50 +01:00
Merlin
127156a88a fix(codex): fail fast after quiescent app-server turns
Fix Codex app-server turns that go quiet after the last non-assistant current-turn item completes without turn/completed.\n\nMaintainer proof: focused watchdog regression passed on rebased head, diff whitespace check passed, and local OpenClaw CLI + WebSocket app-server transport proof observed turn/interrupt after the short idle watchdog.\n\nCo-authored-by: funmerlin <funmerlin@users.noreply.github.com>
2026-05-15 16:16:33 +01:00
Peter Steinberger
bbf50a406e fix: keep Discord prompt metadata structured (#82168) 2026-05-15 16:12:11 +01:00
Peter Steinberger
2eee70e0a6 refactor: run prepared Discord and Slack turns
Route Discord and Slack prepared message turns through the core prepared-turn runner directly.

Local proof before landing:
- node scripts/run-vitest.mjs src/channels/turn/kernel.test.ts extensions/discord/src/monitor/message-handler.process.test.ts extensions/slack/src/monitor/message-handler/prepare.test.ts extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts
- node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental false
- node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental false
- OPENCLAW_TESTBOX_REMOTE_RUN=1 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm check:changed
- codex-review clean after accepted Slack bot-loop history cleanup finding was fixed in core

GitHub checks had no failures; Blacksmith/GitHub runner jobs were still queued when maintainer approved landing based on local proof.
2026-05-15 16:06:22 +01:00
Peter Steinberger
369917ff79 fix: surface cron model override diagnostics 2026-05-15 16:04:49 +01:00
Peter Steinberger
9393be2e4b chore(release): refresh generated release baselines 2026-05-15 15:57:01 +01:00
Peter Steinberger
4780e69352 fix(line): acknowledge webhooks before agent processing 2026-05-15 15:49:36 +01:00
Peter Steinberger
99dfb8291f fix(providers): preserve Kimi MiMo reasoning replay (#82170)
* fix(providers): preserve Kimi MiMo reasoning replay

* chore: rerun provider replay ci
2026-05-15 15:44:34 +01:00
Peter Steinberger
abbfd276ee ci(release): stringify candidate workflow fields 2026-05-15 15:43:43 +01:00
Peter Steinberger
d24a847279 test(doctor): type legacy compaction fixtures (#80645) 2026-05-15 15:33:27 +01:00
Peter Steinberger
76ec830c7a docs: add onboarding i18n changelog (#80645) (thanks @GaosCode) 2026-05-15 15:33:27 +01:00
Peter Steinberger
80b5a0138f test(zalouser): align history expectation after rebase 2026-05-15 15:33:27 +01:00
Peter Steinberger
40789da1ef fix(wizard): narrow setup i18n SDK surface 2026-05-15 15:33:27 +01:00
MrBrain
6472b05fad feat(plugins): localize channel setup wizards 2026-05-15 15:33:27 +01:00
MrBrain
bfc674876d feat(wizard): localize onboarding flows 2026-05-15 15:33:27 +01:00
MrBrain
d8ae3ec4c8 feat(wizard): add cli i18n catalog 2026-05-15 15:33:27 +01:00
Peter Steinberger
492f59e586 ci(release): preserve candidate JSON parse cause 2026-05-15 15:32:17 +01:00
Peter Steinberger
41810a462e fix(discord): suppress link embeds by default
* fix(discord): suppress link embeds by default

* fix(discord): handle missing stream config
2026-05-15 15:22:54 +01:00
Peter Steinberger
1b87ba8ca5 docs(codex): document native compaction behavior 2026-05-15 15:17:12 +01:00
Peter Steinberger
031655b933 fix(doctor): migrate codex compaction config 2026-05-15 15:17:12 +01:00
Peter Steinberger
934fc6ceeb fix(codex): keep app-server compaction native 2026-05-15 15:17:12 +01:00
Peter Steinberger
d61051558b ci(release): harden candidate workflow dispatch lookup 2026-05-15 15:10:34 +01:00
Peter Steinberger
adac07f1d8 ci(release): publish validation manifest on main 2026-05-15 14:55:59 +01:00
Peter Steinberger
c91e20ac0c ci(release): add candidate evidence checklist 2026-05-15 14:54:46 +01:00
Otto Deng
2d91a3b200 fix(gateway/approvals): treat turnSourceTo as optional in chat approval bridge (#82132)
canBridgeNoDeviceChatApprovalFromBackend used matchesRequiredString for
turnSourceTo, which returns false when expected is null. Channels without
a recipient concept (webchat, control-ui) leave turnSourceTo null on both
the approval snapshot and the replay params, so every backend
gateway-client replay was rejected with APPROVAL_CLIENT_MISMATCH after
the approval prompt was answered. turnSourceAccountId and turnSourceThreadId
in the same function already use matchesOptionalString for the same reason;
turnSourceTo was missed when PR #78728 added the helper.

Switch to matchesOptionalString so null-on-both-sides matches. Cross-channel
replay protection is preserved by the existing required turnSourceChannel
and sessionKey checks. Added a regression test asserting webchat replay
with null turnSourceTo is accepted.
2026-05-15 14:51:31 +01:00
Peter Steinberger
1e31bd2ac2 fix(codex): time out silent app-server turns 2026-05-15 14:46:33 +01:00
Peter Steinberger
111a17b6e8 chore: publish 2026.5.12 mac appcast 2026-05-15 14:37:36 +01:00
Yash Saliya
d91f58ee25 fix(message-tool): rename type schema property to avoid JSON Schema keyword collision (#78920)
Merged via squash.

Prepared head SHA: 669084aad8
Co-authored-by: YashSaliya <79741768+YashSaliya@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-15 16:29:56 +03:00
Peter Steinberger
f74436dc81 refactor: assemble channel contexts in core 2026-05-15 14:14:02 +01:00
Jerome Xu
8cc1aee9d8 fix(xiaomi): surface MiMo reasoning-only finals (#60304)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-15 14:12:44 +01:00
Peter Steinberger
bc461965a3 fix: document cron runtime plugin preload (#82111) 2026-05-15 14:04:56 +01:00
Peter Steinberger
38e53a89b2 fix(cron): align runtime plugin preload mode 2026-05-15 14:04:56 +01:00
郑苏波 (Super Zheng)
5779d485fd fix(cron): lazily load runtime plugins to fix external channel resolution
Ensure runtime plugins are loaded before resolving cron delivery context,
preventing multi-channel ambiguity errors when using external channels.
Implemented via a lazy facade to preserve fast isolated agent startup.
2026-05-15 14:04:56 +01:00
Peter Steinberger
6de8563827 refactor: centralize channel history window 2026-05-15 13:56:17 +01:00
Peter Steinberger
56303b96d0 fix(slack): clarify finalized draft guard 2026-05-15 13:48:55 +01:00
Neerav Makwana
2b7c150de3 fix(slack): preserve finalized draft after tool warning 2026-05-15 13:48:55 +01:00
Eden
b67bcd93cc fix(twitch): keep account monitor alive until abort (#81853)
Summary:
- Keep Twitch startAccount alive until abort via runStoppablePassiveMonitor.
- Add lifecycle regression coverage and env-gated live Twitch IRC proof.
- Add changelog credit for #60071 / #81853.

Verification:
- pnpm test extensions/twitch/src/plugin.lifecycle.test.ts extensions/twitch/src/plugin.test.ts extensions/twitch/src/twitch-client.test.ts src/gateway/server-channels.test.ts
- pnpm exec oxfmt --check --threads=1 extensions/twitch/src/plugin.ts extensions/twitch/src/plugin.lifecycle.test.ts extensions/twitch/src/plugin.live.test.ts CHANGELOG.md
- pnpm test:live -- extensions/twitch/src/plugin.live.test.ts (skipped without Twitch live credentials)
- codex-review --mode branch --parallel-tests targeted Twitch/gateway tests
- GitHub checks on aea52056c6 green

Fixes #60071.

Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>
2026-05-15 13:47:10 +01:00
Peter Steinberger
e0f7dafcea docs: require codex review before landing 2026-05-15 13:41:34 +01:00
Peter Steinberger
2ea0c6c929 docs(slack): align unfurl default docs (#82123) 2026-05-15 13:25:52 +01:00
Kibi
cb695b0986 fix(slack): default unfurl_links to false for outbound messages
Slack link unfurls (inline message previews) are enabled by default
when unfurl_links is not explicitly set in chat.postMessage. This means
bot messages containing Slack message links or URLs automatically expand
into rich preview cards, which can be noisy in channels.

Default unfurl_links to false so outbound messages don't show inline
link previews unless the operator explicitly opts in via:

  channels.slack.unfurlLinks: true

unfurlMedia remains opt-in (only sent when explicitly configured).
2026-05-15 13:25:52 +01:00
Peter Steinberger
b4f6cb29b8 fix(slack): break modal routing import cycle 2026-05-15 13:18:55 +01:00
Peter Steinberger
d89732efca fix(slack): route plugin modal submissions
Co-authored-by: shannon0430 <258282406+shannon0430@users.noreply.github.com>

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>

Co-authored-by: Jin Kim <198280395+jink-ucla@users.noreply.github.com>
2026-05-15 13:18:55 +01:00
Peter Steinberger
cda4689d71 fix(auto-reply): document silent reply fallback fix (#82086) (thanks @taozengabc) 2026-05-15 13:18:16 +01:00
Peter Steinberger
391b4916dc fix(auto-reply): cover surface silent reply fallback 2026-05-15 13:18:16 +01:00
taozengabc
a541aa3b0b fix(auto-reply): honor silentReply policy on group failure-fallback path
Threads the runtime config through buildKnownAgentRunFailureReplyPayload
into resolveExternalRunFailureTextForConversation so the documented
agents.defaults.silentReply / surfaces.<id>.silentReply policy is
consulted before silencing failure copy in groups/channels. Default
policy (group: allow, direct: disallow, internal: allow) preserves the
existing 'groups stay quiet on generic runner failure' behavior; opting
into silentReply.group: disallow now lets the run-failure copy reach
the chat instead of disappearing.

Resolves an internal inconsistency: route-reply.ts already routes
NO_REPLY-style payloads through resolveSilentReplyPolicy(), but the
failure-fallback path in agent-runner-execution.ts hardcoded silence on
chat type alone, ignoring the operator-visible knob.

Refs #82060.
2026-05-15 13:18:16 +01:00
Peter Steinberger
2a02d83e2e refactor: record dropped channel history in turn kernel 2026-05-15 13:06:25 +01:00
Peter Steinberger
8859e89e07 feat: attach recent inbound history images to agent turns (#82068)
* feat: attach recent inbound history images

* fix: bound recent history media downloads

* fix: preserve sticker history media

* fix: enforce history media cap for stickers

* refactor: name agent turn attachments generically

* refactor: share pending history media recording

* fix: gate historical media attachment visibility

* fix: avoid media runtime on text-only turns

* fix: preserve fallback history media selection

* fix: avoid sparse media history index collisions

* fix: skip history images for current non-image media

* test: import history media type directly

* test: satisfy agent media runtime mock lint

* fix: respect mocked Slack media fetches

* fix: settle history media recording races
2026-05-15 12:41:52 +01:00
Peter Steinberger
2d8339529b fix: preserve reasoning_content replay for MiMo proxies 2026-05-15 12:35:44 +01:00
Jim Dawdy
aeb06bf4ef fix(agents): apply MiMo reasoning_content fallback wrapper for unowned proxy providers 2026-05-15 12:35:44 +01:00
Jim Dawdy
795ad845d6 fix(xiaomi): address review findings — remove speculative models, add xiaomi-native to native+nonstandard lists, test host resolution 2026-05-15 12:35:44 +01:00
Jim Dawdy
d00cd314d1 fix(transport): drop compat override for requiresReasoningContentOnAssistantMessages (detection-only field) 2026-05-15 12:35:44 +01:00
Jim Dawdy
7958eb0fc4 test(xiaomi): add MiMo thinking profile, stream wrapper, and reasoning_content injection tests 2026-05-15 12:35:44 +01:00
Jim Dawdy
f97645428e feat(xiaomi): register wrapStreamFn, resolveThinkingProfile, isModernModelRef, and replay hooks 2026-05-15 12:35:44 +01:00
Jim Dawdy
e3ad37c24f feat(xiaomi): add MiMo thinking profile and stream wrapper 2026-05-15 12:35:44 +01:00
Jim Dawdy
0dc34ca171 feat(xiaomi): add providerEndpoints for xiaomi-native and mimo-v2.5 model entries 2026-05-15 12:35:44 +01:00
Jim Dawdy
d4c83edba8 fix(transport): propagate requiresReasoningContentOnAssistantMessages to convertMessages 2026-05-15 12:35:44 +01:00
Jim Dawdy
e6dc6c52fe fix(compat): detect xiaomi-native endpoints, set deepseek thinkingFormat and requiresReasoningContentOnAssistantMessages 2026-05-15 12:35:44 +01:00
Jim Dawdy
215e43aa94 fix(transport): add xiaomi-native to ProviderEndpointClass and manifest endpoint classes 2026-05-15 12:35:44 +01:00
Peter Steinberger
e360e105a3 fix: require web search query schema 2026-05-15 12:32:52 +01:00
Peter Steinberger
f06e9f6358 fix(release): keep TypeScript compiler external 2026-05-15 12:32:33 +01:00
Peter Steinberger
b3d9bef38d [codex] Fix Codex OAuth refresh fallback (#82117)
* fix: fall back to Codex CLI OAuth after refresh failure

* fix: support Codex CLI fallback for named profiles
2026-05-15 12:32:00 +01:00
Peter Steinberger
b6809b5e31 fix(gateway): keep config schema admin scoped 2026-05-15 12:25:31 +01:00
Peter Steinberger
8ff722fe7d docs: update changelog for gateway methods (#82063) 2026-05-15 12:25:31 +01:00
Peter Steinberger
3bedce151e fix(gateway): keep exec approvals policy admin scoped 2026-05-15 12:25:31 +01:00
Peter Steinberger
373f709130 fix(gateway): preserve core method collision guards 2026-05-15 12:25:31 +01:00
Peter Steinberger
a383baac03 test(logging): fix stalled recovery threshold test 2026-05-15 12:25:31 +01:00
Peter Steinberger
93b9223bee fix(plugins): tolerate legacy inspect reports 2026-05-15 12:25:31 +01:00
Peter Steinberger
4aa37c3261 fix(gateway): allow partial method registries 2026-05-15 12:25:31 +01:00
Peter Steinberger
db3c4ba8d3 refactor(gateway): collapse method metadata shims 2026-05-15 12:25:31 +01:00
Peter Steinberger
7c639d4b46 fix(gateway): accept legacy plugin registries 2026-05-15 12:25:31 +01:00
Peter Steinberger
fb7dc43043 fix(gateway): preserve lazy method boundaries 2026-05-15 12:25:31 +01:00
Peter Steinberger
386fbd6594 fix(gateway): preserve advertised method ordering 2026-05-15 12:25:31 +01:00
Peter Steinberger
e4a1032072 style(gateway): avoid method list spread allocations 2026-05-15 12:25:31 +01:00
Peter Steinberger
622728757f refactor(gateway): add method descriptor registry 2026-05-15 12:25:31 +01:00
Ayaan Zaidi
b2d04646c1 ci(mantis): run telegram proof agent faster 2026-05-15 16:54:58 +05:30
Ayaan Zaidi
f04d20f8f9 ci(mantis): allow non-visual telegram proof skips 2026-05-15 16:54:58 +05:30
Peter Steinberger
c0fe7ab34a fix: keep queued system event authority structured
Keep queued system-event owner downgrades as structured runtime metadata while rendering the model-visible prompt as plain `System:` lines.

This preserves least-privilege wakeups for webhook/node/exec/cron/reaction/hook producers, keeps legacy `trusted: false` compatibility for installed plugins and older hosts, and updates representative gateway, agent, cron, plugin, and OpenGrep coverage.
2026-05-15 12:24:27 +01:00
Val Alexander
2ac011b8ae fix(ui): repair chat composer usability
Fix the WebChat composer regression reported in #45656 by focusing the textarea from non-control composer chrome clicks and restoring larger labeled desktop composer controls while preserving compact mobile taps.

Verification:
- pnpm test ui/src/ui/views/chat.test.ts ui/src/ui/chat/run-controls.test.ts ui/src/styles/chat/layout.test.ts
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/ui/views/chat.ts ui/src/ui/views/chat.test.ts ui/src/ui/chat/run-controls.ts ui/src/ui/chat/run-controls.test.ts ui/src/styles/chat/layout.css ui/src/styles/chat/layout.test.ts
- git diff --check origin/main...HEAD
- pnpm changed:lanes --json
- pnpm lint:core
- pnpm ui:build
- gh pr checks 82120 --repo openclaw/openclaw --watch=false
- ClawSweeper review completed successfully: https://github.com/openclaw/clawsweeper/actions/runs/25914298634

Closes #45656
2026-05-15 06:07:12 -05:00
civil
c8d53fdf1b docs: credit STT WAV transcode contributor (#82110)
Credit contributor PR #82110 in the existing Audio/STT changelog entry after the ffmpeg muxer fix landed on main.

Verification:
- /Users/steipete/Projects/agent-scripts/skills/codex-review/scripts/codex-review --mode branch
- node scripts/run-vitest.mjs src/media-understanding/apply.test.ts src/media-understanding/runner.cli-audio.test.ts
- gh pr checks 82110 --repo openclaw/openclaw --watch --interval 10
2026-05-15 12:04:19 +01:00
Peter Steinberger
b2dfa98877 docs: credit OpenRouter reasoning replay fix 2026-05-15 11:59:07 +01:00
Peter Steinberger
3537d8a613 fix: preserve valid completions reasoning replay 2026-05-15 11:59:07 +01:00
sliverp
8bfb943945 fix: strip response-only reasoning fields from OpenAI Completions requests
Prevents providers like OpenRouter from returning HTTP 500 errors when replayed assistant messages include fields such as `reasoning_details`.
2026-05-15 11:59:07 +01:00
Peter Steinberger
a1a6cd6508 refactor: centralize inbound history shaping
Centralize inbound history shaping through shared reply-history helpers and preserve existing channel behavior.
2026-05-15 11:56:38 +01:00
Peter Steinberger
f686bb519f fix: force ffmpeg muxers for staged audio outputs
* fix: force ffmpeg muxers for staged audio outputs

* docs: clarify staged audio changelog
2026-05-15 11:56:12 +01:00
Ayaan Zaidi
f1b92c8885 fix(mantis): publish evidence to r2 (#81845)
* fix(mantis): publish evidence to r2

* ci(mantis): pass r2 artifact credentials

* ci(mantis): pin artifact bucket config

* fix(mantis): link raw evidence index
2026-05-15 16:23:53 +05:30
solodmd
239def7838 perf(skills): cache hydrated resolved skills (#81451)
Merged via squash.

Prepared head SHA: e202d16e50
Co-authored-by: solodmd <51304754+solodmd@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-15 13:48:22 +03:00
Peter Steinberger
cd91bd9a1e docs: document admin HTTP RPC plugin 2026-05-15 11:44:58 +01:00
Peter Steinberger
764cfd5552 feat: add bundled admin HTTP RPC plugin 2026-05-15 11:44:58 +01:00
Peter Steinberger
dfeaf6f7cf refactor: add gateway method dispatch contract 2026-05-15 11:44:58 +01:00
Peter Steinberger
cd9b2c0af4 fix: restore voice media uploads 2026-05-15 11:35:34 +01:00
Peter Steinberger
0e5f4ea18c perf: reuse manifest metadata for read-only model catalogs 2026-05-15 11:24:06 +01:00
Frank Yang
b04e42812e fix(memory): stop watcher write-polling fd pressure (#81802)
Merged via squash.

Prepared head SHA: 623874619b
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-05-15 18:12:29 +08:00
Peter Steinberger
21b6dcbe37 fix: restore Discord voice Ogg transcoding 2026-05-15 11:01:15 +01:00
Kaspre
44840007d4 fix(agents): scope custom provider baseUrl SSRF trust by origin (#80751)
* fix(agents): scope provider SSRF trust by origin

* fix(provider): preserve explicit private-network deny

* docs(provider): document exact-origin SSRF trust

* test(provider): cover exact-origin SSRF edges

* docs(provider): align local model private-origin guidance

* refactor(ssrf): keep policy merging in infra

* test(ssrf): cover exact-origin trust through guard

* test(ssrf): block sibling private-origin redirects

* fix(provider): keep loopback trust origin-scoped

* fix(provider): block metadata origin trust

* fix(ssrf): keep metadata rebinding blocked

* fix(ssrf): block cloud metadata origins

* fix(ssrf): block ipv6 metadata origins

* fix(ssrf): block embedded metadata origins

* test(ssrf): cover embedded link-local metadata

* test(provider): cover custom anthropic proxy classification

* test(provider): widen transport policy mock

* test(plugin-sdk): assert metadata-IP allowedOrigins entries are rejected

Plugin authors can construct an SsrFPolicy that lists any well-formed
http(s) origin in allowedOrigins. The abuse-resistance lives one layer
deeper, in resolvePinnedHostnameWithPolicy's metadata/link-local block.
Add an SDK-level smoke test asserting that contract directly:

- AWS/Alibaba IMDS IPv4 literals, GCP metadata canonical hostname,
  IPv6 ULA metadata literal, and non-metadata link-local IPv4 entries
  build a policy via ssrfPolicyFromHttpBaseUrlAllowedOrigin and are
  then rejected at resolvePinnedHostnameWithPolicy.
- DNS rebinding from a trusted private DNS origin to a metadata IP is
  rejected even when the request hostname is origin-trusted.

This would fail if the SDK helper or resolveSsrFPolicyForUrl ever
short-circuited past the metadata block.

* chore(docs): regenerate baselines after upstream rebase

upstream/main moved between rebases; the merged source state for the
PR's `src/config/schema.help.ts` change and the upstream plugin-sdk
surface changes both produce different hashes than the committed
baselines, so `config:docs:check` and `plugin-sdk:api:check` would fail.

Regenerated via `pnpm config:docs:gen` + `pnpm plugin-sdk:api:gen` on
Crabbox; both baselines verified with their respective `--check`
generators.

* test(plugin-sdk): assert SSRF blocked error class

* fix(lint): satisfy exact-origin PR lint rules

* docs: clarify custom provider origin trust

* chore(docs): refresh plugin sdk api baseline

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-15 11:00:29 +01:00
Peter Steinberger
778ad09ff2 test(logging): derive diagnostic abort threshold 2026-05-15 10:38:43 +01:00
Peter Steinberger
582f834269 test(sdk): refresh command facts API baseline 2026-05-15 10:38:43 +01:00
Peter Steinberger
fc5349688f test(agents): update command turn prompt snapshots 2026-05-15 10:38:43 +01:00
Peter Steinberger
3b1497789c refactor(channels): derive command turns from turn facts 2026-05-15 10:38:43 +01:00
Pavan Kumar Gondhi
b9fbc57bbd Bind shell script operands after combined options [AI] (#81882)
* fix: bind shell script operands after combined options

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-15 14:55:37 +05:30
Pavan Kumar Gondhi
238b0fc76f fix(canvas): validate snapshot response formats [AI] (#81881)
* fix: validate canvas snapshot formats

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-15 14:51:38 +05:30
Peter Steinberger
e30be460e1 fix: shorten stalled Codex recovery window 2026-05-15 10:19:37 +01:00
Pavan Kumar Gondhi
eb1e6099d2 Constrain provider catalog entry paths [AI] (#81884)
* fix: constrain provider catalog entries to plugin root

* addressing review-skill

* docs: add changelog entry for PR merge
2026-05-15 14:48:24 +05:30
Pavan Kumar Gondhi
d656087b31 Require canonical node platform IDs [AI] (#81880)
* fix: require canonical node platform ids

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* fix: require consistent node platform metadata

* addressing review-skill

* addressing codex review

* fix: complete root-cause handling

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-15 14:46:46 +05:30
Peter Steinberger
df70ed2b9c fix: force message through empty allowlists 2026-05-15 10:16:27 +01:00
Peter Steinberger
63ad5b4f97 fix: send structured message attachments 2026-05-15 10:16:27 +01:00
Peter Steinberger
3fd4b02eb5 fix: track message attachment aliases 2026-05-15 10:16:27 +01:00
Peter Steinberger
24e88bcdd1 fix: narrow forced message tool inclusion 2026-05-15 10:16:27 +01:00
Peter Steinberger
8650f4ba19 fix: force message tool for source delivery mode 2026-05-15 10:16:27 +01:00
Peter Steinberger
3b2fb9e63d fix: use message mediaUrl attachment hints 2026-05-15 10:16:27 +01:00
Peter Steinberger
9bad29261d fix: preserve forced message tool allowlists 2026-05-15 10:16:27 +01:00
Peter Steinberger
55322d7301 fix: deliver generated media as structured attachments 2026-05-15 10:16:27 +01:00
Peter Steinberger
9d51d2a8b8 docs: credit protocol validator lazy compile (#82064) 2026-05-15 10:16:19 +01:00
samzong
5121f30d2d fix(gateway): lazy compile protocol validators
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-15 10:16:19 +01:00
Val Alexander
28f59a9124 fix(ui): align chat header controls
Summary:
- Align WebChat desktop header controls to a compact 44px header and 36px control rhythm.
- Replace the auto-scroll text dropdown with an icon toggle that keeps tooltip, title, aria-label, and pressed state.
- Lay out mobile chat action icons as a five-column full-width grid.

Verification:
- git diff --check origin/main...HEAD
- pnpm changed:lanes --json
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/ui/app-render.helpers.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/styles/layout.css ui/src/styles/layout.mobile.css ui/src/styles/chat/layout.css ui/src/styles/chat/layout.test.ts ui/src/styles/layout.mobile.test.ts
- pnpm lint:core
- pnpm test ui/src/styles/chat/layout.test.ts ui/src/styles/layout.mobile.test.ts ui/src/ui/app-render.helpers.browser.test.ts
- pnpm ui:build
- Browser proof at desktop 1200x760 and mobile 390x844
- Exact-head GitHub CI green for a25444c5fa
2026-05-15 04:03:34 -05:00
Peter Steinberger
ec9d56601a test: add Gemini subagent stress e2e 2026-05-15 10:01:29 +01:00
Peter Steinberger
b180b8ae48 fix: strip workflow function responses from replies 2026-05-15 09:57:44 +01:00
clawsweeper[bot]
a099acc557 fix: update Azure OpenAI API version default to preview (#82072)
Summary:
- The branch changes the Azure OpenAI Responses transport default API version from `2024-12-01-preview` to `preview`, updates the focused unit assertion, and adds a changelog entry.
- Reproducibility: yes. The source PR provides live Azure curl/OpenClaw commands showing dated defaults fail w ...  `api-version=preview` succeeds, and current main still resolves an unset env var to the old dated default.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: update DEFAULT_AZURE_OPENAI_API_VERSION to 2025-04-01-preview (i…
- PR branch already contained follow-up commit before automerge: fix: use preview literal for AZURE_OPENAI_API_VERSION
- PR branch already contained follow-up commit before automerge: fix: repair Azure API version PR diff and tests
- PR branch already contained follow-up commit before automerge: fix: keep Azure image API version default
- PR branch already contained follow-up commit before automerge: fix: update Azure OpenAI API version default to preview

Validation:
- ClawSweeper review passed for head d7062f162f.
- Required merge gates passed before the squash merge.

Prepared head SHA: d7062f162f
Review: https://github.com/openclaw/openclaw/pull/82072#issuecomment-4458291270

Co-authored-by: Leo Ge <116452300+leoge007@users.noreply.github.com>
Co-authored-by: leoge007 <leoge@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-15 08:42:36 +00:00
Peter Steinberger
06e79b2762 fix: harden response format forwarding 2026-05-15 09:40:06 +01:00
lellansin
8503418274 feat(gateway): forward response format params 2026-05-15 09:40:06 +01:00
Peter Steinberger
be166b9ae4 fix(agent): retry empty anthropic-compatible replies 2026-05-15 08:52:10 +01:00
Val Alexander
930852af29 feat(agents): support per-agent bootstrap profiles
Summary:\n- Add optional per-agent bootstrap profile overrides for contextInjection, bootstrapMaxChars, and bootstrapTotalMaxChars.\n- Resolve per-agent bootstrap profile settings before agents.defaults and thread the resolved session agent through embedded, compact, CLI, and /context diagnostic paths.\n- Update schema/help/docs/changelog plus focused runtime, schema, and /context regression coverage.\n\nVerification:\n- Local focused auto-reply tests and formatter checks passed.\n- Local pnpm check:changed passed before landing follow-ups.\n- Local Node 24 pnpm check:test-types passed after merging latest main into the PR branch.\n- GitHub PR state CLEAN at 0ff12062840f42daf2666c5fabb127c3f7631669.\n- ClawSweeper re-review completed successfully with no actionable repair finding.\n\nFixes #69966.
2026-05-15 02:42:21 -05:00
Peter Steinberger
64d4f99d26 refactor(auto-reply): centralize command turn context
* refactor(auto-reply): centralize command turn context

* fix(channels): narrow command turn context literals

* fix(auto-reply): preserve command auth on refinalize

* fix(auto-reply): keep command turn context sdk-compatible

* fix(auto-reply): route structured command turns before reply setup

* test(cli): type stale launchd job mock
2026-05-15 08:41:09 +01:00
Peter Steinberger
f4d90eb36a test: guard Telegram grammY type imports 2026-05-15 08:38:44 +01:00
Val Alexander
5f89cabeb5 fix(macos): harden screen.snapshot validation and payload bounds
Fixes #68181.

Rejects malformed macOS screen.snapshot params before capture, sanitizes capture failures, and bounds inline base64 snapshot responses against the projected node.invoke.result frame size.

Supersedes #68186.
2026-05-15 02:27:33 -05:00
Peter Steinberger
66ba611f5b docs: update changelog for discord config fixes 2026-05-15 08:23:31 +01:00
Gio Della-Libera
6623444f8d fix(discord): report unresolved token refs at startup (#82009)
Treat configured-but-unresolved Discord token refs as configured so gateway startup reaches the explicit SecretRef resolution error instead of silently classifying the account as unconfigured. Also cover runtime snapshot resolution for active Discord token refs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 08:22:47 +01:00
Peter Steinberger
7acbb2260c fix(mac): surface stale updater launchd jobs 2026-05-15 08:20:55 +01:00
Gio Della-Libera
0dc8552cb3 fix(config): preserve numeric patch object keys (#81999)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 08:20:08 +01:00
Peter Steinberger
bea597b2d6 fix(discord): clear failed partial preview finals 2026-05-15 08:12:51 +01:00
Peter Steinberger
1458829822 fix: serialize stale auth shadow repair 2026-05-15 08:10:44 +01:00
Val Alexander
396d5b26da docs: record sidebar markdown copy fix 2026-05-15 02:07:42 -05:00
tikitoki
e1d69bc433 fix(ui): enable sidebar markdown code copying
Summary:
- Add the existing delegated markdown code-block copy handler to the Control UI chat sidebar container.
- Fix sidebar-rendered markdown code copy buttons that previously emitted no clipboard write because `.chat-sidebar` sits outside `.chat-thread`.

Verification:
- Unpatched current `origin/main` (`b24a6d2cbd636b0b39b732c962d58e574c748abe`) + temporary regression assertion: `pnpm test ui/src/ui/views/chat.test.ts -t "chat sidebar markdown copy"` failed with 0 `navigator.clipboard.writeText` calls.
- PR patch applied onto current `origin/main` + same temporary regression assertion: `pnpm test ui/src/ui/views/chat.test.ts -t "chat sidebar markdown copy"` passed, 1 test passed and 32 skipped.
- Live PR state before merge: `MERGEABLE`, `CLEAN`, head `2e04e981e992b32920476edc648009ddff7976d0`.
- Duplicate sweep found no same-failure duplicate PR/issue.
- Security check clear: UI event binding only; no dependency, workflow, auth, secret, network, or command-execution surface changes.

Known proof gap:
- No full browser walkthrough was run; the focused jsdom proof covers the exact DOM delegation boundary.

Thanks @tikitoki.
2026-05-15 02:05:04 -05:00
Peter Steinberger
b24a6d2cbd fix(control-ui): rotate service worker cache per build (#82050) 2026-05-15 07:59:29 +01:00
github-actions[bot]
925861f9b3 chore(ui): refresh fa control ui locale 2026-05-15 06:56:27 +00:00
github-actions[bot]
aff30f9671 chore(ui): refresh nl control ui locale 2026-05-15 06:56:20 +00:00
github-actions[bot]
fa43ed33d8 chore(ui): refresh th control ui locale 2026-05-15 06:56:01 +00:00
github-actions[bot]
7c39d0d60c chore(ui): refresh vi control ui locale 2026-05-15 06:55:52 +00:00
github-actions[bot]
c1eb1ba7cd chore(ui): refresh pl control ui locale 2026-05-15 06:55:49 +00:00
github-actions[bot]
8e515e5fa8 chore(ui): refresh id control ui locale 2026-05-15 06:55:44 +00:00
github-actions[bot]
d6cb0e5adf chore(ui): refresh ar control ui locale 2026-05-15 06:55:22 +00:00
github-actions[bot]
e5a3e3b357 chore(ui): refresh uk control ui locale 2026-05-15 06:55:16 +00:00
github-actions[bot]
fb87c0b470 chore(ui): refresh tr control ui locale 2026-05-15 06:55:10 +00:00
github-actions[bot]
45c3faeb1e chore(ui): refresh it control ui locale 2026-05-15 06:55:01 +00:00
github-actions[bot]
b521dae2ec chore(ui): refresh fr control ui locale 2026-05-15 06:54:45 +00:00
github-actions[bot]
3f0c875e4c chore(ui): refresh ko control ui locale 2026-05-15 06:54:34 +00:00
github-actions[bot]
9fdd7c56b9 chore(ui): refresh es control ui locale 2026-05-15 06:54:31 +00:00
github-actions[bot]
3a0029a297 chore(ui): refresh ja-JP control ui locale 2026-05-15 06:54:29 +00:00
github-actions[bot]
a1ee350cc9 chore(ui): refresh de control ui locale 2026-05-15 06:54:01 +00:00
github-actions[bot]
3146470ee1 chore(ui): refresh zh-CN control ui locale 2026-05-15 06:53:56 +00:00
github-actions[bot]
c3dc015cac chore(ui): refresh zh-TW control ui locale 2026-05-15 06:53:52 +00:00
github-actions[bot]
599432b5a7 chore(ui): refresh pt-BR control ui locale 2026-05-15 06:53:47 +00:00
Peter Steinberger
65aa6c8a77 test(ci): update installer sync workflow assertion 2026-05-15 07:52:01 +01:00
zhulijin1991
2e2da1f2b9 fix(ci): unblock scheduled and publish checks 2026-05-15 07:52:01 +01:00
Peter Steinberger
1109abc947 fix: align Telegram grammY type imports (#81975) 2026-05-15 07:50:47 +01:00
Peter Steinberger
91f01b7664 fix: document Telegram isolated polling init (#81975) (thanks @neeravmakwana) 2026-05-15 07:50:47 +01:00
Neerav Makwana
de48410384 test(telegram): name isolated init regression
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 07:50:47 +01:00
Neerav Makwana
0beae266da fix(telegram): initialize isolated polling bot
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 07:50:47 +01:00
Peter Steinberger
916fc3d09a test: keep update mock persisted hash order 2026-05-15 07:32:29 +01:00
Peter Steinberger
7bc73ded71 test: remove duplicate persisted hash mock keys 2026-05-15 07:32:29 +01:00
Peter Steinberger
9f211bb802 test: align update mocks with persisted config hash 2026-05-15 07:32:29 +01:00
Peter Steinberger
b830beb34b fix: surface update restart and plugin repair guidance 2026-05-15 07:32:29 +01:00
Galin Iliev
0cbd2d3b08 fix(ui): wrap long inline code in chat bubbles (#81931)
Wrap long inline code tokens inside chat bubbles so unbroken paths and identifiers stay within the message bubble.\n\nFixes #81932.
2026-05-14 23:31:45 -07:00
Peter Steinberger
4a188e7ca5 chore: update dependencies 2026-05-15 07:28:28 +01:00
Peter Steinberger
2645492fde test: cover sync clobber snapshot collisions 2026-05-15 07:12:57 +01:00
Kaspre
5734193fdf fix(plugins): keep metadata snapshot memo fresh
* fix(plugins): keep metadata memo freshness

* fix(plugins): keep metadata memo freshness

* fix(plugins): resolve metadata memo review gaps

* fix(plugins): scope metadata memo watches to env

* fix(plugins): tighten metadata memo fingerprint return type

`resolvePersistedRegistryFastMemoFingerprint` was annotated `: unknown`
but always returns object literals (`{ disabled: true }` or
`{ index, npmPackageJson }`). Spreading the unknown-typed result on
line 478 (`...fastFingerprint`) was rejected by tsgo with TS2698, which
cascaded across every check that runs the project compile (build,
tsgo:prod, check:test-types, lint, all node test shards).

Tighten the return type to `Record<string, unknown>` to match the
function's actual return shapes and unblock the spread.

* test(gateway): tolerate ENOENT in sessions.list spy predicate

The `sessions.list configuredAgentsOnly hides disk-discovered
unregistered agent stores` test spies on `fsSync.readFileSync` and
predicates with `fsSync.realpathSync.native(file) === realDiskOnlyStorePath`
for every captured read. The native realpath call throws on missing
files, so any new readFileSync of a path that may not exist (e.g. the
persisted plugin install records probe added in this PR) crashes the
predicate before the assertion runs.

Wrap the predicate in ENOENT tolerance so the test stays robust against
any future readFileSync of files that may not exist on disk.

* fix(plugins): refresh memo from cached registry

* fix(plugins): use high resolution memo fingerprints

* test(plugins): stabilize memo freshness regression

* test(cli): satisfy config mutation mock hash contract

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-15 07:12:31 +01:00
Val Alexander
7289e14dae fix(ui): keep PWA chat controls above iOS home indicator
Fix the active Control UI WebChat composer path so mobile standalone PWA layouts keep the toolbar above the iOS home indicator even when safe-area insets under-report.

- apply the mobile safe-area composer margin in the later-loading chat layout stylesheet
- add a standalone PWA defensive floor for broken zero safe-area reports
- cover the CSS contract with focused regression coverage

Verification:
- corepack pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/styles/chat/layout.css ui/src/styles/chat/layout.test.ts
- git diff --check
- corepack pnpm test ui/src/styles/chat/layout.test.ts ui/src/ui/chat/chat-responsive.browser.test.ts -- --reporter=verbose
- corepack pnpm check:changed
- GitHub CI green on exact head b2b6007f43

Fixes #77408.

Thanks @BunsDev.
2026-05-15 01:04:28 -05:00
samzong
87724c964f refactor(config): split channel schema imports (#82007)
Split generic channel config schema out of the provider schema barrel so OpenClawSchema no longer imports provider-specific channel schemas for generic channel defaults validation.

Co-authored-by: samzong <samzong.lu@gmail.com>
2026-05-15 07:02:02 +01:00
Peter Steinberger
59d2e7686c refactor: centralize live provider drift policy (#82033) 2026-05-15 06:57:00 +01:00
Peter Steinberger
7e9a863423 test(update): model finalize channel rewrite snapshots 2026-05-15 06:55:04 +01:00
Peter Steinberger
c5ca8a17ce docs(skills): add release ci workflow skill 2026-05-15 06:55:04 +01:00
Peter Steinberger
a543d21352 fix(test): give anthropic cache probes more output budget 2026-05-15 06:55:04 +01:00
Peter Steinberger
41992581b8 fix(test): tolerate cache probes with usage-only output 2026-05-15 06:55:04 +01:00
Peter Steinberger
5f8200e6e8 fix(test): stabilize anthropic cache probe output 2026-05-15 06:55:03 +01:00
Peter Steinberger
cc129a0dd3 fix(update): refresh config after package doctor 2026-05-15 06:55:03 +01:00
Peter Steinberger
f1deb53d43 fix(release): unblock full validation 2026-05-15 06:55:03 +01:00
Peter Steinberger
93c799eb16 test(release): tolerate generated Slack scan artifact 2026-05-15 06:55:03 +01:00
Peter Steinberger
942334f97d test(release): update plugin prerelease assertions 2026-05-15 06:55:03 +01:00
Peter Steinberger
cdc9a380d1 docs: update changelog for canvas lazy-load (#82001) 2026-05-15 06:54:28 +01:00
samzong
a4cd482488 refactor(canvas): lazy-load startup modules
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-15 06:54:28 +01:00
Peter Steinberger
510628ca6f fix: refine clobber snapshot rotation (#82012) 2026-05-15 06:48:03 +01:00
Kaspre
57e699a09a fix(config): rotate clobber snapshots at cap 2026-05-15 06:48:03 +01:00
Peter Steinberger
de18f77737 refactor(cron): centralize agent phase watchdog state 2026-05-15 06:42:45 +01:00
Peter Steinberger
1713930bbe fix(opencode-go): strip Kimi reasoning payloads 2026-05-15 06:42:19 +01:00
alexph-dev
d471540ceb fix: harden telegram html fallback text (#81764)
Harden Telegram HTML parse fallback so plain-text retries render readable labels and links instead of raw anchors.

Co-authored-by: Sam (OpenClaw) <sam.kpg5stars@gmail.com>
2026-05-15 06:40:17 +01:00
Peter Steinberger
c462e68df7 fix: repair stale auth shadows and status plugin failures 2026-05-15 06:39:38 +01:00
Peter Steinberger
4d28450312 refactor: share channel status read model
Share channel status read-model helpers across channels list and status-all.
2026-05-15 06:39:18 +01:00
Peter Steinberger
aedcfa76a4 fix(cron): classify dispatch milestones for watchdog (#81871) 2026-05-15 06:27:04 +01:00
Sam (OpenClaw)
11984c7997 fix(cron): treat attempt dispatch as execution start 2026-05-15 06:27:04 +01:00
Peter Steinberger
42f6d90917 fix(telegram): share API request timeout wrapper 2026-05-15 06:17:06 +01:00
Baris Albayrak
1139777765 fix(whatsapp): mark text slash commands as command turns
* fix(whatsapp): mark text slash commands as command turns

* fix(ci): clear PR 81972 gates

* fix(msteams): wrap graph JSON parse errors

* docs: add WhatsApp slash command changelog

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-15 06:13:38 +01:00
B.K.
22a0ca9e3b fix: hand off managed update run self-updates
Route managed LaunchAgent package self-updates through the post-exit CLI handoff path and persist handoff helper failures through the update restart sentinel so agent-invoked updates cannot stay pending indefinitely.

Add handoff ownership guards for stale helpers, atomic helper sentinel writes, and regression coverage for unrelated and newer pending sentinels.

Fixes #81894.

Co-authored-by: B.K. <bandark@mac.com>
2026-05-15 06:12:57 +01:00
Peter Steinberger
7db44b979f test: tolerate provider account drift in live CI 2026-05-15 06:07:58 +01:00
Peter Steinberger
b672be59ae fix(channels): prefer runtime status in channel list (#82016) 2026-05-15 05:42:10 +01:00
Peter Steinberger
7e7ce53e5a docs(changelog): fold 2026.5.9 into 2026.5.12 2026-05-15 05:40:25 +01:00
Peter Steinberger
4505a88d88 fix(agents): preserve fallback trace truth 2026-05-15 05:13:35 +01:00
Peter Steinberger
dae90067e9 docs: note git installer ref fix (#81875) (thanks @keshavbotagent) 2026-05-15 05:11:35 +01:00
Peter Steinberger
a9aafc84b1 fix: fetch git installer branch refs without tags 2026-05-15 05:11:35 +01:00
Keshav's Bot
b26dcb3390 fix: scope git installer lockfile refresh 2026-05-15 05:11:35 +01:00
Keshav's Bot
36411cde8f fix: let git installer refresh lockfile 2026-05-15 05:11:35 +01:00
Keshav's Bot
e72c849359 fix: make installer pnpm install noninteractive 2026-05-15 05:11:35 +01:00
Keshav's Bot
0d22fbf312 fix: avoid tag fetch for main installer ref 2026-05-15 05:11:35 +01:00
Peter Steinberger
f12e9c41fa fix(codex): inject app-server client factories
Co-authored-by: Benjamin Badejo <ben@benbadejo.com>
2026-05-15 05:03:28 +01:00
Peter Steinberger
0db0979365 fix: harden code mode runtime 2026-05-15 04:16:07 +01:00
Peter Steinberger
204941c03e fix: register code mode worker dependency 2026-05-15 04:16:07 +01:00
Peter Steinberger
bf400c08c3 docs: mark code tool surfaces experimental 2026-05-15 04:16:07 +01:00
Peter Steinberger
8985cd596a fix: refresh code mode after rebase 2026-05-15 04:16:07 +01:00
Peter Steinberger
0844e771a8 feat: add generic code mode runtime 2026-05-15 04:16:07 +01:00
Val Alexander
346a773b18 fix(ui): remove duplicate Usage heading
Remove the local Usage view header now that the shared Control UI shell owns the page title, clean up the obsolete Usage subtitle locale key through the i18n sync pipeline, and add focused render coverage.

Fixes #45709.
Supersedes #67290 and #74238.

Verification:
- node scripts/run-vitest.mjs ui/src/ui/views/usage.test.ts ui/src/ui/views/usage-render-overview.test.ts ui/src/styles/usage.test.ts ui/src/i18n/test/translate.test.ts
- pnpm lint --threads=8
- pnpm ui:i18n:check
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/styles/usage.css ui/src/ui/views/usage.ts ui/src/ui/views/usage.test.ts ui/src/i18n/locales/*.ts
- git diff --check
- node scripts/run-vitest.mjs src/logging/logger-redaction-behavior.test.ts
- GitHub CI run 25895690773 passed after failed-job rerun
- Real behavior proof run 25895689499 passed

Head: f60e501a07
2026-05-14 20:50:44 -05:00
Maple778
2156b204ea fix(ui): align overview session labels
Summary:
- move session display-name resolution into a shared UI helper
- reuse that resolver for Overview recent sessions and chat session controls
- keep Telegram/channel fallback labels from leaking raw compound session keys into Overview

Verification:
- node scripts/run-vitest.mjs ui/src/ui/views/overview.render.test.ts ui/src/ui/app-render.helpers.node.test.ts
- pnpm exec oxfmt --check --threads=1 ui/src/ui/app-render.helpers.ts ui/src/ui/chat/session-controls.ts ui/src/ui/session-display.ts ui/src/ui/views/overview-cards.ts ui/src/ui/views/overview.render.test.ts CHANGELOG.md
- pnpm exec oxlint --tsconfig config/tsconfig/oxlint.core.json ui/src/ui/app-render.helpers.ts ui/src/ui/chat/session-controls.ts ui/src/ui/session-display.ts ui/src/ui/views/overview-cards.ts ui/src/ui/views/overview.render.test.ts src/commands/doctor/shared/legacy-config-core-normalizers.ts
- git diff --check origin/main...HEAD
- GitHub CI on 36fd998f66: check, check-lint, checks-node-core-ui, build-artifacts, Real behavior proof all passed
2026-05-14 20:40:41 -05:00
Vincent Koc
695a4f5039 fix(web-search): wrap more provider json 2026-05-15 09:12:31 +08:00
Vincent Koc
d0034e2f99 fix(msteams): guard graph fetches 2026-05-15 09:09:23 +08:00
Vincent Koc
4ce979934f fix(media): wrap malformed provider json 2026-05-15 09:08:34 +08:00
Vincent Koc
f0803c576f fix(memory): wrap malformed remote json 2026-05-15 09:06:24 +08:00
Vincent Koc
31bfe7f084 fix(web-search): wrap malformed provider json 2026-05-15 09:04:20 +08:00
Vincent Koc
8659eabba7 fix(firecrawl): wrap malformed api json 2026-05-15 09:01:39 +08:00
Vincent Koc
9c88241a85 fix(test): satisfy provider web search test types 2026-05-15 08:57:51 +08:00
hclsys
30cfcf21b7 fix(skills): replace generated plugin skill directories 2026-05-15 08:57:03 +08:00
Vincent Koc
60a6945a6e fix(googlechat): wrap malformed api json 2026-05-15 08:56:16 +08:00
Vincent Koc
c98459d9da fix(msteams): wrap malformed api json 2026-05-15 08:52:15 +08:00
Vincent Koc
376a792100 fix(nextcloud-talk): wrap malformed api json 2026-05-15 08:49:50 +08:00
Vincent Koc
ffae8f32d8 fix(xai): wrap malformed tool json 2026-05-15 08:47:41 +08:00
Kevin Lin
acbe461c16 fix(codex): remove spurious migration warnings
Remove spurious Codex migration warnings for expected planned/manual/archive states while preserving real provider warnings.
2026-05-14 17:47:21 -07:00
EvanYao
70ebf4898b fix(config): treat enabled channels as configured 2026-05-15 08:45:22 +08:00
Vincent Koc
380ba7071f fix(brave): wrap malformed search json 2026-05-15 08:44:34 +08:00
Vincent Koc
d16f79f49d fix(providers): add safe json response helper 2026-05-15 08:41:18 +08:00
Vincent Koc
a709927698 fix(codex): hide empty rate-limit buckets
Hide empty Codex rate-limit buckets from account/status output while preserving server-reported usage-limit blocks.
2026-05-15 08:40:29 +08:00
Vincent Koc
9f99464119 fix(deepinfra): wrap malformed video json 2026-05-15 08:36:05 +08:00
Vincent Koc
c589680e95 fix(doctor): honor alternate memory slot owners 2026-05-15 08:33:29 +08:00
Vincent Koc
1d46a2f0b5 fix(comfy): wrap malformed workflow json 2026-05-15 08:32:31 +08:00
Vincent Koc
62375ae860 fix(lint): quiet ollama num_ctx normalizer 2026-05-15 08:29:02 +08:00
Vincent Koc
6e191f0e1e fix(lmstudio): wrap malformed model json 2026-05-15 08:27:55 +08:00
Vincent Koc
d77f428441 fix(tlon): wrap malformed scry json 2026-05-15 08:22:41 +08:00
Vincent Koc
a118e114fe fix(usage): wrap malformed usage json 2026-05-15 08:18:11 +08:00
Shakker
0179efc022 docs: add update finalizer changelog 2026-05-15 01:10:20 +01:00
Shakker
4199034de2 fix: refresh finalize state after doctor 2026-05-15 01:09:06 +01:00
Shakker
92f8c1edac feat: add update finalization command 2026-05-15 01:09:06 +01:00
Vincent Koc
eb07aba973 fix(clawhub): wrap malformed marketplace json 2026-05-15 08:02:13 +08:00
Vincent Koc
77e5a492f2 fix(signal): wrap malformed release metadata 2026-05-15 07:57:07 +08:00
Vincent Koc
c59e900903 fix(tlon): wrap malformed sse json 2026-05-15 07:54:04 +08:00
Vincent Koc
63d72e2191 fix(voice-call): wrap malformed media stream json 2026-05-15 07:50:08 +08:00
Truffle
d7e946eb34 fix(runner): gate surface_error throw on failoverFailure (#70900)
Merged via squash.

Prepared head SHA: b62213339b
Co-authored-by: truffle-dev <260125384+truffle-dev@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-15 02:47:30 +03:00
Val Alexander
eb4e20ca1d fix(plugins): expose effective context budget in hooks
Add optional context budget/source/reference metadata to plugin hook contexts plus llm_output and sanitized model_call_* hook events.

Thread the existing resolved context-window info through Pi embedded runs, CLI harness runs, and Codex app-server hook emission so plugins can observe the effective budget after agent/model/config caps.

Document the metadata and cover the CLI, Pi, Codex app-server, and model-call paths with focused tests.

Fixes #64327.
2026-05-14 17:51:53 -05:00
Josh Lehman
4004c9342d fix: use Codex context windows for OpenAI runtime (#81906)
* fix: use Codex context windows for OpenAI runtime

* test: satisfy status model fixture types

* fix: note Codex context window budget fix
2026-05-14 15:38:50 -07:00
Josh Lehman
9cfb4c747a docs: restore 2026.5.12 changelog section (#81943) 2026-05-14 15:36:04 -07:00
Extra Small
70ed7c9873 fix(control-ui): make log stream height responsive
Summary:
- Replace the fixed 500px Control UI Logs stream cap with a viewport-responsive max-height plus a 200px floor.
- Keep the offset documented inline and add the changelog entry for #53916.

Verification:
- git diff --check origin/main...HEAD
- git merge-tree --name-only origin/main HEAD
- node assertion confirmed `.log-stream` has `max-height: calc(100vh - 280px)`, `min-height: 200px`, and no `max-height: 500px`
- Source path check confirmed `renderLogs` renders the affected `.log-stream` container

Maintainer note:
- Real behavior proof requirement intentionally overridden by maintainer proof comment: https://github.com/openclaw/openclaw/pull/53916#issuecomment-4455196712
2026-05-14 17:21:23 -05:00
Josh Lehman
3f80f889fa fix: align Codex cron bootstrap context (#81822)
* fix: align Codex cron bootstrap context

* fix: address Codex cron review comments

* fix: suppress Codex project docs for lightweight context

* fix: note Codex cron lightweight context
2026-05-14 15:10:42 -07:00
Josh Avant
bcbf4fc35f fix(discord): honor threadName when sending to threads (#81933) 2026-05-14 17:07:29 -05:00
joshavant
3f0a39510b docs changelog for ollama num_ctx fix 2026-05-14 16:53:12 -05:00
Josh Avant
bd0555d5fc fix ollama native num_ctx migration (#81928) 2026-05-14 16:50:57 -05:00
javierdici
f6c00456dc Render provider errors in chat history (#65689)
Merged via squash.

Prepared head SHA: a777c7506e
Co-authored-by: javierdici <131621115+javierdici@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-15 00:48:41 +03:00
Leo Ge
99a6b1c5a8 fix(acpx): surface Codex ACP diagnostics
Surface redacted Codex wrapper stderr for generic ACP internal failures, preserve safe Codex model/provider routing in isolated CODEX_HOME, and cover the ACP parent stream dispatch order.

Co-authored-by: leoge007 <leoge@users.noreply.github.com>
2026-05-14 22:42:28 +01:00
Gio Della-Libera
abf59205fc fix(config): return persisted config write responses (#81445)
Merged via squash.

Prepared head SHA: 8f549e0621
Co-authored-by: giodl73-repo <235387111+giodl73-repo@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-15 00:35:15 +03:00
Kevin Lin
079bf99671 docs: consolidate plugin management docs (#81898)
* docs: consolidate plugin management docs

* docs: keep community plugins page discovery-only

* docs: restore clawhub publishing guidance

* docs: keep community publishing checklist
2026-05-14 14:27:50 -07:00
Peter Steinberger
926bf66ee3 fix(skills): sync managed symlink skills as directories 2026-05-14 22:11:01 +01:00
stainlu
cca4f3c63c fix(skills): trust managed skill symlink roots 2026-05-14 22:11:01 +01:00
Peter Steinberger
f8ae0fb1c4 fix: narrow ACP timeout config suppression (#81603) 2026-05-14 22:01:40 +01:00
Peter Steinberger
f9112f0e0a fix: tolerate unsupported ACP timeout hints (#81603) (thanks @qkal) 2026-05-14 22:01:40 +01:00
Qkal
ba61d12c45 docs(plugin-sdk): document Codex helper subpaths 2026-05-14 22:01:40 +01:00
Qkal
82f4297311 fix(acp): tolerate unsupported timeout config hints 2026-05-14 22:01:40 +01:00
Peter Steinberger
1d8d664570 chore(release): prepare 2026.5.14 2026-05-14 21:38:45 +01:00
Peter Steinberger
f6f05c4859 build(clawhub): publish bedrock providers
(cherry picked from commit cbafae60dd)
2026-05-14 21:34:36 +01:00
pashpashpash
1a5548203e Stream Codex preambles in channel progress drafts (#81887)
* codex: stream preambles in progress drafts

* test: update preamble progress PR checks

* test: refresh plugin sdk api baseline
2026-05-15 05:32:42 +09:00
Peter Steinberger
d9ff8cfb01 fix: plan managed npm peer pins with npm
Plan managed npm peer dependency pins from npm's lockfile planner instead of recursively scanning nested node_modules packages, preserving host peer ranges when npm cannot produce a usable root pin.

Also preserves active root-managed OpenClaw host runtimes during npm plugin installs, folding the active-host guard/test from #81632.

Verification:
- codex-review --full-access
- pnpm check:test-types
- pnpm exec oxfmt --check --threads=1 src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts CHANGELOG.md test/scripts/mantis-build-telegram-desktop-proof-evidence.test.ts && git diff --check
- OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts -- --reporter=verbose
- OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/test-projects.mjs src/plugins/install.npm-spec.e2e.test.ts -- --reporter=verbose
- node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts test/scripts/mantis-build-telegram-desktop-proof-evidence.test.ts --reporter=verbose
- GitHub current-head checks: 55 completed, 0 failures; remaining Blacksmith-backed jobs capacity-queued at merge decision time.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-14 21:25:22 +01:00
Peter Steinberger
6971036043 test: align mantis evidence assertions 2026-05-14 20:56:13 +01:00
Peter Steinberger
21f1b46f8a refactor(clawhub): reuse response body timeout helper 2026-05-14 20:56:13 +01:00
stainlu
86b0a7ddda fix(clawhub): cancel stalled archive body reads 2026-05-14 20:56:13 +01:00
Conan Scott
817dca5ae9 fix(webchat): render tts audio command replies 2026-05-14 20:41:21 +01:00
Peter Steinberger
686b93e5c7 fix: keep command cron turns lightweight 2026-05-14 20:15:44 +01:00
Peter Steinberger
e575325af6 fix(memory): prioritize canonical daily notes 2026-05-14 20:11:47 +01:00
simplyclever914
c404711703 fix: enforce Codex forced OAuth refresh (#80738)
Treat forced OAuth refresh as a hard refresh contract: fallback credentials may be reused only when they changed after the attempted refresh began.

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Clever <clever@users.noreply.github.com>
2026-05-14 20:08:14 +01:00
Josh Avant
130c2d5044 Fix Telegram polling lease cleanup on restart (#81890)
* fix(telegram): release stopped polling leases

* docs: add Telegram polling lease changelog
2026-05-14 14:04:34 -05:00
Josh Lehman
f64feab47a fix: prevent codex app-server surrogate stalls 2026-05-14 19:59:23 +01:00
Pavan Kumar Gondhi
386d321634 Bind gateway approval access to requester metadata [AI] (#81380)
* fix: bind approval access to requester metadata

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing codex review

* addressing claude review

* addressing ci

* fix: complete root-cause handling

* addressing review-skill

* addressing codex review

* addressing ci

* docs: add changelog entry for PR merge
2026-05-14 23:21:34 +05:30
Peter Steinberger
c9b6b0be0e fix(mantis): stop git-backed evidence publishing 2026-05-14 18:34:31 +01:00
Peter Steinberger
59be6d6390 build(deps): route node proxy helpers through proxyline 2026-05-14 18:27:23 +01:00
pashpashpash
28550a798c fix(doctor): respect runtime message tool grants 2026-05-14 17:46:28 +01:00
Josh Avant
d0f22ccf97 Fix gateway handling for undici HTTP2 session teardown (#81838)
* fix: handle undici HTTP2 session teardown

* docs: add gateway HTTP2 changelog entry
2026-05-14 11:36:59 -05:00
SymbolStar
0de6f93805 fix(telegram): reuse sticky IPv4 dispatcher for getMe health check (#76852) (#76856)
Fixes #76852.

Co-authored-by: jindongfu <jindongfu@microsoft.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-05-15 00:21:08 +08:00
Ayaan Zaidi
85eb3cda65 fix(telegram): add isolated lane drain changelog (#81849) (thanks @VACInc) 2026-05-14 21:49:23 +05:30
Ayaan Zaidi
52c9860bde refactor(telegram): simplify spooled lane tracking 2026-05-14 21:49:23 +05:30
VACInc
3f132370f4 fix telegram isolated spool lane draining 2026-05-14 21:49:23 +05:30
Mason Huang
83d7ab0d36 fix(changelog): reject bot/app handles as Thanks attribution and require explicit human credit (#81357)
Summary:
- The PR expands forbidden changelog `Thanks` attribution rules for bot/app handles, shares the Node predicate ... ngelog gate, requires explicit human credit for bot/app-authored changelog entries, and adds focused tests.
- Reproducibility: yes. Current main source shows bot/app changelog authors can skip human attribution and bot/app `Thanks` handles are not all rejected; I did not execute tests because this review was read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: simplify bot changelog credit guard
- PR branch already contained follow-up commit before automerge: fix: share changelog credit attribution rule
- PR branch already contained follow-up commit before automerge: fix: tighten changelog attribution scanning
- PR branch already contained follow-up commit before automerge: test: cover legacy changelog credit exclusions
- PR branch already contained follow-up commit before automerge: fix: express changelog credit exclusions as union sets
- PR branch already contained follow-up commit before automerge: fix: avoid substring changelog credit exclusions

Validation:
- ClawSweeper review passed for head 1e6d0f53ec.
- Required merge gates passed before the squash merge.

Prepared head SHA: 1e6d0f53ec
Review: https://github.com/openclaw/openclaw/pull/81357#issuecomment-4439359411

Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-14 15:04:43 +00:00
masonxhuang
1f45b37fe1 feat(secret-scanning): add automated message header to maintainer notifications 2026-05-14 22:28:12 +08:00
Gado
83b8289ee2 feat: WhatsApp status reactions, new emoji categories, self-explanatory defaults (#59077) (#80612)
Merged via squash.

Prepared head SHA: 25e0a7a9fd
Co-authored-by: gado-ships-it <276509604+gado-ships-it@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-05-14 14:37:23 +02:00
Vincent Koc
74dae6088b fix(mattermost): wrap malformed interaction json 2026-05-14 20:10:41 +08:00
Andrii Furmanets
42b95a9eb1 fix(plugins): harden git ref checkout 2026-05-14 19:56:36 +08:00
Vincent Koc
e692f5c1cf fix(synology-chat): wrap malformed webhook json 2026-05-14 19:53:19 +08:00
Vincent Koc
2d6fd54ebd fix(cli): keep plugin json output parseable
Co-authored-by: Eric Milgram, PhD <4348294+ScientificProgrammer@users.noreply.github.com>
2026-05-14 19:44:38 +08:00
Vincent Koc
8813b79990 fix(openai): wrap malformed embedding batch jsonl 2026-05-14 19:43:34 +08:00
Vincent Koc
d9c6036a8f fix(qqbot): wrap malformed token json 2026-05-14 19:40:46 +08:00
Vincent Koc
2d9ef76d5b fix(bedrock): wrap malformed embedding json 2026-05-14 19:38:11 +08:00
Vincent Koc
1bdb151d0d fix(node-host): wrap malformed invoke params json 2026-05-14 19:35:45 +08:00
Vincent Koc
1b77553115 fix(build): keep externalized plugins out of root dist 2026-05-14 19:34:15 +08:00
Vincent Koc
1fc82347a7 fix(google): wrap malformed sse json 2026-05-14 19:28:13 +08:00
Vincent Koc
ae0cb0ac6f fix(google-meet): wrap malformed browser status json 2026-05-14 19:23:11 +08:00
Vincent Koc
b02de2e948 fix(qa-lab): wrap malformed model catalog json 2026-05-14 19:18:57 +08:00
Vincent Koc
8f7a3cbff3 fix(plugins): repair legacy npm declaration stubs 2026-05-14 19:17:05 +08:00
Vincent Koc
8754094292 fix(gateway): wrap malformed pricing catalog json 2026-05-14 19:15:20 +08:00
Vincent Koc
c96181fdbe fix(foundry): wrap malformed az token json 2026-05-14 19:10:08 +08:00
Vincent Koc
278e3eabf2 fix(realtime): wrap malformed transcription frames 2026-05-14 19:05:46 +08:00
Vincent Koc
f3fecb7218 changelog: cover claude-cli reasoning preview bridge + gating 2026-05-14 19:03:59 +08:00
Vincent Koc
79bd0185f5 fix(voice-call): wrap guarded api json parse 2026-05-14 19:00:54 +08:00
Ayaan Zaidi
02f2e08493 fix(auto-reply): gate claude cli reasoning bridge 2026-05-14 16:29:46 +05:30
Cameron Beeley
f3875ac937 fix(auto-reply): bridge cli assistant text-delta into reasoning preview
Add a CLI-runtime-gated bridge in runAgentTurnWithFallback that subscribes
to `stream: "assistant"` agent-events for the current runId and re-emits
them as reasoning content through `params.opts.onReasoningStream`. Mirrors
the assistant-text bridge from #76914 and the tool-event bridge from #80046:
same Promise-chain serialization + drain, same silentExpected gate, same
unsubscribe pattern at success/catch/finally.

The reply lane is untouched -- `onPartialReply` continues to settle the
final assistant text via #76914. The reasoning lane now reflects the
model's live text output during streaming, which is the only "what is the
model producing right now" signal available for claude-opus-4-7 over
claude-cli (Anthropic suppresses readable thinking_delta events on the
wire for opus-4-7; only thinking content_block + signature_delta arrive).

The bridge is gated on isCliProvider so API/native runtimes that already
get reasoning content from real thinking_delta events do NOT double-receive
text_delta as reasoning.

Tests cover:
- Forwards assistant agent-events to onReasoningStream with correct text
- Respects silentExpected (heartbeat / NO_REPLY runs don't emit)
- Does not fire on the API/native runtime path (gate works)
2026-05-14 16:29:46 +05:30
Vincent Koc
99d7b206fd fix(twilio): wrap malformed api json 2026-05-14 18:57:19 +08:00
Vincent Koc
49e288823c fix(cli): wrap malformed trajectory export requests 2026-05-14 18:52:05 +08:00
Vincent Koc
a1de61bf65 fix(channels): list manifest-only plugins 2026-05-14 18:47:52 +08:00
Vincent Koc
7a65b8a3d5 fix(google-meet): wrap malformed node host params 2026-05-14 18:47:18 +08:00
Val Alexander
348ffe6061 fix(gateway): stop stale control ui auth retry loops
Fixes #72139.

Summary:
- Stop Control UI and Gateway clients from retrying AUTH_TOKEN_MISMATCH forever when no trusted cached device-token retry is queued.
- Keep the bounded trusted device-token retry path intact.
- Cap Control UI chat history rendering by raw tool-output size, including tool_result.content, with bounded estimation to avoid pre-render stalls.

Verification:
- node scripts/run-vitest.mjs ui/src/ui/gateway.node.test.ts src/gateway/client.test.ts ui/src/ui/chat/build-chat-items.test.ts
- node scripts/run-vitest.mjs src/gateway/reconnect-gating.test.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/views/chat.test.ts
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/ui/gateway.ts ui/src/ui/gateway.node.test.ts ui/src/ui/chat/history-limits.ts ui/src/ui/chat/build-chat-items.ts ui/src/ui/chat/build-chat-items.test.ts src/gateway/client.ts src/gateway/client.test.ts
- git diff --check
- PR CI/check graph passed on d9692555ee.
2026-05-14 05:36:09 -05:00
Ayaan Zaidi
095de07135 docs(platforms): link Android Play Store app 2026-05-14 16:04:31 +05:30
Gio Della-Libera
260e1e138b fix(ui): drop unrestorable redacted config placeholders
Summary:
- Sanitize Control UI form-mode config submissions after schema coercion and before config.set/config.apply.
- Drop stale redacted placeholders only when the loaded form also had the redaction sentinel and the original raw config lacks that path.
- Preserve restorable saved secrets and user-entered literal sentinels so the gateway's fail-closed validation remains authoritative.

Verification:
- node scripts/run-vitest.mjs ui/src/ui/controllers/config.test.ts ui/src/ui/controllers/config/form-utils.node.test.ts
- pnpm exec oxfmt --check --threads=1 ui/src/ui/controllers/config.ts ui/src/ui/controllers/config/form-utils.ts ui/src/ui/controllers/config.test.ts ui/src/ui/controllers/config/form-utils.node.test.ts docs/web/control-ui.md CHANGELOG.md
- git diff --check origin/main...HEAD
- pnpm check:changed
- GitHub PR checks green on head b35a5b975d
2026-05-14 05:33:20 -05:00
Josh Avant
a2963f51d5 fix(telegram): skip unmentioned group media before download (#81785)
* fix(telegram): skip unmentioned group media before download

* docs(changelog): note telegram require mention media fix
2026-05-14 05:22:24 -05:00
Josh Avant
2b04cedfb1 Fix subagent default model precedence (#81783)
* fix subagent default model precedence

* docs changelog for subagent default fix
2026-05-14 05:19:41 -05:00
Ayaan Zaidi
d25bece9f6 refactor(codex): tighten status rate-limit formatting 2026-05-14 15:45:33 +05:30
Matthew Schleder
724868cec8 docs: add Codex rate-limit changelog entry 2026-05-14 15:45:33 +05:30
Matthew Schleder
6b6538bd13 fix(codex): format status rate limits like usage 2026-05-14 15:45:33 +05:30
Vincent Koc
dbabfc550f fix(telnyx): validate webhook client state base64 2026-05-14 18:08:16 +08:00
Vincent Koc
c822824503 fix(qqbot): validate cron payload base64 2026-05-14 18:04:32 +08:00
Vincent Koc
fe97f1fa4f fix(voice-call): validate realtime media frame base64 2026-05-14 18:00:30 +08:00
Gio Della-Libera
b9dc6d86b8 test(config): refresh plugin schema fixtures 2026-05-14 17:58:27 +08:00
Gio Della-Libera
13c2e245aa fix(config): use plugin channel schemas in dry-run 2026-05-14 17:58:26 +08:00
Vincent Koc
f3f6a866ca fix(msteams): validate inline image base64 2026-05-14 17:57:53 +08:00
joshavant
6ae9c8bead note telegram worker dist fix 2026-05-14 04:56:14 -05:00
joshavant
8ba7927f6e fix telegram ingress worker dist entry 2026-05-14 04:55:39 -05:00
Vincent Koc
84ec355af8 fix(qa-channel): reject malformed inline attachment data 2026-05-14 17:54:55 +08:00
Ayaan Zaidi
23ed804657 fix(telegram): keep plugin slash commands on native path 2026-05-14 15:22:53 +05:30
Vincent Koc
23cfc81bcd fix(file-transfer): validate inline write base64 2026-05-14 17:51:20 +08:00
Vincent Koc
92524fcf98 fix(proxy): reject malformed debug proxy targets 2026-05-14 17:46:10 +08:00
Vincent Koc
a47132734b fix(agents): skip continuation bootstrap preload 2026-05-14 17:42:36 +08:00
Val Alexander
b405c6e640 fix(mac): verify launchd stop releases gateway port
Fixes #73132.

Summary:
- Verify macOS LaunchAgent stop/restart port postconditions before reporting success.
- Resolve the effective gateway port from launchd args, persisted service environment, then caller env.
- Delay degraded fallback success output until the listener port is confirmed released.

Verification:
- node scripts/run-vitest.mjs src/daemon/launchd.test.ts src/cli/daemon-cli/lifecycle.test.ts src/cli/daemon-cli/lifecycle-core.test.ts src/cli/daemon-cli/restart-health.test.ts
- pnpm exec oxfmt --check --threads=1 src/daemon/launchd.ts src/daemon/launchd.test.ts CHANGELOG.md
- git diff --check
- Testbox tbx_01krjxf8vrbjwxv3xfdx4770xr: pnpm check:changed
2026-05-14 04:41:45 -05:00
Vincent Koc
c70adb8528 fix(plugins): wrap malformed node proxy payloads 2026-05-14 17:40:38 +08:00
Vincent Koc
6b3998aa40 fix(gateway): ignore malformed host on session routes 2026-05-14 17:35:57 +08:00
Peter Steinberger
365c986a5b docs(models): clarify cli runtime alias comment 2026-05-14 10:35:35 +01:00
Peter Steinberger
ac5674b32c fix(web): keep legacy Brave search fallback provider-owned
- Keep doctor migration as canonical for legacy Brave web-search config.
- Move legacy runtime support into Brave-owned provider config handling.
- Preserve legacy config precedence over ambient BRAVE_API_KEY.

Verification:
- node scripts/run-vitest.mjs run src/secrets/runtime-web-tools.test.ts --maxWorkers=1
- pnpm test extensions/brave -- --maxWorkers=1
- pnpm check:changed via Blacksmith Testbox tbx_01krjwy2gc4d2sxb3hqxcbhhtk / https://github.com/openclaw/openclaw/actions/runs/25852532246
2026-05-14 10:32:55 +01:00
Peter Steinberger
731c8843ff test: update lint suppression allowlist 2026-05-14 10:27:12 +01:00
Peter Steinberger
554dfbd017 docs(ui): add i18n report changelog 2026-05-14 10:27:12 +01:00
Peter Steinberger
4feb4e6623 docs(ui): document i18n report usage 2026-05-14 10:27:12 +01:00
Peter Steinberger
266722500c fix(ui): avoid noisy i18n report locale warnings 2026-05-14 10:27:12 +01:00
samzong
4e76d6e427 fix(ui): harden i18n report filters
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-14 10:27:12 +01:00
samzong
ee9d471865 feat(ui): add i18n baseline report
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-14 10:27:12 +01:00
Vincent Koc
d5abbd29cc changelog: cover @sjf Codex marketplace handling and @scotthuang weixin 2.4.3
- (#81625) Codex migrate delayed marketplace + warning/next-step glyph cleanup. Thanks @sjf.
- (#81730) Weixin bundled catalog bump 2.4.1 -> 2.4.3. Thanks @scotthuang.
2026-05-14 17:26:45 +08:00
Mariano
a5c1956ca1 feat(codex): bind CLI sessions from nodes
Adds node-backed Codex CLI session listing and resume binding for paired nodes, including Windows shim-safe Codex resume spawning, docs, changelog, and focused Codex coverage.

Verification:
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/plugins/codex-harness.md extensions/codex/index.ts extensions/codex/src/command-formatters.ts extensions/codex/src/command-handlers.ts extensions/codex/src/commands.test.ts extensions/codex/src/conversation-binding-data.ts extensions/codex/src/conversation-binding.test.ts extensions/codex/src/conversation-binding.ts extensions/codex/src/node-cli-sessions.ts extensions/codex/src/node-cli-sessions.test.ts
- pnpm run lint:tmp:no-random-messaging
- pnpm run lint:extensions:bundled
- OPENCLAW_VITEST_MAX_WORKERS=4 pnpm test extensions/codex/src/node-cli-sessions.test.ts extensions/codex/src/conversation-binding.test.ts extensions/codex/src/commands.test.ts
- pnpm tsgo:extensions
- git diff --check
- AWS Crabbox focused proof run_a901a61e006f
2026-05-14 11:24:30 +02:00
Ayaan Zaidi
2268ce3a14 docs(changelog): note telegram cron html fix 2026-05-14 14:39:23 +05:30
Ayaan Zaidi
e28d66d531 fix(cli): preserve lazy sender formatting 2026-05-14 14:39:23 +05:30
Ayaan Zaidi
41aee75cd1 test(cli): prove lazy sender preserves html formatting 2026-05-14 14:39:23 +05:30
Peter Steinberger
04605f1670 docs: allow maintainer proof override 2026-05-14 10:08:54 +01:00
Peter Steinberger
a0f35574d0 Remove codex-cli backend and migrate to Codex runtime
Remove the bundled codex-cli backend, migrate legacy codex-cli refs and runtime pins to the Codex app-server runtime, and update live/backend workflow coverage for the supported CLI lanes.
2026-05-14 10:07:18 +01:00
Peter Steinberger
66b98b7294 chore: sync codex review skill 2026-05-14 10:02:14 +01:00
Peter Steinberger
a582fc2d5c chore: tighten codex review skill 2026-05-14 09:58:58 +01:00
Peter Steinberger
beea866a53 chore: add codex review skill 2026-05-14 09:56:13 +01:00
samzong
1d121c1f08 chore(gateway): add startup trace attribution (#81738)
Adds owner-level startup trace attribution for gateway auth, plugin loading, lookup counts, and plugin sidecar services.

Verification:
- node scripts/run-vitest.mjs src/plugins/startup-trace-segment.test.ts src/plugins/services.test.ts src/plugins/loader.test.ts src/gateway/server-startup-config.secrets.test.ts
- pnpm build
- pnpm check

CI override:
- Red checks are unrelated baseline noise. The failed CI shard is src/cli/plugins-install-persist.test.ts, which fails on origin/main 336ba2a2b3 with the same missing resolveIsNixMode mock export. PR #81738 touches gateway/plugin startup trace files and CHANGELOG.md, not the failing CLI plugin install test.

Thanks @samzong.

Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
2026-05-14 16:50:08 +08:00
Vincent Koc
12b8db34ee fix(browser): handle malformed node proxy payloads 2026-05-14 16:48:28 +08:00
Josh Avant
fd244fd76d Fix Telegram polling ingress under event-loop stalls (#81746)
* fix telegram polling ingress under event-loop stalls

* add changelog for telegram ingress fix
2026-05-14 03:35:06 -05:00
scotthuang
c35634c729 Fix/weixin catalog update 2.4.3 (#81730)
* fix(weixin): upgrade catalog to 2.4.3

* fix(weixin): update catalog integrity for 2.4.3

---------

Co-authored-by: scotthuang <scotthuang@tencent.com>
2026-05-14 03:32:38 -05:00
Vincent Koc
0668f1e003 fix(web): resolve explicit global search providers 2026-05-14 16:29:55 +08:00
Vincent Koc
94f3ecae9a perf(cli): route plugin list json directly 2026-05-14 16:29:21 +08:00
Vincent Koc
641ad418c9 fix(clickclack): skip malformed websocket frames 2026-05-14 16:26:02 +08:00
Eduardo Buitrago
336ba2a2b3 fix(agents): forward explicit per-run timeout to LLM idle watchdog (#79426)
Merged via squash.

Prepared head SHA: 0e6cf9b4d5
Co-authored-by: legolaz8451 <18042830+legolaz8451@users.noreply.github.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Reviewed-by: @joshavant
2026-05-14 03:24:01 -05:00
Vincent Koc
8717525fbc test(cli): cover parent startup budgets 2026-05-14 16:18:26 +08:00
Sarah Fortune
2f2563314a fix(codex): handle delayed plugin marketplace (#81625) 2026-05-14 01:17:58 -07:00
Jack Storment
b79c41d252 fix(memory): discover slugged daily memory files alongside date-only files
Widen daily memory filename discovery so slugged session-memory files flow through Dreaming, rem-backfill, rem-harness, doctor, and short-term promotion.

Preserve exact slugged source paths during historical seeding and rem-backfill attribution, including multiple files for the same day.

Add regression coverage for slugged ingestion, rem-backfill, rem-harness preview paths, and doctor backfill day extraction.

Fixes #69536.

Co-authored-by: Jack Storment <crazycoder131@gmail.com>
2026-05-14 09:17:44 +01:00
Vincent Koc
31a28eb5ba fix(media): reject malformed redirect locations 2026-05-14 16:16:56 +08:00
Kaspre
f71df80522 perf(plugins): memoize metadata snapshots narrowly 2026-05-14 16:15:50 +08:00
Vincent Koc
f6d0cc6cc3 perf(cli): keep channel option help lightweight 2026-05-14 16:13:28 +08:00
Vincent Koc
db743e4918 fix(voice-call): ignore malformed host for webhook paths 2026-05-14 16:11:29 +08:00
Vincent Koc
462a056210 fix(gateway): ignore malformed host on json routes 2026-05-14 16:06:23 +08:00
Vincent Koc
fe25ed214e refactor(cli): lazy-load devices runtime 2026-05-14 16:02:42 +08:00
Peter Steinberger
3fa9658b39 fix: carry transcript update sequence 2026-05-14 08:59:31 +01:00
samzong
c20224688c fix(gateway): carry transcript update sequence
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-14 08:59:31 +01:00
Vincent Koc
a012411d5e refactor(cli): lazy-load plugins runtime 2026-05-14 15:57:07 +08:00
anagnorisis2peripeteia
bcb9fa42be fix(models): keep CLI runtime providers in /models picker (#81239)
Merged via squash.

Prepared head SHA: 294741d1db
Co-authored-by: anagnorisis2peripeteia <129746152+anagnorisis2peripeteia@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-14 10:52:57 +03:00
Peter Steinberger
3bd47a95a8 test: align release check with external slack package 2026-05-14 08:49:48 +01:00
Peter Steinberger
e17f628a75 test(release): skip externalized slack pack paths 2026-05-14 08:49:00 +01:00
Peter Steinberger
7edcfabf51 fix(build): keep slack dependencies extension-owned 2026-05-14 08:49:00 +01:00
Peter Steinberger
e7ae306aa1 refactor(auth): use fs-safe stale lock recovery 2026-05-14 08:49:00 +01:00
Peter Steinberger
04afd114bb fix(auth): reclaim zombie-owned stale locks 2026-05-14 08:49:00 +01:00
Peter Steinberger
c499ef1a6b fix(auth): preserve non-signalable lock owners 2026-05-14 08:49:00 +01:00
Peter Steinberger
f84d031d38 fix(auth): fail closed on unreadable stale locks 2026-05-14 08:49:00 +01:00
Peter Steinberger
3048ad4731 refactor(infra): centralize stale lock cleanup 2026-05-14 08:49:00 +01:00
Peter Steinberger
ceb3092493 fix(auth): reclaim stale file locks 2026-05-14 08:49:00 +01:00
Vincent Koc
babd25c6b7 refactor(cli): lazy-load models runtime 2026-05-14 15:48:11 +08:00
Peter Steinberger
bfc798bd0b fix(ci): restore main build 2026-05-14 08:38:49 +01:00
Peter Steinberger
32f89760e3 docs: clarify landing recap requirement 2026-05-14 08:37:30 +01:00
Peter Steinberger
11017c93cf build: keep external provider deps out of core dist 2026-05-14 08:29:10 +01:00
Peter Steinberger
643dea2455 fix(mattermost): gate delivery success log 2026-05-14 08:29:07 +01:00
kinjitakabe
6789fe248b fix(mattermost): diagnose silent final-delivery completions
`deliverMattermostReplyPayload` accepted a substantive (non-reasoning) reply
payload, called the shared `deliverTextOrMediaReply`, and dropped its
`"empty"|"text"|"media"` return value on the floor. When the underlying chunker
or media-resolution produced no text and no media to send, the function
returned `Promise<void>` and the caller in `monitor.ts` unconditionally logged
`delivered reply to <channel>` — masking a silent completion where no
Mattermost API send ever happened (the symptom in #80501).

Thread the outcome through the helper, evaluate it against the original
payload to distinguish intentional reasoning suppression from a substantive
payload that vanished, and log a structured `mattermost no-visible-reply`
diagnostic for the substantive-vanished case. The misleading "delivered
reply to" log now only fires on actual visible delivery; reasoning-skipped
payloads correctly stay silent.

No behavior change: visible-delivery decisions, preview-finalization, and the
existing reasoning-suppression contract are untouched. Operators can now grep
the new diagnostic to detect the failure class instead of seeing the agent
appear to go silent.

Fixes #80501.
2026-05-14 08:29:07 +01:00
Josh Avant
10d2f41c83 fix(browser): request admin scope for CLI control (#81716)
* fix(browser): request admin scope for CLI control

* chore(changelog): note browser CLI scope fix
2026-05-14 02:20:14 -05:00
Vincent Koc
b1fc55fded fix(web): honor legacy search api key selection 2026-05-14 15:14:27 +08:00
Vincent Koc
8962d35264 refactor(cli): mark parent help in descriptors 2026-05-14 15:11:33 +08:00
Vincent Koc
d41907a5cb fix(slack): ignore malformed media redirects 2026-05-14 15:10:40 +08:00
Peter Steinberger
b8dccbf310 ci: run package patch guard in pr checks 2026-05-14 08:09:28 +01:00
Mariano Belinky
949012797b fix: reference codex watchdog changelog entry 2026-05-14 08:07:47 +01:00
Mariano Belinky
1aef36b60d fix(codex): keep post-tool watchdog armed 2026-05-14 08:07:47 +01:00
Vincent Koc
1e5641ba82 changelog: cover @sjf migrate trailing-period cleanup (#81705) 2026-05-14 15:03:30 +08:00
Peter Steinberger
0916a19cb5 ci: block new package patches 2026-05-14 07:57:59 +01:00
Vincent Koc
1d5f01500d fix(matrix): tolerate malformed location params 2026-05-14 14:55:52 +08:00
Peter Steinberger
625713091e docs: clarify plugin externalization guidance 2026-05-14 07:53:25 +01:00
Sarah Fortune
d9e999cf4f fix(migrate): drop trailing periods from migrate item messages (#81705) 2026-05-13 23:52:19 -07:00
Peter Steinberger
81b239dc98 build: externalize slack openshell vertex plugins 2026-05-14 07:46:58 +01:00
Vincent Koc
7f05ea60fa changelog: cover Windows sandbox bind, env-marker inference, bodyless media
- (#63074) Security/sandbox: include Windows USERPROFILE in blocked home roots. Thanks @luoyanglang.
- Models config/auth: stop inferring providers from broad env-var name patterns; use structured SecretRefs only. Thanks @sallyom.
- Media fetch: avoid buffering bodyless responses. Thanks @shakkernerd.
2026-05-14 14:46:06 +08:00
Vincent Koc
8ec9bfb31e fix(ci): authenticate performance report publishing 2026-05-14 14:40:20 +08:00
Josh Avant
4e1f59010e fix(gateway): suppress startup liveness warnings (#81699)
* fix(gateway): suppress startup liveness warnings

* docs(changelog): note diagnostic startup grace fix
2026-05-14 01:39:46 -05:00
Vincent Koc
25eef1203a fix(plugins): prefer installed memory tool owners 2026-05-14 14:35:45 +08:00
Vincent Koc
d656cda46d fix(process): normalize Windows child env keys 2026-05-14 14:34:00 +08:00
Peter Steinberger
5479b6b32c build(deps): consume fs-safe 0.2.3 2026-05-14 07:31:40 +01:00
Peter Steinberger
36755e4057 [codex] externalize amazon bedrock providers (#81687)
* build: externalize amazon bedrock providers

* build: skip external plugins in root dist graph

* test: update managed npm override expectation

* build: mark amazon providers external-only
2026-05-14 07:27:40 +01:00
Vincent Koc
6bd19bffd6 changelog: cover @sjf onboarding wizard provider-flag forwarding (#81669) 2026-05-14 14:23:47 +08:00
Vincent Koc
31de033590 fix(hooks): allow dot-prefixed handler paths 2026-05-14 14:23:13 +08:00
Vincent Koc
e064cc98f0 fix(ci): skip locale refresh on invalid provider auth 2026-05-14 14:13:39 +08:00
Sarah Fortune
0a42afae3a fix(onboard): forward provider auth flags through wizard (#81669)
The wizard's applyAuthChoice call dropped provider-specific flag values
like --openai-api-key, only forwarding token/tokenProvider. As a result,
maybeApplyApiKeyFromOption could not honor the flag and onboarding still
prompted "Use existing OPENAI_API_KEY?" when the operator already
passed --openai-api-key alongside an existing env var (e.g. onboard-fast
harnesses that pre-seed --openai-api-key "$OPENAI_API_KEY").

Spread opts into the inner opts bag so provider-specific flag values
reach the provider auth method via ctx.opts. When no flag is passed the
env-confirm prompt still fires unchanged.
2026-05-13 23:12:40 -07:00
pashpashpash
da1ccd3077 fix(replies): preserve rich coalesced block replies (#81689) 2026-05-14 15:10:58 +09:00
Vincent Koc
2ab08c8a19 fix(cli): keep plugin parent help lightweight 2026-05-14 14:09:53 +08:00
Vincent Koc
c635f0087e fix(plugins): preserve dot-prefixed package metadata 2026-05-14 14:08:53 +08:00
Vincent Koc
3b7d01b63f fix(ci): prefer valid locale refresh provider 2026-05-14 14:03:06 +08:00
Vincent Koc
8f612787a8 fix(ci): restore control ui locale refresh 2026-05-14 13:58:27 +08:00
WhatsSkiLL
eefa6ecea0 fix(plugins): discover setup provider env vars (#81542)
Discover provider plugins from setup.providers[].envVars credentials during provider discovery while keeping the deprecated providerAuthEnvVars fallback.

Co-authored-by: JARVIS-Glasses <whatsskilll@gmail.com>
2026-05-14 06:58:05 +01:00
Vincent Koc
9518d12e13 fix(codex): remap dot-prefixed bootstrap context 2026-05-14 13:55:26 +08:00
Peter Steinberger
65ea6fdb49 docs: clarify Codex home isolation 2026-05-14 06:51:57 +01:00
Vincent Koc
af3d9333aa fix(agents): remap dot-prefixed context paths 2026-05-14 13:40:25 +08:00
Vincent Koc
cd42df45d6 fix(telegram): allow dot-prefixed local media 2026-05-14 13:32:29 +08:00
Vincent Koc
3485a907d1 fix(auth): stop Codex OAuth refresh spam
Treat high-confidence app-server OAuth refresh invalidation as terminal auth-profile failure, while keeping entitlement and rate-limit payloads out of re-auth classification.
2026-05-14 13:28:47 +08:00
Vincent Koc
6cac228a0c changelog: cover @sjf migrate selection hint cleanup (#8da06d46f8f) 2026-05-14 13:24:20 +08:00
Vincent Koc
b73d01f13b fix(canvas): reject malformed document paths 2026-05-14 13:23:26 +08:00
samzong
bb8aa0cfe2 [Fix] Throttle agent event fanout (#80335)
Merged via squash.

Prepared head SHA: 5dddb405ad
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-13 22:21:46 -07:00
Vincent Koc
1fc92ddfb1 fix(cli): preserve multiline table colors 2026-05-14 13:18:08 +08:00
Vincent Koc
284dcc51b8 fix(replies): preserve rich outbound content 2026-05-14 13:17:06 +08:00
Vincent Koc
6cfb62d5aa fix(canvas): harden asset path resolution 2026-05-14 13:15:32 +08:00
Vincent Koc
bc0def52af fix(ci): read sparse package manifests from index 2026-05-14 13:11:42 +08:00
Vincent Koc
ce63b9ca46 fix(plugin-sdk): classify memory core alias 2026-05-14 13:11:42 +08:00
Sarah Fortune
8da06d46f8 fix(migrate): hide per-item hints in Codex skill/plugin selection prompts 2026-05-13 22:05:42 -07:00
Ayaan Zaidi
e44b915dbf docs(changelog): note oauthRef runtime auth fix (#81633) 2026-05-14 10:31:16 +05:30
Ayaan Zaidi
df4aac8f96 fix(auth): accept oauthRef profiles for runtime auth 2026-05-14 10:31:16 +05:30
Vincent Koc
c04bbd3cbb fix(agents): allow dot-prefixed sandbox paths 2026-05-14 12:52:24 +08:00
Vincent Koc
fe89243c3b fix(plugin-sdk): restore memory core alias 2026-05-14 12:50:22 +08:00
Val Alexander
6db2ee6583 fix(ios): restore privacy permission prompts
Restores first-use iOS authorization prompts for Contacts, Calendar, and Reminders by adding the missing usage descriptions, requesting access from `.notDetermined` in the service paths, and adding Settings Privacy & Access status/actions.

Verification:
- `plutil -lint apps/ios/Sources/Info.plist apps/ios/Tests/Info.plist apps/ios/ShareExtension/Info.plist apps/ios/ActivityWidget/Info.plist apps/ios/WatchApp/Info.plist apps/ios/WatchExtension/Info.plist`
- `swiftformat --lint apps/ios/Sources/Permissions/PermissionRequestBridge.swift apps/ios/Sources/Contacts/ContactsService.swift apps/ios/Sources/Calendar/CalendarService.swift apps/ios/Sources/Reminders/RemindersService.swift apps/ios/Sources/Settings/PrivacyAccessSectionView.swift apps/ios/Sources/Settings/SettingsTab.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift --config config/swiftformat`
- `swiftlint lint --config apps/ios/.swiftlint.yml apps/ios/Sources/Permissions/PermissionRequestBridge.swift apps/ios/Sources/Contacts/ContactsService.swift apps/ios/Sources/Calendar/CalendarService.swift apps/ios/Sources/Reminders/RemindersService.swift apps/ios/Sources/Settings/PrivacyAccessSectionView.swift apps/ios/Sources/Settings/SettingsTab.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Tests/PermissionRequestBridgeTests.swift`
- `git diff --check origin/main...HEAD`
- `rg '<<<<<<<|=======|>>>>>>>' CHANGELOG.md apps/ios apps/shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift`
- `pnpm ios:build`
- `xcodebuild test -project apps/ios/OpenClaw.xcodeproj -scheme OpenClaw -destination 'platform=iOS Simulator,name=iPhone 17' -configuration Debug -only-testing:OpenClawTests/PermissionRequestBridgeTests`
- Fresh-erased iPhone 17 simulator proof for Contacts denial/Open Settings, Calendar add-only/full-access upgrade, and Reminders authorization prompts.

Not tested: physical device, or a paired gateway command invocation after onboarding.
2026-05-13 23:45:35 -05:00
Vincent Koc
ca7349b585 fix(media): normalize cross-platform media paths 2026-05-14 12:43:15 +08:00
Val Alexander
dd4c68b525 fix(agents): suppress aborted assistant output
Summary:
- Suppress aborted embedded-run assistant partials, reasoning text, reply directives, and stale previous-assistant fallback output.
- Preserve clean timeout/error payloads, tool/media payloads, and compaction bookkeeping for non-aborted delivery paths.
- Add focused regressions for aborted partial text, reasoning text, stale fallback, and timeout delivery.

Verification:
- git diff --check HEAD~1..HEAD
- PATH=/Users/buns/.nvm/versions/node/v22.22.2/bin:$PATH pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/agents/pi-embedded-runner/run.ts src/agents/pi-embedded-runner/run/payloads.errors.test.ts src/agents/pi-embedded-runner/run/payloads.ts
- PATH=/Users/buns/.nvm/versions/node/v22.22.2/bin:$PATH pnpm test src/agents/pi-embedded-runner/run/payloads.errors.test.ts src/agents/pi-embedded-runner/run/payloads.test.ts -- --reporter=verbose
- PATH=/Users/buns/.nvm/versions/node/v22.22.2/bin:$PATH pnpm test src/agents/pi-embedded-runner/run.overflow-compaction.loop.test.ts src/auto-reply/reply/agent-runner-execution.test.ts src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts src/auto-reply/reply/get-reply-run-queue.test.ts src/auto-reply/reply/abort.test.ts -- --reporter=verbose
- PATH=/Users/buns/.nvm/versions/node/v22.22.2/bin:$PATH pnpm test src/auto-reply/inbound.test.ts -- --reporter=verbose
- PATH=/Users/buns/.nvm/versions/node/v22.22.2/bin:$PATH pnpm check:changed
- GitHub CI run 25841945093 passed, including checks-node-auto-reply-core-top-level, checks-node-core, and build-artifacts.
- Real behavior proof run 25841947282 passed.

Fixes #48241
Thanks @BunsDev
2026-05-13 23:41:59 -05:00
Vincent Koc
89987df3d5 test(matrix): guard externalized runtime deps 2026-05-14 12:38:17 +08:00
Vincent Koc
b97b5d6f22 changelog: cover three @sjf codex-migrate UX fixes
- humanize MIGRATION_REASON_* codes in conflict-status messaging
- swap migrate glyphs for manual-review (🔍) and archive (📖) items
- split codex-migrate output into preview + result phases
2026-05-14 12:36:45 +08:00
Bek
aa39107261 docs: align Slack docs for socket mode and troubleshooting (#81647) 2026-05-14 00:34:30 -04:00
Vincent Koc
117d2c9f2e changelog: cover Codex MCP, migration binary, and subagent maintenance fixes
- (#81551) Codex app-server MCP server projection, thread rotation, resume resend (jalehman)
- (#81582) Codex migration uses managed codex binary (fuller-stack-dev)
- (#81498) Subagent registry sessions preserved through maintenance (ai-hpc)
2026-05-14 12:31:58 +08:00
Vincent Koc
f5ebe63ecd fix(auto-reply): preserve debounce ordering 2026-05-14 12:29:30 +08:00
Sarah Fortune
2d231cef27 fix(migrate): humanize conflict-status messaging across migrate UI
Replace internal `MIGRATION_REASON_*` codes with natural sentences across the migrate UI.

| Surface | Before | After |
| --- | --- | --- |
| Selection prompt (skill) | `(Codex CLI skill; conflict: target exists)` | `(Codex skill already installed in workspace)` |
| Selection prompt (plugin) | `(openai-curated; conflict: plugin exists)` | `(openai-curated plugin already installed in workspace)` |
| Plan/result row (skill conflict) | `• conflict: gh-address-comments (Copy Codex skill into OpenClaw)` | `⚠️ gh-address-comments (Already installed in workspace.)` |
| Plan/result row (plugin conflict) | `• conflict: <name> (Install Codex plugin into OpenClaw)` | `⚠️ <name> (Already installed in workspace.)` |
2026-05-13 21:25:58 -07:00
rolandrscheel
e4cee2eb69 perf(gateway): cache session list resolver lookups
Refs #75839.\n\nRebases and lands the sessions.list resolver-cache fix from #77187 after maintainer conflict repair. The change keeps cache state scoped to a single sessions.list call and memoizes deterministic per-row resolver work for repeated provider/model tuples.\n\nVerification:\n- pnpm test src/gateway/session-utils.perf.test.ts src/gateway/session-utils.test.ts\n- pnpm exec oxfmt --check --threads=1 src/gateway/session-utils.ts src/gateway/session-utils.perf.test.ts scripts/github/real-behavior-proof-policy.mjs\n- git diff --check HEAD -- CHANGELOG.md scripts/github/real-behavior-proof-policy.mjs src/gateway/session-utils.perf.test.ts src/gateway/session-utils.ts\n- GitHub PR checks: 87 passing, CodeQL neutral, 21 skipped\n\nCo-authored-by: OpenClaw Agent <openclaw-agent@users.noreply.github.com>
2026-05-13 23:20:40 -05:00
Vincent Koc
5b418c3c4f fix(channels): preserve Telegram ordering without blocking follow-ups 2026-05-14 12:19:02 +08:00
Val Alexander
c722ae6a65 fix(control-ui): prevent iOS input zoom
Fixes #64651. Supersedes #64673.

Keeps shared form, config, and usage Control UI text-entry controls at 16px on touch-primary devices while preserving chat composer input sizing, so iOS Safari no longer auto-zooms focused fields.

Verification:
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/styles/components.css ui/src/styles/config.css ui/src/styles/usage.css ui/src/styles/chat/layout.test.ts ui/src/styles/components.test.ts ui/src/styles/config.test.ts ui/src/styles/usage.test.ts
- git diff --check
- pnpm test ui/src/styles/chat/layout.test.ts ui/src/styles/components.test.ts ui/src/styles/config.test.ts ui/src/styles/usage.test.ts
- pnpm check:changed
- Playwright WebKit iPhone 12 computed-style proof for all targeted controls at 16px
- GitHub Real behavior proof, CI, and workflow sanity on exact PR head fa0d44a8fd
2026-05-13 23:17:34 -05:00
Val Alexander
5d4a8b0072 fix(agents): make trajectory cleanup timeout configurable
Refs #75839.\n\nAdds OPENCLAW_TRAJECTORY_FLUSH_TIMEOUT_MS and OPENCLAW_AGENT_CLEANUP_TIMEOUT_MS for agent cleanup steps while preserving the 10s default. Includes focused timeout precedence tests, trajectory docs, and changelog coverage.\n\nVerification:\n- pnpm test src/agents/run-cleanup-timeout.test.ts\n- pnpm exec oxfmt --check --threads=1 src/agents/run-cleanup-timeout.ts src/agents/run-cleanup-timeout.test.ts\n- pnpm format:docs:check docs/tools/trajectory.md\n- git diff --check\n- pnpm check:changed\n- GitHub PR checks: 88 passing, CodeQL neutral, 21 skipped
2026-05-13 23:09:56 -05:00
Vincent Koc
5496c0d5b7 docs(testing): clarify pnpm proof routing 2026-05-14 12:09:17 +08:00
Josh Lehman
1ee0d51e92 fix(codex): preserve MCP servers in app-server harness (#81551)
* Plumb bundle MCP config into Codex app server

* fix: align codex mcp thread config with pi

* fix: rotate codex mcp threads when disabled

* fix: scope codex bundle mcp to bundled servers

* fix(codex): resend user MCP config on resume

---------

Co-authored-by: Josh Lehman <phaedrus@Mac.hsd1.ca.comcast.net>
2026-05-13 21:05:20 -07:00
Val Alexander
4935e24c7a fix: reconcile control ui run status cleanup
Fix stale Control UI active-run cleanup across terminal, reconnect, reset, and session-switch paths. Adds shared run lifecycle cleanup, stale compaction/fallback reconciliation, focused tests, and the compact composer run-status chip. Fixes #76874 and #64220; refs #71630. Validated with green PR CI on head 141f07158f and focused local UI tests.
2026-05-13 23:03:45 -05:00
Josh Lehman
aac216d699 fix: route plugin LLM completions through Codex runtime (#81511)
* fix: route plugin LLM completions through Codex runtime

* fix: preserve OpenRouter completion model ids

* fix: allow registry config compat guards
2026-05-13 21:02:28 -07:00
Vincent Koc
3b8ac38ae9 fix(codex): classify app-server auth refresh failures
Classify Codex native/app-server auth refresh logout failures and preserve app-server relogin detail in RPC errors.
2026-05-14 11:56:18 +08:00
Kevin Lin
78644bc6de fix: carry codex migration config through onboarding 2026-05-13 20:50:07 -07:00
Vincent Koc
3da9027770 fix(update): allow update config size drops 2026-05-14 11:47:17 +08:00
Val Alexander
256377c029 feat(ui): add WebChat auto-scroll mode selector
Add a persisted Control UI/WebChat auto-scroll mode setting with near-bottom, always, and off modes. The implementation preserves the current near-bottom behavior by default, keeps manual scroll-to-bottom available when automatic scrolling is off, exposes the selector in desktop and mobile chat controls, syncs i18n fallbacks, and adds focused storage/render/scroll coverage.

Verification:
- pnpm test ui/src/ui/app-settings.test.ts ui/src/ui/views/chat.test.ts ui/src/ui/app-render.helpers.node.test.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/storage.node.test.ts ui/src/ui/app-scroll.test.ts -- --reporter=verbose
- pnpm check:changed
- pnpm ui:i18n:check
- pnpm ui:build
- PR CI green on head 1b8859c8ba

Fixes #7648.
Fixes #81287.
2026-05-13 22:24:53 -05:00
NVIDIAN
7c5222a195 fix: preserve pending subagent sessions during maintenance (#81498) 2026-05-13 23:19:18 -04:00
pashpashpash
78eb92e622 Route Codex message tool replies back to WebChat and TUI (#81586)
* fix: route internal ui message tool replies

* docs: document reserved codex sdk helpers

* test(gateway): stabilize sessions send agent assertion

* fix(agents): preserve rich internal source replies

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-14 11:55:54 +09:00
Val Alexander
faa443a452 fix(chat/ios): downscale image attachments before send
Resize iOS chat PhotosPicker image attachments through the shared JPEG transcoder before staging/sending. Cap long edge and payload bytes, strip source metadata, preserve previews from processed data, and add focused processor/view-model regression tests.\n\nFixes #68524.\nSupersedes #73710.
2026-05-13 21:44:05 -05:00
Jerry-Xin
61ae9b7193 fix(update): preserve config during update repair
Preserve update-time config state by snapshotting before repair/restart writes, keeping plugin install records available for migration, and blocking unsafe update-time config size drops.

Also documents the Codex reserved SDK subpaths needed by the plugin contract guardrail.

Fixes #80077.

Thanks @Jerry-Xin and @vincentkoc.

Co-authored-by: Jerry-Xin <3401616+Jerry-Xin@users.noreply.github.com>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-05-14 10:43:33 +08:00
Jason
3fd64a281f fix: use managed codex binary for source migration (#81582)
* fix: use managed codex binary for source migration

* docs: document codex reserved sdk subpaths
2026-05-13 19:40:54 -07:00
Vincent Koc
c95ccf43c1 fix: sync codex cli package pin 2026-05-14 10:35:18 +08:00
Val Alexander
6a41a54212 fix(macos): harden direct gateway TLS pinning
Summary:
- Require macOS system trust before saving and accepting first-use direct `wss://` gateway TLS pins.
- Honor `gateway.remote.tlsFingerprint` in macOS direct node-mode TLS params.
- Add focused Swift coverage and update remote gateway docs/changelog.

Verification:
- Local: swiftformat --lint on touched Swift files.
- Local: git diff --check HEAD~1..HEAD.
- Local: swift test --package-path apps/shared/OpenClawKit --filter GatewayTLSPinningTests.
- Local: swift test --package-path apps/macos --filter 'MacNodeModeCoordinatorTests|GatewayEndpointStoreTests'.
- Local: PATH=/Users/buns/.nvm/versions/node/v24.13.0/bin:$PATH pnpm docs:list.
- CI: macos-node, macos-swift, check-docs, security-fast, security-scm-fast, security-dependency-audit, Opengrep OSS, and changed-path checks passed on PR head cf383fc047.

Fixes #50642.
Supersedes #50643.
2026-05-13 21:30:22 -05:00
Eduardo Piva
983064f5f8 fix(sessions): report ACP-runtime metadata for ACP-keyed sessions
Report ACP control-plane session runtime metadata from persisted ACP session metadata/backend, and keep ACP-shaped bridge sessions on normal configured model/runtime metadata.

Proof: focused sessions runtime/model-display tests, core prod/test typechecks, touched-file format check, seeded openclaw sessions --json behavior proof, and passing relevant CI. Known unrelated red check: checks-fast-contracts-plugins-d plugin SDK documentation contract for codex helper subpaths.
2026-05-13 19:03:50 -07:00
Sarah Fortune
bce56bacc7 fix(migrate): swap glyphs on manual-review and archive item rows
Manual-review items are kind:"manual" with status:"skipped" so they were rendering with ⏭️, which reads like "done, ignored" — exactly the wrong signal for items that still need user attention. Render with 🔍 instead so the row says "look closer here".

Archive items end up status:"migrated" once written to the report dir, so they were rendering with , which overstates what happened — the file was saved aside, not imported. Render with 📖 so the row reads "filed away".

Skill/plugin/secret/memory rows continue to render with their status glyphs (  ⏭️ ⚠️) unchanged. JSON output (--json) is unaffected.
2026-05-13 18:58:17 -07:00
Vincent Koc
e774b25b2f fix(agents): preserve reply metadata through tool media 2026-05-14 09:54:31 +08:00
pashpashpash
3ce922437f fix: load Codex for selectable OpenAI agent models
Treat selectable configured OpenAI agent models as Codex runtime requirements during plugin auto-enable, startup planning, and doctor install repair.\n\nPR: https://github.com/openclaw/openclaw/pull/81591
2026-05-14 09:51:15 +08:00
Vincent Koc
97ed9b2d82 test(agents): fix live profile lint 2026-05-14 09:19:04 +08:00
Vincent Koc
5923d9e807 fix(plugin-sdk): export codex runtime helpers 2026-05-14 09:19:04 +08:00
Vincent Koc
a504cd0190 test: make root permission assertions deterministic 2026-05-14 08:52:41 +08:00
Peter Steinberger
f3361dc928 test(agents): surface live OpenAI replay auth failures 2026-05-14 01:36:56 +01:00
Val Alexander
52370c5998 feat(ui): add browser-local Control UI text size setting
Adds a bounded browser-local Control UI text size setting in Appearance and Quick Settings, persists it in UiSettings, and applies CSS text-scale variables across chat text, composer input, sidebars, and tool cards while preserving mobile Safari input zoom safety.

Fixes #8547.
Thanks @BunsDev.
2026-05-13 19:18:05 -05:00
Peter Steinberger
0b55317494 test(plugins): isolate capability provider runtime mocks 2026-05-14 01:07:57 +01:00
Peter Steinberger
3225ec43c8 test(plugin-install): align npm peer scan expectations 2026-05-14 00:51:09 +01:00
Peter Steinberger
23446a248b ci(release): fail full validation on child failure 2026-05-14 00:46:22 +01:00
Eduardo Piva
9431d18aaf fix(sessions): classify spawn-child sessions correctly
Classify ACP spawn-child sessions via persisted spawnedBy metadata and share the session kind classifier across sessions/status output.

Verified with Azure Crabbox seeded ACP session-store proof, targeted session/status tests, touched-file lint, build, and green PR CI.
2026-05-13 16:39:04 -07:00
pashpashpash
74860e93fd fix(codex): preserve user home for app-server launches 2026-05-13 16:37:03 -07:00
3879 changed files with 248135 additions and 39751 deletions

View File

@@ -0,0 +1,130 @@
---
name: autoreview
description: "Autoreview closeout: local dirty changes, PR branch vs main, parallel tests."
---
# Autoreview
Run Codex's built-in code review as a closeout check. This is code review (`codex review`), not Guardian `auto_review` approval routing.
Codex native review mode performs best and is recommended. Non-Codex reviewers are fallback/second-opinion paths that receive a generated diff prompt, not the full Codex review-mode runtime.
Use when:
- user asks for Codex review / autoreview / second-model review
- after non-trivial code edits, before final/commit/ship
- reviewing a local branch or PR branch after fixes
## Contract
- Treat review output as advisory. Never blindly apply it.
- Verify every finding by reading the real code path and adjacent files.
- Read dependency docs/source/types when the finding depends on external behavior.
- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes that over-complicate the codebase.
- Prefer small fixes at the right ownership boundary; no refactor unless it clearly improves the bug class.
- Keep going until the selected review path returns no accepted/actionable findings.
- If a review-triggered fix changes code, rerun focused tests and rerun the review helper.
- Default to Codex review. If Codex is unavailable or exits with an error, the helper falls back to the first configured CLI from `claude -p`, `pi -p`, `opencode run`, `droid exec`, or `copilot`. Prefer Codex for final closeout because it uses native review mode; non-Codex reviewers use a Codex-inspired generated diff prompt. The helper runs nested Codex review in yolo/full-access mode by default; use `--no-yolo` only when intentionally testing sandbox behavior.
- Stop as soon as the review command/helper exits 0 with no accepted/actionable findings. Do not run an extra direct `codex review` just to get a nicer "clean" line, a second opinion, or clearer closeout wording.
- Treat the helper's successful exit plus absence of actionable findings as the clean review result, even if the underlying Codex CLI output is terse.
- If rejecting a finding as intentional/not worth fixing, add a brief inline code comment only when it explains a real invariant or ownership decision that future reviewers should know.
- Do not push just to review. Push only when the user requested push/ship/PR update.
## Pick Target
Dirty local work:
```bash
codex review --uncommitted
```
Use this only when the patch is actually unstaged/staged/untracked in the
current checkout. For committed, pushed, or PR work, point Codex at the commit
or branch diff instead; do not force `--mode local` / `--uncommitted` just
because the helper docs mention dirty work first. A clean `--uncommitted` review
only proves there is no local patch.
Branch/PR work:
```bash
git fetch origin
codex review --base origin/main
```
Do not pass an inline prompt with `--base`; current CLI rejects `--base` + `[PROMPT]` even though help text is ambiguous. If custom instructions are needed, run the plain base review first, then do a local/manual follow-up pass.
If an open PR exists, use its actual base:
```bash
base=$(gh pr view --json baseRefName --jq .baseRefName)
codex review --base "origin/$base"
```
Committed single change:
```bash
codex review --commit HEAD
```
or with the helper:
```bash
.agents/skills/autoreview/scripts/autoreview --mode commit --commit HEAD
```
Use commit review for already-landed or already-pushed work on `main`. Reviewing
clean `main` against `origin/main` is usually an empty diff after push. For a
small stack, review each commit explicitly or review the branch before merging
with `--base`.
## Parallel Closeout
Format first if formatting can change line locations. Then it is OK to run tests and review in parallel:
```bash
.agents/skills/autoreview/scripts/autoreview --parallel-tests "<focused test command>"
```
Tradeoff: tests may force code changes that stale the review. If tests or review lead to code edits, rerun the affected tests and rerun review until no accepted/actionable findings remain. Once that rerun exits cleanly, stop; do not spend another long review cycle on redundant confirmation.
## Context Efficiency
Codex review is usually noisy. Default to a subagent filter when subagents are available. Ask it to run the review and return only:
- actionable findings it accepts
- findings it rejects, with one-line reason
- exact files/tests to rerun
Run inline only for tiny changes or when subagents are unavailable.
## Helper
Bundled helper:
```bash
.agents/skills/autoreview/scripts/autoreview --help
```
The helper:
- chooses dirty `--uncommitted` first
- otherwise uses current PR base if `gh pr view` works
- otherwise uses `origin/main` for non-main branches
- use `--mode commit --commit <ref>` for already-committed work, especially clean `main` after landing
- should be left in `--mode auto` or forced to `--mode branch` for PR/branch work; do not force `--mode local` after committing
- supports `--reviewer codex|claude|pi|opencode|droid|copilot|auto`; `auto` means Codex first
- supports `--fallback-reviewer auto|claude|pi|opencode|droid|copilot|none`; default is configured CLI fallback
- falls back only when Codex is unavailable or exits nonzero, not when Codex reports findings
- writes only to stdout unless `--output` or `AUTOREVIEW_OUTPUT` is set
- supports `--dry-run`, `--parallel-tests`, and commit refs
- runs nested review with `--dangerously-bypass-approvals-and-sandbox --sandbox danger-full-access` by default
- keeps accepting `--full-access`; use `--no-yolo` or `AUTOREVIEW_YOLO=0` to opt out
- still accepts legacy `CODEX_REVIEW_*` env vars when the matching `AUTOREVIEW_*` var is unset
- prints `autoreview clean: no accepted/actionable findings reported` when the selected review command exits 0
## Final Report
Include:
- review command used
- tests/proof run
- findings accepted/rejected, briefly why
- the clean review result from the final helper/review run, or why a remaining finding was consciously rejected
Do not run another Codex review solely to improve the final report wording. If the final helper run exited 0 and produced no accepted/actionable findings, report that exact run as clean.

View File

@@ -0,0 +1,630 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: autoreview [options]
Options:
--mode auto|local|branch|commit
Target selection. Default: auto.
--base REF Base ref for branch review. Default: PR base or origin/main.
--commit REF Commit ref for commit review. Default: HEAD.
--reviewer codex|claude|pi|opencode|droid|copilot|auto
Review engine. Default: Codex with configured fallback on error.
--fallback-reviewer auto|claude|pi|opencode|droid|copilot|none
Fallback when Codex is unavailable or exits nonzero. Default: auto.
--codex-bin PATH Codex binary. Default: codex.
--claude-bin PATH Claude binary. Default: claude.
--pi-bin PATH Pi binary. Default: pi.
--opencode-bin PATH OpenCode binary. Default: opencode.
--droid-bin PATH Droid binary. Default: droid.
--copilot-bin PATH GitHub Copilot binary. Default: copilot.
--full-access Keep yolo/full-access mode enabled. Default.
--no-yolo Run nested Codex review with normal sandbox/approval prompts.
--output FILE Also save output to file.
--parallel-tests CMD Run review and test command concurrently.
--dry-run Print selected commands, do not run.
-h, --help Show help.
Modes:
local codex review --uncommitted
branch codex review --base <base>
commit codex review --commit <commit>
auto dirty tree -> local, else PR/current branch -> branch
EOF
}
mode=auto
base_ref=
commit_ref=HEAD
reviewer=${AUTOREVIEW_REVIEWER:-${CODEX_REVIEW_REVIEWER:-auto}}
fallback_reviewer=${AUTOREVIEW_FALLBACK_REVIEWER:-${CODEX_REVIEW_FALLBACK_REVIEWER:-auto}}
codex_bin=${CODEX_BIN:-codex}
claude_bin=${CLAUDE_BIN:-claude}
pi_bin=${PI_BIN:-pi}
opencode_bin=${OPENCODE_BIN:-opencode}
droid_bin=${DROID_BIN:-droid}
copilot_bin=${COPILOT_BIN:-copilot}
codex_args=()
yolo=${AUTOREVIEW_YOLO:-${CODEX_REVIEW_YOLO:-1}}
output=${AUTOREVIEW_OUTPUT:-${CODEX_REVIEW_OUTPUT:-}}
parallel_tests=
dry_run=false
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
mode=${2:-}
shift 2
;;
--base)
base_ref=${2:-}
shift 2
;;
--commit)
commit_ref=${2:-}
shift 2
;;
--reviewer)
reviewer=${2:-}
shift 2
;;
--fallback-reviewer)
fallback_reviewer=${2:-}
shift 2
;;
--codex-bin)
codex_bin=${2:-}
shift 2
;;
--claude-bin)
claude_bin=${2:-}
shift 2
;;
--pi-bin)
pi_bin=${2:-}
shift 2
;;
--opencode-bin)
opencode_bin=${2:-}
shift 2
;;
--droid-bin)
droid_bin=${2:-}
shift 2
;;
--copilot-bin)
copilot_bin=${2:-}
shift 2
;;
--full-access)
yolo=1
shift
;;
--no-yolo)
yolo=0
shift
;;
--output)
output=${2:-}
shift 2
;;
--parallel-tests)
parallel_tests=${2:-}
shift 2
;;
--dry-run)
dry_run=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
exit 2
;;
esac
done
case "$yolo" in
0|false|False|FALSE|no|No|NO|off|Off|OFF) ;;
*) codex_args+=(--dangerously-bypass-approvals-and-sandbox --sandbox danger-full-access) ;;
esac
case "$mode" in
auto|local|branch|commit) ;;
*)
echo "invalid --mode: $mode" >&2
exit 2
;;
esac
case "$reviewer" in
auto|codex|claude|pi|opencode|droid|copilot) ;;
*)
echo "invalid --reviewer: $reviewer" >&2
exit 2
;;
esac
case "$fallback_reviewer" in
auto|claude|pi|opencode|droid|copilot|none) ;;
*)
echo "invalid --fallback-reviewer: $fallback_reviewer" >&2
exit 2
;;
esac
repo_root=$(git rev-parse --show-toplevel)
current_branch=$(git branch --show-current 2>/dev/null || true)
dirty=false
if [[ -n "$(git status --porcelain)" ]]; then
dirty=true
fi
pr_url=
if [[ -z "$base_ref" && "$mode" != local ]] && command -v gh >/dev/null 2>&1; then
if pr_lines=$(gh pr view --json baseRefName,url --jq '[.baseRefName, .url] | @tsv' 2>/dev/null); then
base_name=${pr_lines%%$'\t'*}
pr_url=${pr_lines#*$'\t'}
if [[ -n "$base_name" ]]; then
base_ref="origin/$base_name"
fi
fi
fi
if [[ -z "$base_ref" ]]; then
base_ref=origin/main
fi
review_kind=
if [[ "$mode" == local || ( "$mode" == auto && "$dirty" == true ) ]]; then
review_kind=local
elif [[ "$mode" == commit ]]; then
review_kind=commit
elif [[ "$mode" == branch || ( "$mode" == auto && -n "$current_branch" && "$current_branch" != "main" ) ]]; then
review_kind=branch
else
echo "no review target: clean main checkout and no forced mode" >&2
exit 1
fi
if [[ "$review_kind" == local ]]; then
review_cmd=("$codex_bin" "${codex_args[@]}" review --uncommitted)
elif [[ "$review_kind" == commit ]]; then
review_cmd=("$codex_bin" "${codex_args[@]}" review --commit "$commit_ref")
else
review_cmd=("$codex_bin" "${codex_args[@]}" review --base "$base_ref")
fi
printf 'autoreview target: %s\n' "$review_kind"
printf 'branch: %s\n' "${current_branch:-detached}"
if [[ -n "$pr_url" ]]; then
printf 'pr: %s\n' "$pr_url"
fi
if [[ "$reviewer" == auto ]]; then
printf 'reviewer: codex\n'
else
printf 'reviewer: %s\n' "$reviewer"
fi
case "$reviewer" in
codex|auto) ;;
*)
printf 'note: Codex native review mode is the recommended and best-supported review path; %s uses a generated diff prompt.\n' "$reviewer"
;;
esac
if [[ "$reviewer" == auto || "$reviewer" == codex ]]; then
printf 'review:'
printf ' %q' "${review_cmd[@]}"
printf '\n'
else
printf 'review: %s prompt review\n' "$reviewer"
fi
if [[ -n "$parallel_tests" ]]; then
printf 'tests: %s\n' "$parallel_tests"
fi
if [[ "$review_kind" == branch ]]; then
printf 'fetch: git fetch origin --quiet\n'
fi
if [[ -n "$output" ]]; then
printf 'output: %s\n' "$output"
fi
if [[ "$dry_run" == true ]]; then
exit 0
fi
if [[ "$review_kind" == branch ]]; then
git fetch origin --quiet || {
echo "warning: git fetch origin failed; reviewing with existing refs" >&2
}
fi
review_output=$output
review_output_is_temp=false
if [[ -z "$review_output" ]]; then
review_output=$(mktemp)
review_output_is_temp=true
fi
mkdir -p "$(dirname "$review_output")"
: > "$review_output"
cleanup() {
if [[ "${review_output_is_temp:-false}" == true && -n "${review_output:-}" ]]; then
rm -f "$review_output"
fi
if [[ -n "${prompt_file:-}" ]]; then
rm -f "$prompt_file"
fi
}
trap cleanup EXIT
run_review() {
mkdir -p "$(dirname "$review_output")"
"${review_cmd[@]}" 2>&1 | tee "$review_output"
}
diff_for_review() {
case "$review_kind" in
local)
git -C "$repo_root" diff --stat
git -C "$repo_root" diff --cached --stat
git -C "$repo_root" diff --find-renames
git -C "$repo_root" diff --cached --find-renames
while IFS= read -r untracked_file; do
[[ -n "$untracked_file" ]] || continue
git -C "$repo_root" diff --no-index -- /dev/null "$untracked_file" || true
done < <(git -C "$repo_root" ls-files --others --exclude-standard)
;;
commit)
git -C "$repo_root" show --find-renames --stat --format=fuller "$commit_ref"
git -C "$repo_root" show --find-renames --format=medium "$commit_ref"
;;
branch)
git -C "$repo_root" diff --find-renames --stat "$base_ref"...HEAD
git -C "$repo_root" diff --find-renames "$base_ref"...HEAD
;;
esac
}
build_prompt_file() {
prompt_file=$(mktemp)
{
cat <<EOF
You are performing a closeout code review for the current repository.
Review target: $review_kind
Branch: ${current_branch:-detached}
Base: ${base_ref:-}
Commit: ${commit_ref:-}
Rules:
- Review the proposed code change as a closeout reviewer.
- Focus on the diff below. If your CLI exposes read-only repository tools, inspect surrounding code and tests to verify findings; never modify files.
- Do not modify files.
- Report only discrete, actionable issues introduced by this change.
- Prioritize correctness, regressions, security, data loss, performance cliffs, and missing tests that would catch a real bug.
- Do not report pre-existing issues, speculative risks, broad rewrites, style nits, changelog gaps, or findings that depend on unstated assumptions.
- Identify the concrete scenario where the issue appears, and keep the line reference as small as possible.
- A finding should overlap changed code or clearly cite changed code as the cause.
- For each accepted/actionable finding, use exactly this format:
[P<0-3>] Short title
File: path:line
Why: one sentence
Fix: one sentence
- If no accepted/actionable findings, output exactly:
autoreview clean: no accepted/actionable findings reported
Diff:
EOF
diff_for_review
} > "$prompt_file" || return
}
reviewer_output_has_clean_marker() {
local path=$1
grep -Eq '^[^[:alnum:]]*autoreview clean: no accepted/actionable findings reported[[:space:]]*$' "$path"
}
run_prompt_reviewer() {
local selected=$1
local copilot_prompt=
local prompt_bytes=0
local reviewer_output
local status=0
if ! build_prompt_file; then
rm -f "${prompt_file:-}"
prompt_file=
return 1
fi
reviewer_output=$(mktemp)
mkdir -p "$(dirname "$review_output")"
case "$selected" in
claude)
if ! command -v "$claude_bin" >/dev/null 2>&1; then
echo "fallback reviewer unavailable: $claude_bin" >&2
status=127
elif printf 'fallback: claude -p\n' | tee -a "$review_output"; then
"$claude_bin" --tools "" --no-session-persistence -p < "$prompt_file" 2>&1 | tee -a "$review_output" "$reviewer_output"
status=$?
else
status=$?
fi
;;
pi)
if ! command -v "$pi_bin" >/dev/null 2>&1; then
echo "fallback reviewer unavailable: $pi_bin" >&2
status=127
elif printf 'fallback: pi -p\n' | tee -a "$review_output"; then
"$pi_bin" --no-tools --no-session -p < "$prompt_file" 2>&1 | tee -a "$review_output" "$reviewer_output"
status=$?
else
status=$?
fi
;;
opencode)
if ! command -v "$opencode_bin" >/dev/null 2>&1; then
echo "fallback reviewer unavailable: $opencode_bin" >&2
status=127
elif printf 'fallback: opencode run\n' | tee -a "$review_output"; then
"$opencode_bin" run --pure --dir "$repo_root" \
"Review the attached prompt file. Do not modify files." \
--file "$prompt_file" 2>&1 | tee -a "$review_output" "$reviewer_output"
status=$?
else
status=$?
fi
;;
droid)
if ! command -v "$droid_bin" >/dev/null 2>&1; then
echo "fallback reviewer unavailable: $droid_bin" >&2
status=127
elif printf 'fallback: droid exec\n' | tee -a "$review_output"; then
"$droid_bin" exec --cwd "$repo_root" -f "$prompt_file" 2>&1 | tee -a "$review_output" "$reviewer_output"
status=$?
else
status=$?
fi
;;
copilot)
if ! command -v "$copilot_bin" >/dev/null 2>&1; then
echo "fallback reviewer unavailable: $copilot_bin" >&2
status=127
elif printf 'fallback: copilot\n' | tee -a "$review_output"; then
prompt_bytes=$(wc -c < "$prompt_file" | tr -d '[:space:]')
if (( prompt_bytes > 120000 )); then
echo "copilot reviewer unavailable: generated prompt is too large for copilot -p; use codex, droid, or another file/stdin-capable reviewer" \
2>&1 | tee -a "$review_output" "$reviewer_output"
status=1
else
copilot_prompt=$(< "$prompt_file")
"$copilot_bin" -C "$repo_root" --available-tools=none --stream off --output-format text --silent \
-p "$copilot_prompt" \
2>&1 | tee -a "$review_output" "$reviewer_output"
status=$?
fi
else
status=$?
fi
;;
*)
echo "unsupported prompt reviewer: $selected" >&2
status=2
;;
esac
if [[ "$status" == 0 ]]; then
if grep -Eq '\[P[0-3]\]' "$reviewer_output"; then
status=1
elif ! grep -q '[^[:space:]]' "$reviewer_output"; then
status=1
elif ! reviewer_output_has_clean_marker "$reviewer_output"; then
status=1
fi
fi
rm -f "$reviewer_output"
rm -f "$prompt_file"
prompt_file=
return "$status"
}
run_selected_review() {
local selected=$1
case "$selected" in
codex)
if ! command -v "$codex_bin" >/dev/null 2>&1; then
echo "codex reviewer unavailable: $codex_bin" >&2
return 127
fi
run_review
;;
claude|pi|opencode|droid|copilot)
run_prompt_reviewer "$selected"
;;
*)
echo "unsupported reviewer: $selected" >&2
return 2
;;
esac
}
fallback_reviewer_is_available() {
local selected=$1
case "$selected" in
claude) command -v "$claude_bin" >/dev/null 2>&1 ;;
pi) command -v "$pi_bin" >/dev/null 2>&1 ;;
opencode) command -v "$opencode_bin" >/dev/null 2>&1 ;;
droid) command -v "$droid_bin" >/dev/null 2>&1 ;;
copilot) command -v "$copilot_bin" >/dev/null 2>&1 ;;
*) return 1 ;;
esac
}
run_auto_fallback_review() {
local selected
if [[ "$fallback_reviewer" != auto ]]; then
run_selected_review "$fallback_reviewer"
return $?
fi
for selected in claude pi opencode droid copilot; do
if fallback_reviewer_is_available "$selected"; then
run_selected_review "$selected"
return $?
fi
done
echo "fallback reviewer unavailable: no configured fallback CLI found" >&2
return 127
}
run_auto_review() {
run_selected_review codex
local status=$?
if [[ "$status" == 0 ]]; then
return 0
fi
if (( status > 128 && status < 192 )); then
return "$status"
fi
if review_output_has_findings; then
return "$status"
fi
if [[ "$fallback_reviewer" == none ]]; then
return "$status"
fi
if [[ "$fallback_reviewer" == auto ]]; then
printf 'autoreview warning: codex exited %s; trying configured fallback reviewers\n' "$status" >&2
else
printf 'autoreview warning: codex exited %s; falling back to %s\n' "$status" "$fallback_reviewer" >&2
fi
run_auto_fallback_review
}
elapsed_since() {
local started_at=$1
local finished_at
finished_at=$(date +%s)
printf '%s\n' "$((finished_at - started_at))"
}
format_elapsed() {
local seconds=$1
if (( seconds < 60 )); then
printf '%ss\n' "$seconds"
else
printf '%sm%ss\n' "$((seconds / 60))" "$((seconds % 60))"
fi
}
review_output_empty() {
[[ ! -s "$review_output" ]] || ! grep -q '[^[:space:]]' "$review_output"
}
review_findings_text() {
if grep -Fxq 'codex' "$review_output"; then
awk '
$0 == "codex" {
capture = 1
output = $0 ORS
next
}
capture {
output = output $0 ORS
}
END {
printf "%s", output
}
' "$review_output"
return
fi
cat "$review_output"
}
review_output_has_findings() {
review_findings_text | grep -Eq '\[P[0-3]\]'
}
report_clean_review_or_fail() {
local elapsed_text
elapsed_text=$(format_elapsed "${review_elapsed_seconds:-0}")
if review_output_has_findings; then
printf 'autoreview complete after %s\n' "$elapsed_text"
printf 'autoreview findings: accepted/actionable findings reported\n'
return 1
fi
if review_output_empty; then
printf 'autoreview complete after %s; no output\n' "$elapsed_text"
return 1
fi
printf 'autoreview complete after %s\n' "$elapsed_text"
printf 'autoreview clean: no accepted/actionable findings reported\n'
}
if [[ -z "$parallel_tests" ]]; then
review_started_at=$(date +%s)
set +e
if [[ "$reviewer" == auto ]]; then
run_auto_review
else
run_selected_review "$reviewer"
fi
review_status=$?
review_elapsed_seconds=$(elapsed_since "$review_started_at")
set -e
if [[ "$review_status" == 0 ]]; then
report_clean_review_or_fail
exit $?
fi
exit "$review_status"
fi
review_status_file=$(mktemp)
review_elapsed_file=$(mktemp)
tests_status_file=$(mktemp)
(
set +e
review_started_at=$(date +%s)
if [[ "$reviewer" == auto ]]; then
run_auto_review
else
run_selected_review "$reviewer"
fi
status=$?
elapsed=$(elapsed_since "$review_started_at")
printf '%s\n' "$status" > "$review_status_file"
printf '%s\n' "$elapsed" > "$review_elapsed_file"
) &
review_pid=$!
(
set +e
bash -lc "$parallel_tests"
status=$?
printf '%s\n' "$status" > "$tests_status_file"
) &
tests_pid=$!
wait "$review_pid" || true
wait "$tests_pid" || true
review_status=$(cat "$review_status_file")
review_elapsed_seconds=$(cat "$review_elapsed_file")
tests_status=$(cat "$tests_status_file")
rm -f "$review_status_file" "$review_elapsed_file" "$tests_status_file"
printf 'autoreview exit: %s\n' "$review_status"
printf 'tests exit: %s\n' "$tests_status"
if [[ "$review_status" != 0 || "$tests_status" != 0 ]]; then
exit 1
fi
report_clean_review_or_fail

View File

@@ -1,17 +1,32 @@
---
name: crabbox
description: Use Crabbox for OpenClaw remote validation across Linux, macOS, Windows, and WSL2. Default to Blacksmith Testbox for broad Linux proof; includes direct Blacksmith and owned AWS/Hetzner fallback notes when Crabbox fails.
description: Use the Crabbox wrapper for OpenClaw remote validation across Linux, macOS, Windows, and WSL2, including delegated Blacksmith Testbox proof. Report the actual provider and id.
---
# Crabbox
Use Crabbox when OpenClaw needs remote Linux proof for broad tests, CI-parity
checks, secrets, hosted services, Docker/E2E/package lanes, warmed reusable
boxes, sync timing, logs/results, cache inspection, or lease cleanup.
Use the Crabbox wrapper when OpenClaw needs remote Linux proof for broad tests,
CI-parity checks, secrets, hosted services, Docker/E2E/package lanes, warmed
reusable boxes, sync timing, logs/results, cache inspection, or lease cleanup.
Default backend: `blacksmith-testbox`. The separate `blacksmith-testbox` skill
has been removed; this skill owns both the normal Crabbox path and the direct
Blacksmith fallback playbook.
Crabbox is the transport/orchestration surface. The actual backend can be:
- brokered AWS Crabbox: direct provider, `provider=aws`, lease ids like
`cbx_...`, `syncDelegated=false`
- Blacksmith Testbox through Crabbox: delegated provider,
`provider=blacksmith-testbox`, ids like `tbx_...`, `syncDelegated=true`
For OpenClaw maintainer broad `pnpm` gates, Blacksmith Testbox through the
Crabbox wrapper is acceptable and often preferred when the standing Testbox
rules apply. Do not describe those runs as "AWS Crabbox"; report them as
Testbox-through-Crabbox with the `tbx_...` id and Actions run.
Use the repo `.crabbox.yaml` brokered AWS path when the task specifically needs
direct AWS Crabbox behavior, persistent direct-provider leases, `--fresh-pr`,
`--full-resync`, environment forwarding, capture/download support, or provider
comparison. Use `--provider blacksmith-testbox` when the task needs OpenClaw
maintainer Testbox proof, prepared CI environment, broad/heavy pnpm gates, or
the user asks for Testbox/Blacksmith.
## First Checks
@@ -28,9 +43,15 @@ pnpm crabbox:run -- --help | sed -n '1,120p'
- OpenClaw scripts prefer `../crabbox/bin/crabbox` when present. The user PATH
shim can be stale.
- Check `.crabbox.yaml` for repo defaults, but override provider explicitly.
Even if config still says AWS, maintainer validation should normally pass
`--provider blacksmith-testbox`.
- Check `.crabbox.yaml` for direct-provider defaults. Omitting `--provider`
means brokered AWS today.
- For broad OpenClaw maintainer `pnpm` gates, prefer the repo wrapper with
`--provider blacksmith-testbox` or the repo Testbox helpers when the standing
Testbox policy applies.
- Always report the actual provider and id. `cbx_...` means AWS Crabbox;
`tbx_...` means Blacksmith Testbox through Crabbox. If the output only says
`blacksmith testbox list`, use `blacksmith testbox list --all` before
concluding no box exists.
- If a warm direct-provider lease smells stale, retry with `--full-resync`
(alias `--fresh-sync`) before replacing the lease. This resets the remote
workdir, skips the fingerprint fast path, reseeds Git when possible, and
@@ -54,7 +75,24 @@ pnpm crabbox:run -- --help | sed -n '1,120p'
## macOS And Windows Targets
Use these only when the task needs an existing non-Linux host. OpenClaw broad
validation still defaults to `blacksmith-testbox`.
Linux validation uses the repo Crabbox config unless a provider is explicitly
requested.
When the user explicitly asks for brokered macOS runners, use Crabbox AWS
macOS only after confirming the deployed coordinator supports EC2 Mac host
lifecycle/image routes and the operator has AWS EC2 Mac Dedicated Host quota
and IAM. Prefer `CRABBOX_HOST_ID` for a known Crabbox-managed Dedicated Host,
or run the no-spend preflight first:
```sh
crabbox admin hosts quota --provider aws --target macos --region eu-west-1 --type mac2.metal --json
crabbox admin hosts allocate --provider aws --target macos --region eu-west-1 --type mac2.metal --dry-run --json
CRABBOX_MACOS_TYPES=all scripts/macos-host-region-preflight.sh
```
Do not silently substitute AWS macOS for normal OpenClaw Linux proof. Report
paid-host blockers as quota, IAM, coordinator deployment, or host availability
instead of falling back to local macOS.
Crabbox supports static SSH targets:
@@ -75,20 +113,15 @@ Crabbox supports static SSH targets:
with `../crabbox/bin/crabbox run --help`, config/flag tests, and the Crabbox
Go test suite.
## Default Blacksmith Backend
## Direct Brokered AWS Backend
Use this for `pnpm check`, `pnpm check:changed`, `pnpm test`,
`pnpm test:changed`, Docker/E2E/live/package gates, or anything likely to fan
out across many Vitest projects.
Use this when the task needs direct AWS Crabbox semantics rather than the
prepared Blacksmith Testbox CI environment.
Changed gate:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
pnpm crabbox:run -- \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
@@ -99,11 +132,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
Full suite:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
pnpm crabbox:run -- \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
@@ -114,11 +143,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
Focused rerun:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
pnpm crabbox:run -- \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
@@ -128,19 +153,53 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
Read the JSON summary. Useful fields:
- `provider`: should be `blacksmith-testbox`
- `leaseId`: `tbx_...`
- `syncDelegated`: should be `true`
- `provider`: `aws`
- `leaseId`: `cbx_...`
- `syncDelegated`: `false`
- `commandPhases`: populated when the command prints `CRABBOX_PHASE:<name>`
- `commandMs` / `totalMs`
- `exitCode`
Crabbox should stop one-shot Blacksmith Testboxes automatically after the run.
Verify cleanup when a run fails, is interrupted, or the command output is
unclear:
Crabbox should stop one-shot AWS leases automatically after the run. Verify
cleanup when a run fails, is interrupted, or the command output is unclear:
```sh
blacksmith testbox list
../crabbox/bin/crabbox list --provider aws
```
## Blacksmith Testbox Through Crabbox
Use this for OpenClaw maintainer broad/heavy `pnpm` gates when the prepared CI
environment is the right proof surface:
```sh
node scripts/crabbox-wrapper.mjs run \
--provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
-- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 OPENCLAW_TESTBOX=1 OPENCLAW_TESTBOX_REMOTE_RUN=1 pnpm check:changed
```
Read the JSON summary and the Testbox line. Useful fields:
- `provider`: `blacksmith-testbox`
- `leaseId`: `tbx_...`
- `syncDelegated`: `true`
- `syncPhases`: delegated/skipped because Blacksmith owns checkout/sync
- Actions run URL/id from the Testbox output
- `exitCode`
`blacksmith testbox list` may hide hydrating or ready boxes. Use:
```sh
blacksmith testbox list --all
blacksmith testbox status <tbx_id>
```
## Observability Flags
@@ -228,6 +287,13 @@ Use the smallest Crabbox lane that proves the reported user path, not just the
touched code. Aim for one after-fix E2E proof before commenting, closing, or
opening a PR for a user-visible bug.
When the user says "test in Crabbox", do not simply copy tests to the remote
box and run them there. Crabbox is for remote real-scenario proof: copy or
install OpenClaw as the user would, run the same setup/update/CLI/Gateway/API
call that failed, and capture behavior from that entrypoint. For regressions or
bug reports, prove the broken state first when feasible, then run the same
scenario after the fix.
Pick the lane by symptom:
- Docker/setup/install bug: build a package tarball and run the matching
@@ -249,8 +315,9 @@ Pick the lane by symptom:
Efficient flow:
1. Reproduce or prove the pre-fix symptom when feasible. If the issue cannot be
reproduced, capture the exact command and observed behavior instead.
1. Reproduce or prove the pre-fix symptom from the real user-facing entrypoint
when feasible. If the issue cannot be reproduced, capture the exact command
and observed behavior instead.
2. Patch locally and run narrow local tests for edit speed.
3. Run one Crabbox E2E command that starts from the user-facing entrypoint:
package install, Docker setup, onboarding, channel add, gateway start, or
@@ -331,13 +398,13 @@ Interactive CLI/onboarding:
## Reuse And Keepalive
For most Blacksmith-backed Crabbox calls, one-shot is enough. Use reuse only
when you need multiple manual commands on the same hydrated box.
For most Crabbox calls, one-shot is enough. Use reuse only when you need
multiple manual commands on the same hydrated box.
If Crabbox returns a reusable id or you intentionally keep a lease:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --id <tbx_id> --no-sync --timing-json --shell -- "pnpm test <path>"
pnpm crabbox:run -- --id <cbx_id-or-slug> --no-sync --timing-json --shell -- "pnpm test <path>"
```
Stop boxes you created before handoff:
@@ -358,18 +425,18 @@ Common desktop flow:
```sh
../crabbox/bin/crabbox warmup --provider hetzner --desktop --browser --class standard --idle-timeout 60m --ttl 240m
../crabbox/bin/crabbox desktop launch --provider hetzner --id <cbx_id-or-slug> --browser --url https://example.com --webvnc --open
../crabbox/bin/crabbox desktop launch --provider hetzner --id <cbx_id-or-slug> --browser --url https://example.com --webvnc --open --take-control
```
Useful WebVNC commands:
```sh
../crabbox/bin/crabbox webvnc --provider hetzner --id <cbx_id-or-slug> --open
../crabbox/bin/crabbox webvnc daemon start --provider hetzner --id <cbx_id-or-slug> --open
../crabbox/bin/crabbox webvnc --provider hetzner --id <cbx_id-or-slug> --open --take-control
../crabbox/bin/crabbox webvnc daemon start --provider hetzner --id <cbx_id-or-slug> --open --take-control
../crabbox/bin/crabbox webvnc daemon status --provider hetzner --id <cbx_id-or-slug>
../crabbox/bin/crabbox webvnc daemon stop --provider hetzner --id <cbx_id-or-slug>
../crabbox/bin/crabbox webvnc status --provider hetzner --id <cbx_id-or-slug>
../crabbox/bin/crabbox webvnc reset --provider hetzner --id <cbx_id-or-slug> --open
../crabbox/bin/crabbox webvnc reset --provider hetzner --id <cbx_id-or-slug> --open --take-control
../crabbox/bin/crabbox desktop doctor --provider hetzner --id <cbx_id-or-slug>
../crabbox/bin/crabbox desktop click --provider hetzner --id <cbx_id-or-slug> --x 640 --y 420
../crabbox/bin/crabbox desktop paste --provider hetzner --id <cbx_id-or-slug> --text "user@example.com"
@@ -382,18 +449,46 @@ Useful WebVNC commands:
browser/app inside the visible session, bridges the lease into the authenticated
WebVNC portal, and opens the portal. Keep browsers windowed for human QA; use
`--fullscreen` only for capture/video workflows.
For human handoff, include `--take-control` so the opened portal viewer gets
keyboard/mouse control automatically instead of landing as an observer.
Human handoff preflight:
- Do not assume a visible desktop or launched browser means the repo CLI/app is
installed, built, or on the interactive terminal's `PATH`.
- Before handing WebVNC to a human tester, prove the expected command from the
same kept lease and from a neutral directory such as `~`.
- If the handoff needs repo-local code, sync/build/link it explicitly on that
lease. Source-tree CLIs often need build output before a symlink works.
- Prefer a real `command -v <expected-command> && <expected-command> --version`
check over a repo-root-only `pnpm ...` command.
Generic handoff repair pattern:
```sh
../crabbox/bin/crabbox run --id <cbx_id-or-slug> --full-resync --shell -- \
"set -euo pipefail
pnpm install --frozen-lockfile
pnpm build
sudo ln -sf \"\$PWD/<cli-entry>\" /usr/local/bin/<expected-command>
cd ~
command -v <expected-command>
<expected-command> --version"
```
## If Crabbox Fails
Keep the fallback narrow. First decide whether the failure is Crabbox itself,
Blacksmith/Testbox, repo hydration, sync, or the test command.
the brokered AWS lease, Blacksmith/Testbox, repo hydration, sync, or the test
command.
Fast checks:
```sh
command -v crabbox
../crabbox/bin/crabbox --version
crabbox run --provider blacksmith-testbox --help | sed -n '1,140p'
pnpm crabbox:run -- --help | sed -n '1,140p'
../crabbox/bin/crabbox doctor
command -v blacksmith
blacksmith --version
blacksmith testbox list
@@ -403,36 +498,36 @@ Common Crabbox-only failures:
- Provider missing or old CLI: use `../crabbox/bin/crabbox` from the sibling
repo, or update/install Crabbox before retrying.
- Bad local config: pass `--provider blacksmith-testbox` plus explicit
`--blacksmith-*` flags instead of relying on `.crabbox.yaml`.
- Slug/claim confusion: use the raw `tbx_...` id, or run one-shot without
`--id`.
- Bad local config: inspect `.crabbox.yaml`, `crabbox config show`, and
`crabbox whoami`; normal OpenClaw proof should use brokered AWS without
asking for cloud keys.
- Slug/claim confusion: use the raw `cbx_...` / `tbx_...` id, or run one-shot
without `--id`.
- Sync/timing bug: add `--debug --timing-json`; capture the final JSON and the
printed Actions URL. Large sync warnings now include top source directories
by file count and a hint to update `.crabboxignore` / `sync.exclude`; inspect
those before reaching for `--force-sync-large`. Quiet rsync watchdogs and SSH
timeouts now print `next_action=` hints; follow them, usually `--full-resync`
first and a fresh lease second.
- Cleanup uncertainty: run `blacksmith testbox list` and stop only boxes you
- Cleanup uncertainty: run `crabbox list --provider aws`; for explicit
Blacksmith runs, use `blacksmith testbox list` and stop only boxes you
created.
- Testbox queued/capacity pressure: do not convert a broad changed gate or full
suite into local `OPENCLAW_LOCAL_CHECK_MODE=throttled pnpm ...`. Leave the
remote lane queued, switch to a narrower targeted local check, or stop and
report the capacity blocker.
- Testbox queued/capacity pressure: do not retry Blacksmith repeatedly. Rerun
once without `--provider` so `.crabbox.yaml` routes to brokered AWS, or report
the Blacksmith blocker if Testbox itself is the requested proof.
If Crabbox cannot dispatch, sync, attach, or stop but Blacksmith itself works,
first try the same command through the repo wrapper with `--debug` and
`--timing-json`:
If brokered AWS cannot dispatch, sync, attach, or stop, retry once with
`--debug` and `--timing-json`:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --debug --timing-json -- \
pnpm crabbox:run -- --debug --timing-json -- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed
```
Full suite:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --debug --timing-json -- \
pnpm crabbox:run -- --debug --timing-json -- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test
```
@@ -451,9 +546,10 @@ Raw Blacksmith footguns:
- Treat `blacksmith testbox list` as cleanup diagnostics, not a shared reusable
queue.
Escalate to owned AWS/Hetzner only when Blacksmith is down, quota-limited,
missing the needed environment, or owned capacity is the explicit goal. Use the
Owned Cloud Fallback section below.
Use Blacksmith only when the task is specifically about Testbox, brokered AWS
is unavailable, or an explicit comparison is needed. If Blacksmith is down or
quota-limited, do not keep probing it; stay on brokered AWS and note the
delegated-provider outage.
## Blacksmith Backend Notes
@@ -489,13 +585,14 @@ Important Blacksmith footguns:
blacksmith auth login --non-interactive --organization openclaw
```
## Owned Cloud Fallback
## Brokered AWS
Use AWS/Hetzner only when Blacksmith is down, quota-limited, missing the needed
environment, or owned capacity is explicitly the goal.
Use AWS for normal OpenClaw remote proof. The repo `.crabbox.yaml` already
selects brokered AWS, so omit `--provider` unless you are testing a different
provider deliberately.
```sh
pnpm crabbox:warmup -- --provider aws --class beast --market on-demand --idle-timeout 90m
pnpm crabbox:warmup -- --class beast --market on-demand --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
pnpm crabbox:stop -- <cbx_id-or-slug>
@@ -519,8 +616,8 @@ crabbox whoami
- If broker auth is missing, run `crabbox login --url https://crabbox.openclaw.ai --provider aws`.
- If the CLI asks for `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, or AWS
profile setup during normal OpenClaw validation, assume the agent selected
the wrong path. Use brokered `crabbox login`, `--provider blacksmith-testbox`,
or an existing brokered lease before asking the user for cloud credentials.
the wrong path. Use brokered `crabbox login` or an existing brokered lease
before asking the user for cloud credentials.
- Ask for AWS keys only for explicit direct-provider/account administration,
not for normal brokered OpenClaw proof.
- Trusted automation may still use
@@ -533,8 +630,7 @@ macOS config lives at:
```
It should include `broker.url`, `broker.token`, and usually `provider: aws`
for owned-cloud lanes. Do not let that config override the OpenClaw default
when Blacksmith proof is requested; pass `--provider blacksmith-testbox`.
for OpenClaw lanes. Let that config drive normal validation.
### Interactive Desktop / WebVNC
@@ -572,14 +668,15 @@ Use `--market spot|on-demand` only on AWS warmup/one-shot runs.
## Failure Triage
- Crabbox cannot find provider: verify `../crabbox/bin/crabbox --help` lists
`blacksmith-testbox`; update Crabbox before falling back.
the provider selected by `.crabbox.yaml`; update Crabbox before falling back.
- Hydration stuck or failed: open the printed GitHub Actions run URL and inspect
the hydration step.
- Sync failed: rerun with `--debug`; check changed-file count and whether the
checkout is dirty.
- Command failed: rerun only the failing shard/file first. Do not rerun a full
suite until the focused failure is understood.
- Cleanup uncertain: `blacksmith testbox list`; stop owned `tbx_...` leases you
- Cleanup uncertain: `crabbox list --provider aws`; for explicit Blacksmith
runs, use `blacksmith testbox list` and stop owned `tbx_...` leases you
created.
- Crabbox broken but Blacksmith works: use the direct Blacksmith fallback above,
then file/fix the Crabbox issue.

View File

@@ -0,0 +1,44 @@
---
name: discrawl
description: "Discord archive: search, sync freshness, DMs, channel slices, SQL counts, and Discrawl repo work."
metadata:
openclaw:
homepage: https://github.com/openclaw/discrawl
requires:
bins:
- discrawl
install:
- kind: go
module: github.com/openclaw/discrawl/cmd/discrawl@latest
bins:
- discrawl
---
# Discrawl
Use local Discord archive data before live Discord APIs. Check freshness for recent/current questions:
```bash
discrawl status --json
discrawl doctor
```
Refresh only when stale or asked:
```bash
discrawl sync --source wiretap
discrawl sync
```
Query with bounded slices:
```bash
DISCRAWL_NO_AUTO_UPDATE=1 discrawl search --limit 20 "query"
discrawl messages --channel '#maintainers' --days 7 --all
discrawl dms --last 20
DISCRAWL_NO_AUTO_UPDATE=1 discrawl --json sql "select count(*) from messages;"
```
Report absolute date spans, channel/DM names, counts, and known gaps. Use read-only SQL for exact counts/rankings. Never use `--unsafe --confirm` unless the user explicitly requests a reviewed DB mutation.
Boundaries: bot sync needs configured Discord bot credentials. Wiretap reads local Discord Desktop artifacts only; do not extract user tokens, call Discord as the user, or write to Discord storage. Git-share snapshots must not include secrets or `@me` DM rows.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Discrawl"
short_description: "Search local Discord archives and freshness"
default_prompt: "Use $discrawl to search local Discord archives, check freshness, inspect DMs or channel slices, and report exact date spans and source gaps."

View File

@@ -1,68 +1,50 @@
---
name: gitcrawl
description: Use gitcrawl for OpenClaw issue and PR archive search, duplicate discovery, related-thread clustering, and local GitHub mirror freshness checks.
description: "GitHub archive: issue/PR search, sync freshness, duplicate clusters, gh-shim PR status, and Gitcrawl repo work."
metadata:
openclaw:
homepage: https://github.com/openclaw/gitcrawl
requires:
bins:
- gitcrawl
install:
- kind: go
module: github.com/openclaw/gitcrawl/cmd/gitcrawl@latest
bins:
- gitcrawl
---
# Gitcrawl
Use this skill before live GitHub search when triaging OpenClaw issues or PRs.
`gitcrawl` is the local candidate-discovery layer. It is fast, includes open and closed threads, and can surface duplicate attempts, related issues, and already-landed fixes. It is not the final source of truth for comments, labels, merges, closes, or current CI.
## Default Flow
1. Check local state:
Use local GitHub issue/PR archives before live GitHub search. Check freshness first:
```bash
gitcrawl doctor --json
```
2. Read the target from the local archive:
Find candidates:
```bash
gitcrawl threads openclaw/openclaw --numbers <issue-or-pr-number> --include-closed --json
```
3. Find related candidates:
```bash
gitcrawl neighbors openclaw/openclaw --number <issue-or-pr-number> --limit 12 --json
gitcrawl search openclaw/openclaw --query "<scope or title keywords>" --mode hybrid --limit 20 --json
gitcrawl search issues "query" -R openclaw/openclaw --state open --json number,title,url
gitcrawl clusters openclaw/openclaw --sort size --min-size 5
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id>
```
4. Inspect relevant clusters:
For PR triage, start cached and go live only before mutation/merge decisions:
```bash
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
gitcrawl gh pr status <number-or-url> -R openclaw/openclaw --compact
gitcrawl gh pr view <number-or-url> -R openclaw/openclaw --json number,title,state,url,isDraft,headRef,headSha
gitcrawl gh --live pr status <number-or-url> -R openclaw/openclaw --compact
```
5. Verify anything actionable with live GitHub and the checkout:
Use live `gh` plus checkout proof before commenting, labeling, closing, reopening, merging, or filing a PR review:
```bash
gh pr view <number> --json number,title,state,mergedAt,body,files,comments,reviews,statusCheckRollup
gh issue view <number> --json number,title,state,body,comments,closedAt
```
## Freshness Rules
- Treat `gitcrawl` as stale if `doctor` shows no target thread, an old `last_sync_at`, missing embeddings for neighbor/search commands, or a clearly wrong open/closed state.
- If stale data blocks the decision, refresh the portable store first:
```bash
gitcrawl init --portable-store git@github.com:openclaw/gitcrawl-store.git --json
```
- Run expensive update commands such as `gitcrawl sync --include-comments` only when the user asked to update the local store or stale data is blocking the decision.
- The sync default is all GitHub thread states; pass `--state open`, `--state closed`, or `--state all` only when a task requires a narrower or explicit scope.
## Boundaries
- Use `gitcrawl` for candidates, clusters, and historical context.
- Use `gh`, `gh api`, and the current checkout for live state before commenting, labeling, closing, reopening, merging, or filing a PR review.
- Do not close or label based only on `gitcrawl` similarity. Require matching problem intent plus live verification.
- If `gitcrawl` is unavailable, say so and fall back to targeted `gh search` rather than blocking normal maintainer work.
Report absolute dates, repo names, issue/PR numbers, cluster ids, and source gaps. Do not close/label from similarity alone; require matching intent plus live verification.

View File

@@ -0,0 +1,44 @@
---
name: graincrawl
description: "Granola archive: search, sync freshness, notes, transcripts, panels, SQL counts, and Graincrawl repo work."
metadata:
openclaw:
homepage: https://github.com/openclaw/graincrawl
requires:
bins:
- graincrawl
install:
- kind: go
module: github.com/vincentkoc/graincrawl/cmd/graincrawl@latest
bins:
- graincrawl
---
# Graincrawl
Use local Granola archive data first. Check freshness for recent/current questions:
```bash
graincrawl doctor --json
graincrawl status --json
```
Refresh only when stale or asked:
```bash
graincrawl sync --source private-api
graincrawl sync --source desktop-cache
```
Query with bounded reads:
```bash
graincrawl search "query"
graincrawl notes --json
graincrawl note get <id>
graincrawl transcripts get <id>
graincrawl panels get <id>
graincrawl --json sql "select count(*) as notes from notes;"
```
Report absolute date spans, note titles, source gaps, and transcript/panel availability. Use read-only SQL for exact counts/rankings. Before encrypted source debugging, run explicit unlock/secrets checks; do not surprise-prompt Keychain.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Graincrawl"
short_description: "Search local Granola notes and transcripts"
default_prompt: "Use $graincrawl to search local Granola notes, transcripts, and panels, check freshness, and report exact date spans and source gaps."

View File

@@ -0,0 +1,42 @@
---
name: notcrawl
description: "Notion archive: search, sync freshness, pages/databases, Markdown exports, SQL counts, and Notcrawl repo work."
metadata:
openclaw:
homepage: https://github.com/openclaw/notcrawl
requires:
bins:
- notcrawl
install:
- kind: go
module: github.com/vincentkoc/notcrawl/cmd/notcrawl@latest
bins:
- notcrawl
---
# Notcrawl
Use local Notion archive data before browsing or live Notion API calls. Check freshness for recent/current questions:
```bash
notcrawl doctor
notcrawl status --json
```
Refresh only when stale or asked:
```bash
notcrawl sync --source desktop
notcrawl sync --source api
```
Query with bounded reads:
```bash
notcrawl search "query"
notcrawl databases
notcrawl report
notcrawl sql "select count(*) from pages;"
```
Report workspace/teamspace, page/database titles, absolute date spans, counts, and known gaps. Use read-only SQL only; never mutate the archive. API mode requires `NOTION_TOKEN`; do not assume token availability.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Notcrawl"
short_description: "Search local Notion archives and freshness"
default_prompt: "Use $notcrawl to search local Notion pages and databases, check freshness, inspect exports, and report exact date spans and source gaps."

View File

@@ -24,6 +24,36 @@ gitcrawl search openclaw/openclaw --query "<scope or title keywords>" --mode hyb
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
```
## Claim specific review targets
When a maintainer asks Codex to review, triage, fix, or land a specific OpenClaw issue/PR, check assignment before deep work.
- Identify the requesting maintainer's GitHub login. In this environment, default Peter to `steipete`; if another maintainer is clearly the requester, use that maintainer's bare login.
- Read current assignees with live `gh issue view` / `gh pr view`; `gitcrawl` is not enough for assignment state.
- If unassigned, assign the requester before deep review. This is allowed for specific requested targets; do not auto-assign broad discovery candidates or shortlists.
- If assigned to someone else, say so clearly before analysis and include assignment age:
- fresh: assigned within 6h; treat as actively owned unless user explicitly asks to continue or reassign
- stale: assigned 6h+ ago; treat as ownership hint, not a hard block; continue only with that caveat
- If assigned to requester plus others, mention co-assignees and continue.
- If assignment event time is unavailable, say `assigned, time unknown`; treat as assigned, not stale.
- Never remove or replace assignees unless explicitly asked.
Assignment time proof:
```bash
gh api "repos/openclaw/openclaw/issues/<number>/timeline" --paginate \
-H "Accept: application/vnd.github+json" \
--jq '[.[] | select(.event=="assigned") | {assignee:.assignee.login, assigner:.assigner.login, actor:.actor.login, created_at}]'
```
Use the newest `assigned` event for each current assignee. Issue timeline events expose `created_at`; GitHub GraphQL `AssignedEvent.createdAt` is also valid when REST pagination is awkward.
Claim command for issues or PRs:
```bash
gh api -X POST "repos/openclaw/openclaw/issues/<number>/assignees" -f 'assignees[]=<login>' >/dev/null
```
## Surface opener identity
- For every reviewed, triaged, closed, or landed issue/PR, show the opener's human name when available, GitHub login, and account age.
@@ -138,7 +168,9 @@ Output only qualifying candidates, with: ref, surface, proof, cause, fix sketch,
- Start every PR review with 1-3 plain sentences explaining what the change does and why it matters. Put this before `Findings`.
- Then list findings first. If none, say `No blocking findings` or `No findings`.
- Always answer: bug/behavior being fixed, PR/issue URL and affected surface, and best-fix verdict.
- Always answer: bug/behavior being fixed, PR/issue URL and affected surface, provenance for regressions when traceable, and best-fix verdict.
- For bug/regression fixes, include a compact `Provenance:` line after cause/root-cause when a bounded history pass can identify it. Use `git log -S/-G`, `git blame`, linked PRs/issues, and tests; separate author, committer/merger, and current PR author when they differ.
- Phrase provenance as `introduced by`, `made visible by`, or `carried forward by`, with confidence (`clear`, `likely`, `unknown`). If unclear, say what evidence is missing instead of guessing. For features, docs, and refactors, use `Provenance: N/A` or omit it when no broken behavior is being fixed.
- Keep summaries compact, but include enough proof that the verdict is auditable without rereading the PR.
## Read beyond the diff
@@ -160,8 +192,9 @@ Output only qualifying candidates, with: ref, surface, proof, cause, fix sketch,
- Before landing, require:
1. symptom evidence such as a repro, logs, or a failing test
2. a verified root cause in code with file/line
3. a fix that touches the implicated code path
4. a regression test when feasible, or explicit manual verification plus a reason no test was added
3. provenance for regressions when traceable by bounded git/PR history
4. a fix that touches the implicated code path
5. a regression test when feasible, or explicit manual verification plus a reason no test was added
- If the claim is unsubstantiated or likely wrong, request evidence or changes instead of merging.
- If the linked issue appears outdated or incorrect, correct triage first. Do not merge a speculative fix.
- If Crabbox/E2E proof is blocked, say exactly why and use the closest available
@@ -214,6 +247,7 @@ gh search issues --repo openclaw/openclaw --match title,body --limit 50 \
not correctness findings.
- If bot review conversations exist on your PR, address them and resolve them yourself once fixed.
- Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed.
- Before landing any PR with non-trivial code changes, run `$autoreview` until no accepted/actionable findings remain, unless equivalent manual review already covered it, the change is trivial/docs-only, or the user opts out.
- When landing or merging any PR, follow the global `/landpr` process.
- Use `scripts/committer "<msg>" <file...>` for scoped commits instead of manual `git add` and `git commit`.
- Keep commit messages concise and action-oriented.

View File

@@ -0,0 +1,90 @@
---
name: openclaw-release-ci
description: "Run, watch, debug, and summarize OpenClaw full release CI, release checks, live provider gates, install/update proofs, and release-secret preflights."
---
# OpenClaw Release CI
Use this with `$openclaw-release-maintainer` and `$openclaw-testing` when a release candidate needs full validation, install/update proof, live provider checks, or CI recovery.
## Guardrails
- No version bump, tag, npm publish, GitHub release, or release promotion without explicit operator approval.
- Validate provider secrets before dispatching expensive full release matrices.
- Do not set GitHub secrets from unvalidated 1Password candidates. If a candidate returns 401/403, leave the existing secret alone and report the exact missing provider.
- Use `$one-password` for secret reads/writes: one persistent tmux session, targeted items only, no secret output.
- Watch one parent run plus compact child summaries. Avoid broad `gh run view` polling loops; REST quota is easy to burn.
- Fetch logs only for failed or currently-blocking jobs. If quota is low, stop polling and wait for reset.
- Treat live-provider flakes separately from code failures: prove key validity, provider HTTP status, retry evidence, and exact failing lane before editing code.
## Preflight
Before full release validation:
```bash
node .agents/skills/openclaw-release-ci/scripts/verify-provider-secrets.mjs --required openai,anthropic,fireworks
gh api rate_limit --jq '.resources.core'
git status --short --branch
git rev-parse HEAD
```
If env lacks keys, use `$one-password` to inject or set them, then rerun the script. The script prints only provider status and HTTP class, never tokens.
## Dispatch
Prefer the trusted workflow on `main`, target the exact release SHA:
```bash
gh workflow run full-release-validation.yml \
--repo openclaw/openclaw \
--ref main \
-f ref=<release-sha> \
-f provider=openai \
-f mode=both \
-f release_profile=full \
-f rerun_group=all
```
Use `release_profile=stable` unless the operator explicitly asks for the broad advisory provider/media matrix. Use narrow `rerun_group` after focused fixes.
## Watch
Use the summary helper instead of repeated raw polling:
```bash
node .agents/skills/openclaw-release-ci/scripts/release-ci-summary.mjs <full-release-run-id>
```
Then watch only when useful:
```bash
gh run watch <full-release-run-id> --repo openclaw/openclaw --exit-status
```
Stop watchers before ending the turn or switching strategy.
## Failure Triage
1. Confirm parent SHA and child run IDs.
2. List failed jobs only:
```bash
gh run view <child-run-id> --repo openclaw/openclaw --json jobs \
--jq '.jobs[] | select(.conclusion=="failure" or .conclusion=="timed_out" or .conclusion=="cancelled") | [.databaseId,.name,.conclusion,.url] | @tsv'
```
3. Fetch one failed job log. If rate-limited, note reset time and avoid more REST calls.
4. For secret-looking failures, validate the provider endpoint from the same secret source before editing code.
5. For live-cache failures, inspect whether it is missing/invalid key, empty text, provider refusal, timeout, or baseline miss. Do not weaken release gates without clear provider evidence.
6. Fix narrowly, run local/changed proof, commit, push, rerun the smallest matching group.
## Evidence
Record:
- release SHA
- full parent run URL
- child run IDs and conclusions: CI, Release Checks, Plugin Prerelease, NPM Telegram
- targeted local proof commands
- provider-secret preflight result
- known gaps or unrelated failures
For lessons and recovery patterns, read `references/release-ci-notes.md`.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "OpenClaw Release CI"
short_description: "Verify and debug OpenClaw release validation runs"
default_prompt: "Use $openclaw-release-ci to preflight provider secrets, watch full release validation, summarize child runs, and triage only failing release lanes."

View File

@@ -0,0 +1,41 @@
# Release CI Notes
## What Went Wrong
- Full validation was started before all provider keys were proven valid.
- GitHub secret presence was confused with key validity.
- Repeated `gh run view` and log fetches exhausted REST quota.
- Parent run state was less useful than child run evidence.
- Live-cache failures needed structured classification: invalid key, empty provider output, timeout, or real cache regression.
- Background watchers accumulated and made interruption recovery harder.
## Better Defaults
- Run provider-secret preflight first. Require real `/models` or equivalent endpoint checks for release-blocking providers.
- Keep one watcher open. Use child summaries every few minutes, not every few seconds.
- Fetch failed-job logs only after a job reaches a terminal failing state.
- Prefer narrow `rerun_group` recovery after a focused fix.
- Leave bad secrets unset. A 401 candidate from 1Password should not overwrite GitHub.
- Make the final release evidence note durable: parent URL, child run URLs, SHA, command proof, and gaps.
## Secret Handling Pattern
- Use `$one-password`; never run broad env dumps.
- Search exact item titles or known ids.
- Validate candidates without printing values.
- Set GitHub secrets only after endpoint validation succeeds.
- After setting, verify metadata with `gh secret list`, not value output.
## Live Cache Pattern
- Empty text with token usage is a provider/output issue until proven otherwise.
- Retry lane-level mismatches once with a fresh session id.
- Keep cache baselines strict, but log enough structured usage to distinguish cache miss from response mismatch.
- If a provider key validates locally but fails in Actions, inspect whether the workflow reads the expected secret name.
## Quota-Safe GitHub Pattern
- Check `gh api rate_limit --jq '.resources.core'` before log-heavy work.
- Use one child-run listing call, then inspect failed jobs only.
- If remaining quota is low, pause until reset; do not keep polling.
- Prefer GraphQL only for metadata when REST is exhausted; logs still need REST.

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import process from "node:process";
const runId = process.argv[2];
const repo = process.env.OPENCLAW_RELEASE_REPO || "openclaw/openclaw";
if (!runId) {
console.error("usage: release-ci-summary.mjs <full-release-run-id>");
process.exit(2);
}
function gh(args) {
return execFileSync("gh", args, {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
}
function jsonGh(args) {
return JSON.parse(gh(args));
}
function rate() {
try {
return jsonGh(["api", "rate_limit"]).resources.core;
} catch {
return undefined;
}
}
const core = rate();
if (core) {
const reset = new Date(core.reset * 1000).toISOString();
console.log(`rate: remaining=${core.remaining}/${core.limit} reset=${reset}`);
if (core.remaining < 20) {
console.error("rate too low for CI summary; wait for reset before polling");
process.exit(3);
}
}
const parent = jsonGh([
"run",
"view",
runId,
"--repo",
repo,
"--json",
"status,conclusion,createdAt,headSha,url,jobs",
]);
console.log(`parent: ${runId} ${parent.status}/${parent.conclusion || "none"}`);
console.log(`sha: ${parent.headSha}`);
console.log(`url: ${parent.url}`);
for (const job of parent.jobs ?? []) {
const marker = job.conclusion || job.status;
console.log(`parent-job: ${marker} ${job.name}`);
}
const since = parent.createdAt;
const runList = gh([
"api",
`repos/${repo}/actions/runs?per_page=100`,
"--jq",
`.workflow_runs[] | select(.created_at >= "${since}") | select(.name=="CI" or .name=="OpenClaw Release Checks" or .name=="Plugin Prerelease" or .name=="NPM Telegram Beta E2E" or .name=="Full Release Validation") | [.id,.name,.status,.conclusion,.head_sha,.html_url] | @tsv`,
]).trim();
if (!runList) {
console.log("children: none found yet");
process.exit(0);
}
console.log("children:");
for (const line of runList.split("\n")) {
const [id, name, status, conclusion, sha, url] = line.split("\t");
console.log(`child: ${id} ${name} ${status}/${conclusion || "none"} sha=${sha}`);
console.log(`child-url: ${url}`);
}

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env node
import process from "node:process";
const args = new Map();
for (let index = 2; index < process.argv.length; index += 1) {
const arg = process.argv[index];
if (!arg.startsWith("--")) continue;
const [key, inlineValue] = arg.slice(2).split("=", 2);
const value = inlineValue ?? process.argv[index + 1];
if (inlineValue === undefined) index += 1;
args.set(key, value);
}
const requiredInput = String(args.get("required") ?? "openai,anthropic").trim();
const required = new Set(
(requiredInput.toLowerCase() === "none" ? "" : requiredInput)
.split(",")
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean),
);
const timeoutMs = Number(args.get("timeout-ms") ?? 10_000);
function envFirst(names) {
for (const name of names) {
const value = process.env[name]?.trim();
if (value) return { name, value };
}
return undefined;
}
async function checkProvider(id, config) {
const secret = envFirst(config.env);
if (!secret) {
return { id, ok: false, status: "missing", env: config.env.join("|") };
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const headers = config.headers(secret.value);
const response = await fetch(config.url, {
headers,
signal: controller.signal,
});
return {
id,
ok: response.ok,
status: response.ok ? "ok" : `http_${response.status}`,
env: secret.name,
};
} catch (error) {
return {
id,
ok: false,
status: error?.name === "AbortError" ? "timeout" : "error",
env: secret.name,
};
} finally {
clearTimeout(timer);
}
}
const providers = {
openai: {
env: ["OPENAI_API_KEY"],
url: "https://api.openai.com/v1/models",
headers: (token) => ({ authorization: `Bearer ${token}` }),
},
anthropic: {
env: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_TOKEN"],
url: "https://api.anthropic.com/v1/models",
headers: (token) => ({
"anthropic-version": "2023-06-01",
"x-api-key": token,
}),
},
fireworks: {
env: ["FIREWORKS_API_KEY"],
url: "https://api.fireworks.ai/inference/v1/models",
headers: (token) => ({ authorization: `Bearer ${token}` }),
},
openrouter: {
env: ["OPENROUTER_API_KEY"],
url: "https://openrouter.ai/api/v1/models",
headers: (token) => ({ authorization: `Bearer ${token}` }),
},
};
const unknown = [...required].filter((id) => !providers[id]);
if (unknown.length > 0) {
console.error(`unknown providers: ${unknown.join(",")}`);
process.exit(2);
}
const results = [];
for (const id of Object.keys(providers)) {
if (required.has(id) || envFirst(providers[id].env)) {
results.push(await checkProvider(id, providers[id]));
}
}
let failed = false;
for (const result of results) {
const requiredLabel = required.has(result.id) ? "required" : "optional";
console.log(`${result.id}: ${result.status} env=${result.env} ${requiredLabel}`);
if (required.has(result.id) && !result.ok) failed = true;
}
if (failed) {
console.error("release provider secret preflight failed");
process.exit(1);
}

View File

@@ -34,10 +34,10 @@ Supports single or multiple alerts. For multiple alerts, process in ascending or
For each alert:
1. **Identify**`fetch-alert` + `fetch-content` to get metadata and body
2. **Decide** — Agent reads the body file, identifies all secrets, produces redacted version
3. **Redact**`redact-body` for issue/PR body; skip for comments (delete directly)
2. **Decide** — Agent reads the body file, identifies whether plaintext secrets remain, and produces a redacted version only when needed
3. **Redact**`redact-body-if-needed` for issue/PR body; skip for comments (delete directly)
4. **Purge**`delete-comment` + `recreate-comment` for comments; cannot purge body history
5. **Notify**`notify` posts the right template per location type
5. **Notify**`notify` posts the right template per location type, unless the current issue/PR body is already redacted
6. **Resolve**`resolve` closes the alert
7. **Summary**`summary` prints formatted results
@@ -81,11 +81,20 @@ The `fetch-content` output includes:
The agent reads the body file from `fetch-content` output and:
1. Identifies ALL secrets in the content (there may be more than the alert flagged)
2. Replaces each secret with `[REDACTED <secret_type>]`**no partial values, no prefix/suffix**
3. Saves the redacted content to a new temp file
2. Determines whether any plaintext credential remains in the current body
3. Replaces each remaining secret with `[REDACTED <secret_type>]`**no partial values, no prefix/suffix**
4. Saves the redacted content to a new temp file
This is the only step that requires semantic understanding. Everything else is mechanical.
For `issue_body` and `pull_request_body`: if the current body has already been redacted by the author and no plaintext credential remains, **do not post a public notification comment**. Resolve the alert with a maintainer-only resolution comment such as:
```bash
node secret-scanning.mjs resolve <ALERT_NUMBER> revoked "Current issue/PR body is already redacted; no public notification posted."
```
This avoids creating a fresh public pointer to historical sensitive content.
## Step 3: Redact
### For comments (issue_comment / PR comments)
@@ -95,9 +104,11 @@ This is the only step that requires semantic understanding. Everything else is m
### For issue_body / pull_request_body
```bash
node secret-scanning.mjs redact-body <issue|pr> <NUMBER> <redacted-body-file>
node secret-scanning.mjs redact-body-if-needed <issue|pr> <NUMBER> <current-body-file> <redacted-body-file> <result-file>
```
Use the `body_file` from `fetch-content` as `<current-body-file>`. The command writes `notify_required` to `<result-file>` and only PATCHes the body when the redacted file differs from the current body.
## Step 4: Purge Edit History
### Comments — Delete and Recreate
@@ -134,10 +145,12 @@ The recreated comment should follow this format:
<redacted original content>
```
### issue_body / pull_request_body — Cannot Purge
### issue_body / pull_request_body — Cannot Purge Edit History
Editing creates an edit history revision with the pre-edit plaintext. This cannot be cleared via API.
Do not advise authors publicly to delete/recreate issues or close/reopen PRs. That can draw attention to historical content. Keep purge guidance maintainer-only.
**Output to maintainer terminal only (never in public comments):**
```
@@ -155,12 +168,13 @@ Cannot clean. Notify author to delete branch or force-push (for unmerged PRs).
## Step 5: Notify
```bash
node secret-scanning.mjs notify <TARGET> <AUTHOR> <LOCATION_TYPE> <SECRET_TYPES> [REPLY_TO_NODE_ID]
node secret-scanning.mjs notify <TARGET> <AUTHOR> <LOCATION_TYPE> <SECRET_TYPES> [REPLY_TO_NODE_ID|BODY_REDACTION_RESULT_FILE]
```
- For non-discussion types, `<TARGET>` is the issue/PR number.
- For `discussion_comment`, `<TARGET>` is the `discussion_node_id` returned by `fetch-content`.
- For reply-style `discussion_comment` locations, pass the optional `reply_to_node_id` from `fetch-content` so the notification stays in the same thread.
- For `issue_body` and `pull_request_body`, pass the `<result-file>` from `redact-body-if-needed`. The script skips notification when `notify_required` is `false` and refuses body notifications without this file.
Secret types are comma-separated: `"Discord Bot Token,Feishu App Secret"`
@@ -170,6 +184,8 @@ The script picks the right template:
- **body types**: "your issue/PR description … redacted in place"
- **commit**: "code you committed"
For `issue_body` and `pull_request_body`, only notify when the current body still contained plaintext and maintainers redacted it. If the user already redacted the current body, skip this step and resolve silently.
## Step 6: Resolve
```bash
@@ -178,7 +194,7 @@ node secret-scanning.mjs resolve <ALERT_NUMBER>
node secret-scanning.mjs resolve <ALERT_NUMBER> revoked "Custom comment"
```
Resolution is `revoked` by default. As maintainers we cannot control whether users rotate — our responsibility is to redact + notify. The `revoked` means "this secret should be considered leaked", not "I confirmed it was revoked".
Resolution is `revoked` by default. As maintainers we cannot control whether users rotate — our responsibility is to remove current plaintext exposure and notify only when public notification is useful. The `revoked` means "this secret should be considered leaked", not "I confirmed it was revoked".
## Step 7: Summary

View File

@@ -7,6 +7,7 @@ import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
const REPO = "openclaw/openclaw";
const REPO_URL = `https://github.com/${REPO}`;
@@ -50,6 +51,34 @@ function ghGraphQL(query, options = {}) {
return gh(["api", "graphql", "-f", `query=${query}`], options);
}
function isBodyLocationType(locationType) {
return locationType === "issue_body" || locationType === "pull_request_body";
}
export function decideBodyRedaction(currentBody, redactedBody) {
const bodyChanged = String(currentBody) !== String(redactedBody);
return {
body_changed: bodyChanged,
notify_required: bodyChanged,
};
}
export function loadBodyRedactionResult(locationType, resultFile) {
if (!isBodyLocationType(locationType)) {
return { notify_required: true };
}
if (!resultFile) {
fail("Body notifications require a redaction result file from redact-body-if-needed");
}
if (!fs.existsSync(resultFile)) fail(`File not found: ${resultFile}`);
const result = JSON.parse(fs.readFileSync(resultFile, "utf8"));
if (typeof result.notify_required !== "boolean") {
fail(`Invalid redaction result file: missing boolean notify_required in ${resultFile}`);
}
return result;
}
function failOnGraphQLFailure(result, message) {
if (result?.gh_failed) {
const details = (
@@ -470,6 +499,43 @@ function cmdRedactBody(kind, number, bodyFile) {
console.log(JSON.stringify({ ok: true, kind, number: Number(number) }));
}
/**
* redact-body-if-needed <issue|pr> <number> <current-body-file> <redacted-body-file> <result-file>
* PATCH only when the agent-produced redacted body differs from the current body.
*/
function cmdRedactBodyIfNeeded(kind, number, currentBodyFile, redactedBodyFile, resultFile) {
if (!kind || !number || !currentBodyFile || !redactedBodyFile || !resultFile) {
fail(
"Usage: redact-body-if-needed <issue|pr> <number> <current-body-file> <redacted-body-file> <result-file>",
);
}
if (!fs.existsSync(currentBodyFile)) fail(`File not found: ${currentBodyFile}`);
if (!fs.existsSync(redactedBodyFile)) fail(`File not found: ${redactedBodyFile}`);
const currentBody = fs.readFileSync(currentBodyFile, "utf8");
const redactedBody = fs.readFileSync(redactedBodyFile, "utf8");
const decision = decideBodyRedaction(currentBody, redactedBody);
const result = {
ok: true,
kind,
number: Number(number),
...decision,
};
if (decision.body_changed) {
const endpoint =
kind === "pr" ? `repos/${REPO}/pulls/${number}` : `repos/${REPO}/issues/${number}`;
gh(["api", endpoint, "-X", "PATCH", "-F", `body=@${redactedBodyFile}`]);
result.redacted = true;
} else {
result.redacted = false;
result.reason = "current_body_already_redacted";
}
fs.writeFileSync(resultFile, `${JSON.stringify(result, null, 2)}\n`, { mode: 0o600 });
console.log(JSON.stringify(result));
}
/**
* delete-comment <comment-id>
* Delete a comment (and all its edit history).
@@ -555,6 +621,17 @@ function cmdNotify(target, author, locationType, secretTypes, replyToNodeId) {
const types = secretTypes.split(",").map((s) => s.trim());
const typeList = types.map((t, i) => `${i + 1}. **${t}**`).join("\n");
const redactionResult = loadBodyRedactionResult(locationType, replyToNodeId);
if (isBodyLocationType(locationType) && !redactionResult.notify_required) {
console.log(
JSON.stringify({
ok: true,
skipped: true,
reason: "current_body_already_redacted",
}),
);
return;
}
let locationDesc;
let actionDesc;
@@ -581,6 +658,8 @@ function cmdNotify(target, author, locationType, secretTypes, replyToNodeId) {
}
const body = [
`> **Note:** This is an automated message sent by the OpenClaw maintainer team. **NO_REPLY.**`,
"",
`@${author} :warning: **Security Notice: Secret Leakage Detected**`,
"",
`GitHub Secret Scanning detected the following exposed secret types in ${locationDesc}:`,
@@ -756,12 +835,13 @@ function cmdSummary(jsonFile) {
// ─── Dispatch ───────────────────────────────────────────────────────────────
const [command, ...args] = process.argv.slice(2);
const args = [];
const commands = {
export const commands = {
"fetch-alert": () => cmdFetchAlert(args[0]),
"fetch-content": () => cmdFetchContent(args[0]),
"redact-body": () => cmdRedactBody(args[0], args[1], args[2]),
"redact-body-if-needed": () => cmdRedactBodyIfNeeded(args[0], args[1], args[2], args[3], args[4]),
"delete-comment": () => cmdDeleteComment(args[0]),
"delete-discussion-comment": () => cmdDeleteDiscussionComment(args[0]),
"recreate-comment": () => cmdRecreateComment(args[0], args[1]),
@@ -772,26 +852,37 @@ const commands = {
summary: () => cmdSummary(args[0]),
};
if (!command || !commands[command]) {
console.error(
[
"Usage: node secret-scanning.mjs <command> [args]",
"",
"Commands:",
" fetch-alert <number> Fetch alert metadata + locations",
" fetch-content '<location-json>' Fetch content for a location",
" redact-body <issue|pr> <n> <file> PATCH body with redacted file",
" delete-comment <comment-id> Delete a comment",
" delete-discussion-comment <node-id> Delete a discussion comment (GraphQL)",
" recreate-comment <issue-n> <file> Create replacement comment",
" recreate-discussion-comment <disc-node-id> <file> [reply-to-node-id] Create discussion comment (GraphQL)",
" notify <target> <author> <type> <types> [reply-to-node-id] Post notification",
" resolve <n> [resolution] [comment] Close alert",
" list-open List open alerts",
" summary <json-file> Print formatted summary",
].join("\n"),
);
process.exit(1);
function main(argv = process.argv.slice(2)) {
const [command, ...commandArgs] = argv;
args.length = 0;
args.push(...commandArgs);
if (!command || !commands[command]) {
console.error(
[
"Usage: node secret-scanning.mjs <command> [args]",
"",
"Commands:",
" fetch-alert <number> Fetch alert metadata + locations",
" fetch-content '<location-json>' Fetch content for a location",
" redact-body <issue|pr> <n> <file> PATCH body with redacted file",
" redact-body-if-needed <issue|pr> <n> <current-file> <redacted-file> <result-file> PATCH body only if redaction changed it",
" delete-comment <comment-id> Delete a comment",
" delete-discussion-comment <node-id> Delete a discussion comment (GraphQL)",
" recreate-comment <issue-n> <file> Create replacement comment",
" recreate-discussion-comment <disc-node-id> <file> [reply-to-node-id] Create discussion comment (GraphQL)",
" notify <target> <author> <type> <types> [reply-to-node-id|body-result-file] Post notification",
" resolve <n> [resolution] [comment] Close alert",
" list-open List open alerts",
" summary <json-file> Print formatted summary",
].join("\n"),
);
process.exit(1);
}
commands[command]();
}
commands[command]();
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main();
}

View File

@@ -19,9 +19,16 @@ or validating a change without wasting hours.
Prove the touched surface first. Do not reflexively run the whole suite.
1. Inspect the diff and classify the touched surface:
- source: `pnpm changed:lanes --json`, then `pnpm check:changed`
- tests only: `pnpm test:changed`
- one failing file: `pnpm test <path-or-filter> -- --reporter=verbose`
- normal source checkout, source change: `pnpm changed:lanes --json`, then `pnpm check:changed`
- normal source checkout, tests only: `pnpm test:changed`
- normal source checkout, one failing file: `pnpm test <path-or-filter> -- --reporter=verbose`
- Codex worktree or linked/sparse checkout, one/few explicit files: `node scripts/run-vitest.mjs <path-or-filter>`
- Codex worktree or linked/sparse checkout, changed gates or anything broad:
use the Crabbox wrapper with the provider that matches the proof surface.
For maintainer heavy `pnpm` gates, that is usually delegated Blacksmith
Testbox through Crabbox, e.g. `node scripts/crabbox-wrapper.mjs run
--provider blacksmith-testbox ... -- pnpm check:changed`. For direct AWS
Crabbox proof, omit `--provider` and let `.crabbox.yaml` choose AWS.
- workflow-only: `git diff --check`, workflow syntax/lint (`actionlint` when available)
- docs-only: `pnpm docs:list`, docs formatter/lint only if docs tooling changed or requested
2. Reproduce narrowly before fixing.
@@ -36,11 +43,24 @@ Prove the touched surface first. Do not reflexively run the whole suite.
- Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets.
- Use `scripts/committer "<msg>" <paths...>` when committing; stage only your files.
- If deps are missing, run `pnpm install`, retry once, then report the first actionable error.
- For Blacksmith Testbox proof, use Crabbox first. `pnpm crabbox:run -- --provider
blacksmith-testbox --timing-json -- <command...>` warms, claims, syncs, runs,
reports, and cleans up one-shot boxes. Reuse only an id/slug created in this
operator session; `blacksmith testbox list` is diagnostics only, not a shared
work queue.
- In a Codex worktree or linked/sparse checkout, do not run direct local
`pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, or `scripts/committer` until
you have verified pnpm will not reconcile or reinstall dependencies. Use
`node scripts/run-vitest.mjs` for tiny local proof, `node
scripts/crabbox-wrapper.mjs` for Testbox, and `git commit --no-verify` only
after the relevant remote or node-wrapper proof is already clean.
- For remote proof, use the Crabbox wrapper first, but name the actual backend.
Direct AWS Crabbox uses `provider=aws` and `cbx_...` ids. Delegated
Blacksmith Testbox through Crabbox uses `provider=blacksmith-testbox`,
`syncDelegated=true`, and `tbx_...` ids. Both satisfy "remote proof" when the
requested proof surface allows either.
- Do not infer "no Testbox is running" from plain `blacksmith testbox list`.
Use `blacksmith testbox list --all` or `blacksmith testbox status <tbx_id>`
before reporting cloud state.
- Reuse only an id/slug created in this operator session unless explicitly
coordinating with another lane. If Testbox queues, fails capacity, or cannot
allocate, report the blocker or switch to direct AWS Crabbox only when that
still proves the requested surface.
## Local Test Shortcuts
@@ -55,6 +75,14 @@ OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test <path-or-filter>
Use targeted file paths whenever possible. Avoid raw `vitest`; use the repo
`pnpm test` wrapper so project routing, workers, and setup stay correct.
When the checkout is a Codex worktree, prefer the direct node harness instead:
```bash
node scripts/run-vitest.mjs <path-or-filter>
```
That keeps the test scoped without giving pnpm a chance to run dependency
status checks or install reconciliation in a linked worktree.
## Command Semantics

View File

@@ -0,0 +1,41 @@
---
name: slacrawl
description: "Slack archive: search, sync freshness, threads/DMs, SQL counts, and Slacrawl repo work."
metadata:
openclaw:
homepage: https://github.com/openclaw/slacrawl
requires:
bins:
- slacrawl
install:
- kind: go
module: github.com/vincentkoc/slacrawl/cmd/slacrawl@latest
bins:
- slacrawl
---
# Slacrawl
Use local Slack archive data first. Check freshness for recent/current questions:
```bash
slacrawl doctor
slacrawl status --json
```
Refresh only when stale or asked:
```bash
slacrawl sync --source desktop
slacrawl sync --source api --latest-only
```
Query with bounded slices:
```bash
slacrawl search --limit 20 "query"
slacrawl messages --since 7d --limit 50
slacrawl sql "select count(*) from messages;"
```
Report workspace/channel names, absolute date spans, counts, and token/source limits. Use read-only SQL for exact counts/rankings. API sync and full thread/DM hydration require Slack tokens; do not assume they exist.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Slacrawl"
short_description: "Search local Slack archives and freshness"
default_prompt: "Use $slacrawl to search local Slack archives, check freshness, inspect channel or DM slices, and report exact date spans and token/source limits."

View File

@@ -2,10 +2,9 @@
You are Mantis running native Telegram Desktop visual proof for an OpenClaw PR.
Goal: inspect the pull request, decide the best Telegram-visible behavior to
prove, run before/after native Telegram Desktop sessions, iterate until the GIFs
are visually good, and leave a Mantis evidence manifest for the workflow to
publish.
Goal: inspect the pull request, decide whether it has an honest
Telegram-visible before/after behavior, then either run native Telegram Desktop
proof or leave a no-visual-proof manifest for the workflow to publish.
Hard limits:
@@ -16,6 +15,12 @@ Hard limits:
- Do not use fixed `/status` proof unless it genuinely proves the PR.
- Do not finish with tiny, cropped-wrong, off-bottom, or sidebar-heavy GIFs.
- Do not invent a generic proof. The proof must match the PR behavior.
- Do not force GIFs for internal-only, workflow-only, test-only, docs-only, or
otherwise non-visual PRs. A no-visual-proof manifest is a successful workflow
outcome when GIFs would be misleading, but it is not proof that the PR passed.
- Keep public-facing manifest summaries short and user-domain. Do not mention
harness internals, mock-provider limits, secret/trust boundaries, local paths,
transcript seeding, or workflow implementation details in the summary.
Inputs are provided as environment variables:
@@ -36,10 +41,54 @@ Required workflow:
1. Read `.agents/skills/telegram-crabbox-e2e-proof/SKILL.md`.
2. Inspect the PR with `gh pr view "$MANTIS_PR_NUMBER"` and
`gh pr diff "$MANTIS_PR_NUMBER"`.
3. Decide what Telegram message, mock model response, command, callback, button,
3. Decide whether the PR has a visibly reproducible Telegram Desktop
before/after. If it does not, write
`${MANTIS_OUTPUT_DIR}/mantis-evidence.json` with `comparison.pass: true`, no
artifacts, and a summary that starts with
`Mantis did not generate before/after GIFs because`. Include a short
public reason, such as `the PR changes internal session bookkeeping rather
than Telegram-visible behavior`. Use this manifest shape and do not create
worktrees or start Crabbox for this case:
```json
{
"schemaVersion": 1,
"id": "telegram-desktop-proof",
"title": "Mantis Telegram Desktop Proof",
"summary": "Mantis did not generate before/after GIFs because <reason>.",
"scenario": "telegram-desktop-proof",
"comparison": {
"baseline": {
"ref": "<BASELINE_REF>",
"sha": "<BASELINE_SHA>",
"expected": "no visible Telegram Desktop delta",
"status": "skipped"
},
"candidate": {
"ref": "<CANDIDATE_REF>",
"sha": "<CANDIDATE_SHA>",
"expected": "no visible Telegram Desktop delta",
"status": "skipped",
"fixed": true
},
"pass": true
},
"artifacts": []
}
```
If the PR appears visual but proof is blocked by Telegram Desktop session
state, authorization, credentials, Crabbox, or another capture-infrastructure
issue, do not describe it as a no-visual PR. Write a manifest with
`comparison.pass: false`, skipped lanes, no artifacts, and a summary that
starts with `Mantis could not capture Telegram Desktop proof because`. The
publisher will keep that out of PR comments so the failure stays in the
workflow logs and artifacts.
4. Decide what Telegram message, mock model response, command, callback, button,
media, or sequence best proves the PR. Use `MANTIS_INSTRUCTIONS` as extra
maintainer guidance, not as a replacement for reading the PR.
4. Create detached worktrees under
5. Create detached worktrees under
`.artifacts/qa-e2e/mantis/telegram-desktop-proof-worktrees/baseline` and
`.artifacts/qa-e2e/mantis/telegram-desktop-proof-worktrees/candidate`, then
install and build each worktree with the repo's normal `pnpm` commands.
@@ -49,7 +98,7 @@ Required workflow:
runtime commands. The candidate SUT may receive only the proof runner's
short-lived Telegram bot token, generated local config/state paths, and mock
model key needed for this isolated proof.
5. In each worktree, run the real-user Telegram Crabbox proof flow from the
6. In each worktree, run the real-user Telegram Crabbox proof flow from the
skill with `$OPENCLAW_TELEGRAM_USER_PROOF_CMD`; do not run
`pnpm qa:telegram-user:crabbox` directly. The proof command comes from the
trusted workflow checkout while the current directory controls which
@@ -59,11 +108,11 @@ Required workflow:
install, or patch replacement proof tooling during the run. Use the same
proof idea for baseline and candidate. You may iterate and rerun if the
visual result is not convincing.
6. Open Telegram Desktop directly to the newest relevant message with the
7. Open Telegram Desktop directly to the newest relevant message with the
runner `view` command before finishing each recording. Keep the chat scrolled
to the bottom so new proof messages appear in-frame.
7. Finish each session with `--preview-crop telegram-window`.
8. Build `${MANTIS_OUTPUT_DIR}/mantis-evidence.json` with:
8. Finish each session with `--preview-crop telegram-window`.
9. Build `${MANTIS_OUTPUT_DIR}/mantis-evidence.json` with:
```bash
node scripts/mantis/build-telegram-desktop-proof-evidence.mjs \
@@ -93,6 +142,10 @@ Visual acceptance:
Expected final state:
- `${MANTIS_OUTPUT_DIR}/mantis-evidence.json` exists.
- The manifest contains paired `motionPreview` artifacts labeled `Main` and
`This PR`.
- Visual proof manifests contain paired `motionPreview` artifacts labeled
`Main` and `This PR`.
- No-visual-proof manifests contain no artifacts and have `comparison.pass:
true`.
- Capture-infrastructure failure manifests contain no artifacts and have
`comparison.pass: false`.
- The worktree can be dirty only under `.artifacts/`.

6
.github/labeler.yml vendored
View File

@@ -101,7 +101,9 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/qa-lab/**"
- "qa/scenarios/**"
- "docs/concepts/qa-e2e-automation.md"
- "docs/concepts/personal-agent-benchmark-pack.md"
- "docs/channels/qa-channel.md"
"channel: signal":
- changed-files:
@@ -244,6 +246,10 @@
- "docs/gateway/security.md"
- "security/**"
"extensions: admin-http-rpc":
- changed-files:
- any-glob-to-any-file:
- "extensions/admin-http-rpc/**"
"extensions: copilot-proxy":
- changed-files:
- any-glob-to-any-file:

View File

@@ -20,6 +20,8 @@ on:
- "docs/**"
pull_request:
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]
paths-ignore:
- "CHANGELOG.md"
permissions:
contents: read
@@ -38,7 +40,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
outputs:
checkout_revision: ${{ steps.checkout_ref.outputs.sha }}
@@ -301,7 +303,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
env:
PRE_COMMIT_HOME: .cache/pre-commit-security-fast
@@ -394,7 +396,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 10
steps:
- name: Checkout
@@ -419,7 +421,7 @@ jobs:
permissions: {}
needs: [security-scm-fast, security-dependency-audit]
if: ${{ !cancelled() && always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 5
steps:
- name: Verify fast security jobs
@@ -452,7 +454,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
outputs:
channels-result: ${{ steps.built_artifact_checks.outputs['channels-result'] }}
@@ -641,6 +643,15 @@ jobs:
echo "${name}-result=${results[$name]}" >> "$GITHUB_OUTPUT"
done
failures=0
for name in channels core-support-boundary gateway-watch; do
if [ "${results[$name]}" = "failure" ]; then
echo "::error title=${name} failed::${name} failed"
failures=1
fi
done
exit "$failures"
- name: Upload gateway watch regression artifacts
if: always() && needs.preflight.outputs.run_check_additional == 'true'
uses: actions/upload-artifact@v7
@@ -655,7 +666,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast_core == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -750,7 +761,7 @@ jobs:
name: ${{ matrix.checkName }}
needs: [preflight]
if: needs.preflight.outputs.run_plugin_contracts_shards == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -828,35 +839,13 @@ jobs:
EOF
OPENCLAW_VITEST_INCLUDE_FILE="$include_file" pnpm test:contracts:plugins
checks-fast-plugin-contracts:
permissions:
contents: read
name: checks-fast-contracts-plugins
needs: [preflight, checks-fast-plugin-contracts-shard]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_plugin_contracts_shards == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify plugin contract shards
env:
SHARD_RESULT: ${{ needs.checks-fast-plugin-contracts-shard.result }}
run: |
if [ "$SHARD_RESULT" = "cancelled" ]; then
echo "Plugin contract shards were cancelled, usually because a newer commit superseded this run." >&2
exit 1
fi
if [ "$SHARD_RESULT" != "success" ]; then
echo "Plugin contract shards failed: $SHARD_RESULT" >&2
exit 1
fi
checks-fast-channel-contracts-shard:
permissions:
contents: read
name: ${{ matrix.checkName }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -934,35 +923,13 @@ jobs:
EOF
OPENCLAW_VITEST_INCLUDE_FILE="$include_file" pnpm test:contracts:channels
checks-fast-channel-contracts:
permissions:
contents: read
name: checks-fast-contracts-channels
needs: [preflight, checks-fast-channel-contracts-shard]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_fast == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify channel contract shards
env:
SHARD_RESULT: ${{ needs.checks-fast-channel-contracts-shard.result }}
run: |
if [ "$SHARD_RESULT" = "cancelled" ]; then
echo "Channel contract shards were cancelled, usually because a newer commit superseded this run." >&2
exit 1
fi
if [ "$SHARD_RESULT" != "success" ]; then
echo "Channel contract shards failed: $SHARD_RESULT" >&2
exit 1
fi
checks-fast-protocol:
permissions:
contents: read
name: "checks-fast-protocol"
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 30
steps:
- name: Checkout
@@ -1021,45 +988,13 @@ jobs:
- name: Run protocol check
run: pnpm protocol:check
checks:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, build-artifacts]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.checks_matrix) }}
steps:
- name: Verify ${{ matrix.task }} (${{ matrix.runtime }})
env:
TASK: ${{ matrix.task }}
CHANNELS_RESULT: ${{ needs.build-artifacts.outputs['channels-result'] }}
shell: bash
run: |
set -euo pipefail
case "$TASK" in
channels)
if [ "$CHANNELS_RESULT" != "success" ]; then
echo "Channel tests failed in build-artifacts: $CHANNELS_RESULT" >&2
exit 1
fi
;;
*)
echo "Unsupported checks task: $TASK" >&2
exit 1
;;
esac
checks-node-compat:
permissions:
contents: read
name: checks-node-compat-node22
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'workflow_dispatch'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
steps:
- name: Checkout
@@ -1136,7 +1071,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && (matrix.runner || 'ubuntu-24.04') || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'ubuntu-24.04') || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1240,63 +1175,6 @@ jobs:
}
EOF
checks-node-core-test-dist-shard:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, build-artifacts]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_node_core_dist == 'true' && needs.build-artifacts.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_dist_matrix) }}
steps:
- name: Verify Node test shard
env:
CORE_SUPPORT_BOUNDARY_RESULT: ${{ needs.build-artifacts.outputs['core-support-boundary-result'] }}
SHARD_NAME: ${{ matrix.shard_name }}
shell: bash
run: |
set -euo pipefail
case "$SHARD_NAME" in
core-support-boundary)
if [ "$CORE_SUPPORT_BOUNDARY_RESULT" != "success" ]; then
echo "Core support boundary shard failed in build-artifacts: $CORE_SUPPORT_BOUNDARY_RESULT" >&2
exit 1
fi
;;
*)
echo "Unsupported built-artifact shard: $SHARD_NAME" >&2
exit 1
;;
esac
checks-node-core-test:
permissions:
contents: read
name: checks-node-core
needs: [preflight, checks-node-core-test-nondist-shard, checks-node-core-test-dist-shard]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify node test shards
env:
DIST_SHARD_RESULT: ${{ needs.checks-node-core-test-dist-shard.result }}
NONDIST_SHARD_RESULT: ${{ needs.checks-node-core-test-nondist-shard.result }}
RUN_DIST_SHARDS: ${{ needs.preflight.outputs.run_checks_node_core_dist }}
RUN_NONDIST_SHARDS: ${{ needs.preflight.outputs.run_checks_node_core_nondist }}
run: |
if [ "$RUN_NONDIST_SHARDS" = "true" ] && [ "$NONDIST_SHARD_RESULT" != "success" ]; then
echo "Node non-dist test shards failed: $NONDIST_SHARD_RESULT" >&2
exit 1
fi
if [ "$RUN_DIST_SHARDS" = "true" ] && [ "$DIST_SHARD_RESULT" != "success" ]; then
echo "Node dist test shards failed: $DIST_SHARD_RESULT" >&2
exit 1
fi
# Types, lint, and format check shards.
check-shard:
permissions:
@@ -1304,7 +1182,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && matrix.runner || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && matrix.runner || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1312,7 +1190,7 @@ jobs:
include:
- check_name: check-preflight-guards
task: preflight-guards
runner: ubuntu-24.04
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-prod-types
task: prod-types
runner: blacksmith-4vcpu-ubuntu-2404
@@ -1321,16 +1199,16 @@ jobs:
runner: blacksmith-16vcpu-ubuntu-2404
- check_name: check-dependencies
task: dependencies
runner: ubuntu-24.04
runner: blacksmith-8vcpu-ubuntu-2404
- check_name: check-policy-guards
task: policy-guards
runner: ubuntu-24.04
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-test-types
task: test-types
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-strict-smoke
task: strict-smoke
runner: ubuntu-24.04
runner: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
shell: bash
@@ -1398,6 +1276,7 @@ jobs:
pnpm tool-display:check
pnpm check:host-env-policy:swift
pnpm dup:check:coverage
pnpm deps:patches:check
;;
prod-types)
pnpm tsgo:prod
@@ -1441,31 +1320,13 @@ jobs:
path: .artifacts/deadcode
if-no-files-found: ignore
check:
permissions:
contents: read
name: "check"
needs: [preflight, check-shard]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify check shards
env:
SHARD_RESULT: ${{ needs.check-shard.result }}
run: |
if [ "$SHARD_RESULT" != "success" ]; then
echo "Check shards failed: $SHARD_RESULT" >&2
exit 1
fi
check-additional-shard:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1636,59 +1497,13 @@ jobs:
exit "$failures"
check-additional:
permissions:
contents: read
name: "check-additional"
needs: [preflight, check-additional-shard, build-artifacts]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify additional check shards
env:
SHARD_RESULT: ${{ needs.check-additional-shard.result }}
BUILD_ARTIFACTS_RESULT: ${{ needs.build-artifacts.result }}
GATEWAY_RESULT: ${{ needs.build-artifacts.outputs.gateway-watch-result }}
run: |
if [ "$SHARD_RESULT" != "success" ]; then
echo "Additional check shards failed: $SHARD_RESULT" >&2
exit 1
fi
if [ "$BUILD_ARTIFACTS_RESULT" != "success" ]; then
echo "Build artifact job failed: $BUILD_ARTIFACTS_RESULT" >&2
exit 1
fi
if [ "$GATEWAY_RESULT" != "success" ]; then
echo "Gateway topology check failed: $GATEWAY_RESULT" >&2
exit 1
fi
build-smoke:
permissions:
contents: read
name: "build-smoke"
needs: [preflight, build-artifacts]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_build_smoke == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success') }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify build smoke
env:
BUILD_ARTIFACTS_RESULT: ${{ needs.build-artifacts.result }}
run: |
if [ "$BUILD_ARTIFACTS_RESULT" != "success" ]; then
echo "Build smoke checks failed in build-artifacts: $BUILD_ARTIFACTS_RESULT" >&2
exit 1
fi
# Validate docs (format, lint, broken links) only when docs files changed.
check-docs:
permissions:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_check_docs == 'true'
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- name: Checkout
@@ -1762,7 +1577,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_skills_python_job == 'true'
runs-on: ubuntu-24.04
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- name: Checkout
@@ -1794,7 +1609,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_windows == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-windows-2025' || 'windows-2025' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'windows-2025' || (github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-windows-2025' || 'windows-2025') }}
timeout-minutes: 60
env:
NODE_OPTIONS: --max-old-space-size=8192
@@ -1907,7 +1722,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_macos_node == 'true' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-6vcpu-macos-latest' || 'macos-latest' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-latest' || (github.repository == 'openclaw/openclaw' && 'blacksmith-6vcpu-macos-latest' || 'macos-latest') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1951,7 +1766,7 @@ jobs:
name: "macos-swift"
needs: [preflight]
if: needs.preflight.outputs.run_macos_swift == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-latest' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-26' || (github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-26') }}
timeout-minutes: 20
steps:
- name: Checkout
@@ -2048,7 +1863,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_android_job == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false

View File

@@ -137,8 +137,10 @@ jobs:
env:
OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_I18N_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENCLAW_CONTROL_UI_I18N_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
OPENCLAW_CONTROL_UI_I18N_PROVIDER: ${{ secrets.ANTHROPIC_API_KEY != '' && 'anthropic' || 'openai' }}
OPENCLAW_CONTROL_UI_I18N_MODEL: ${{ secrets.ANTHROPIC_API_KEY != '' && 'claude-opus-4-7' || vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
OPENCLAW_CONTROL_UI_I18N_THINKING: low
OPENCLAW_CONTROL_UI_I18N_AUTH_OPTIONAL: "1"
LOCALE: ${{ matrix.locale }}
run: node --import tsx scripts/control-ui-i18n.ts sync --locale "${LOCALE}" --write

View File

@@ -16,29 +16,37 @@ permissions:
jobs:
sync-publish-repo:
runs-on: ubuntu-latest
env:
OPENCLAW_DOCS_SYNC_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
steps:
- name: Skip publish sync without token
if: env.OPENCLAW_DOCS_SYNC_TOKEN == ''
run: echo "OPENCLAW_DOCS_SYNC_TOKEN is not configured; skipping docs publish repo sync."
- name: Checkout source repo
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Checkout ClawHub docs source
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
uses: actions/checkout@v6
with:
repository: openclaw/clawhub
path: clawhub-source
fetch-depth: 1
persist-credentials: false
token: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN || github.token }}
token: ${{ env.OPENCLAW_DOCS_SYNC_TOKEN || github.token }}
- name: Setup Node
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
uses: actions/setup-node@v6
with:
node-version: "22.18.0"
- name: Clone publish repo
env:
OPENCLAW_DOCS_SYNC_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
run: |
set -euo pipefail
for attempt in 1 2 3 4 5; do
@@ -56,6 +64,7 @@ jobs:
exit 1
- name: Sync docs into publish repo
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
run: |
clawhub_sha="$(git -C "$GITHUB_WORKSPACE/clawhub-source" rev-parse HEAD)"
node scripts/docs-sync-publish.mjs \
@@ -67,13 +76,16 @@ jobs:
--clawhub-source-sha "$clawhub_sha"
- name: Install docs MDX checker dependency
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
working-directory: publish
run: npm install --no-save --package-lock=false @mdx-js/mdx@3.1.1
- name: Check publish docs MDX
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
run: node "$GITHUB_WORKSPACE/publish/.openclaw-sync/check-docs-mdx.mjs" "$GITHUB_WORKSPACE/publish/docs"
- name: Commit publish repo sync
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
working-directory: publish
run: |
set -euo pipefail

View File

@@ -6,6 +6,7 @@ on:
paths:
- "**/*.md"
- "docs/**"
- "!CHANGELOG.md"
permissions:
contents: read

View File

@@ -297,6 +297,7 @@ jobs:
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
if [[ "$conclusion" != "success" ]]; then
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
exit 1
fi
}
@@ -396,6 +397,7 @@ jobs:
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
if [[ "$conclusion" != "success" ]]; then
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
exit 1
fi
}
@@ -504,6 +506,7 @@ jobs:
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
if [[ "$conclusion" != "success" ]]; then
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
exit 1
fi
}
@@ -635,6 +638,7 @@ jobs:
name: Run package Telegram E2E
needs: [resolve_target, prepare_release_package]
if: ${{ always() && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group) && (inputs.npm_telegram_package_spec != '' || inputs.release_package_spec != '' || (inputs.rerun_group == 'all' && inputs.release_profile == 'full')) }}
continue-on-error: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
runs-on: ubuntu-24.04
timeout-minutes: ${{ inputs.release_profile == 'full' && 120 || 60 }}
outputs:
@@ -726,6 +730,7 @@ jobs:
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
if [[ "$conclusion" != "success" ]]; then
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
exit 1
fi
summary:
@@ -735,62 +740,6 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Request private evidence update
env:
RELEASE_PRIVATE_DISPATCH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN }}
TARGET_REF: ${{ inputs.ref }}
PACKAGE_SPEC: ${{ inputs.evidence_package_spec || inputs.npm_telegram_package_spec }}
GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
run: |
set -euo pipefail
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" ]]; then
echo "Release checks were skipped by rerun group; skipping automatic private evidence update."
exit 0
fi
if [[ -z "${RELEASE_PRIVATE_DISPATCH_TOKEN// }" ]]; then
echo "OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN is not configured; skipping automatic private evidence update."
exit 0
fi
release_id="${TARGET_REF#refs/tags/}"
release_id="${release_id#v}"
if [[ "$PACKAGE_SPEC" =~ ^openclaw@(.+)$ ]]; then
release_id="${BASH_REMATCH[1]}"
fi
release_id="$(printf '%s' "$release_id" | tr '/:@ ' '----' | tr -cd 'A-Za-z0-9._-')"
if [[ -z "$release_id" ]]; then
echo "::error::Could not derive release evidence id from target ref '${TARGET_REF}'."
exit 1
fi
payload="$(
jq -cn \
--arg full_validation_run_id "$GITHUB_RUN_ID_VALUE" \
--arg release_id "$release_id" \
--arg release_ref "$TARGET_REF" \
--arg package_spec "$PACKAGE_SPEC" \
--arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed; the parent summary re-checks current child run conclusions." \
'{
event_type: "openclaw_full_release_validation_completed",
client_payload: {
full_validation_run_id: $full_validation_run_id,
release_id: $release_id,
release_ref: $release_ref,
package_spec: $package_spec,
notes: $notes
}
}'
)"
curl --fail-with-body \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${RELEASE_PRIVATE_DISPATCH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/openclaw/releases-private/dispatches \
-d "$payload"
- name: Verify child workflow results
env:
GH_TOKEN: ${{ github.token }}
@@ -935,6 +884,54 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"
}
summarize_failed_child() {
local label="$1"
local run_id="$2"
if [[ -z "${run_id// }" ]]; then
return 0
fi
local run_json status conclusion artifacts_json
run_json="$(gh run view "$run_id" --json status,conclusion,url,jobs)"
status="$(jq -r '.status' <<< "$run_json")"
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
if [[ "$status" == "completed" && "$conclusion" == "success" ]]; then
return 0
fi
{
echo
echo "### Failed child detail: ${label}"
echo
jq -r '
"- Run: " + (.url // ""),
"- Result: `" + (.status // "") + "/" + (.conclusion // "") + "`",
"",
"Failed jobs:",
(.jobs[]
| select(.conclusion != "success" and .conclusion != "skipped")
| "- `" + (.name | gsub("`"; "\\`")) + "`: `" + ((.conclusion // .status // "") | tostring) + "` " + (.url // ""))
' <<< "$run_json" || true
echo
echo "Artifacts:"
artifacts_json="$(
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100" 2>/dev/null || true
)"
if [[ -n "${artifacts_json// }" ]]; then
jq -r '
if ((.artifacts // []) | length) == 0 then
"- none"
else
(.artifacts[]
| "- `" + (.name | gsub("`"; "\\`")) + "` (" + ((.size_in_bytes // 0) | tostring) + " bytes)")
end
' <<< "$artifacts_json" || echo "- unable to list artifacts"
else
echo "- unable to list artifacts"
fi
} >> "$GITHUB_STEP_SUMMARY"
}
failed=0
append_child_overview
@@ -959,6 +956,8 @@ jobs:
if [[ "$NPM_TELEGRAM_RESULT" == "skipped" && -z "${NPM_TELEGRAM_RUN_ID// }" ]]; then
check_child "npm_telegram" "" 0 || failed=1
elif [[ "$CHILD_WORKFLOW_REF" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
check_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID" 0 || echo "::warning::npm_telegram is advisory for Tideclaw alpha validation."
else
check_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID" 1 || failed=1
fi
@@ -968,4 +967,126 @@ jobs:
summarize_child_timing "release_checks" "$RELEASE_CHECKS_RUN_ID"
summarize_child_timing "npm_telegram" "$NPM_TELEGRAM_RUN_ID"
if [[ "$failed" != "0" ]]; then
summarize_failed_child "normal_ci" "$NORMAL_CI_RUN_ID"
summarize_failed_child "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID"
summarize_failed_child "release_checks" "$RELEASE_CHECKS_RUN_ID"
summarize_failed_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID"
fi
exit "$failed"
- name: Request private evidence update
env:
RELEASE_PRIVATE_DISPATCH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN }}
TARGET_REF: ${{ inputs.ref }}
PACKAGE_SPEC: ${{ inputs.evidence_package_spec || inputs.npm_telegram_package_spec }}
GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
run: |
set -euo pipefail
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" ]]; then
echo "Release checks were skipped by rerun group; skipping automatic private evidence update."
exit 0
fi
if [[ -z "${RELEASE_PRIVATE_DISPATCH_TOKEN// }" ]]; then
echo "OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN is not configured; skipping automatic private evidence update."
exit 0
fi
release_id="${TARGET_REF#refs/tags/}"
release_id="${release_id#v}"
if [[ "$PACKAGE_SPEC" =~ ^openclaw@(.+)$ ]]; then
release_id="${BASH_REMATCH[1]}"
fi
release_id="$(printf '%s' "$release_id" | tr '/:@ ' '----' | tr -cd 'A-Za-z0-9._-')"
if [[ -z "$release_id" ]]; then
echo "::warning::Could not derive release evidence id from target ref '${TARGET_REF}'; skipping automatic private evidence update."
exit 0
fi
payload="$(
jq -cn \
--arg full_validation_run_id "$GITHUB_RUN_ID_VALUE" \
--arg release_id "$release_id" \
--arg release_ref "$TARGET_REF" \
--arg package_spec "$PACKAGE_SPEC" \
--arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed; the parent summary re-checks current child run conclusions." \
'{
event_type: "openclaw_full_release_validation_completed",
client_payload: {
full_validation_run_id: $full_validation_run_id,
release_id: $release_id,
release_ref: $release_ref,
package_spec: $package_spec,
notes: $notes
}
}'
)"
if ! curl --fail-with-body \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${RELEASE_PRIVATE_DISPATCH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/openclaw/releases-private/dispatches \
-d "$payload"; then
echo "::warning::Automatic private release evidence dispatch failed; child workflow validation remains authoritative."
fi
- name: Write release validation manifest
if: ${{ success() }}
env:
TARGET_REF: ${{ inputs.ref }}
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RERUN_GROUP: ${{ inputs.rerun_group }}
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'full' }}
NORMAL_CI_RUN_ID: ${{ needs.normal_ci.outputs.run_id }}
PLUGIN_PRERELEASE_RUN_ID: ${{ needs.plugin_prerelease.outputs.run_id }}
RELEASE_CHECKS_RUN_ID: ${{ needs.release_checks.outputs.run_id }}
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
run: |
set -euo pipefail
manifest_dir="${RUNNER_TEMP}/full-release-validation"
mkdir -p "$manifest_dir"
jq -n \
--arg workflowName "Full Release Validation" \
--arg runId "$GITHUB_RUN_ID" \
--arg runAttempt "$GITHUB_RUN_ATTEMPT" \
--arg workflowRef "$GITHUB_REF_NAME" \
--arg targetRef "$TARGET_REF" \
--arg targetSha "$TARGET_SHA" \
--arg releaseProfile "$RELEASE_PROFILE" \
--arg rerunGroup "$RERUN_GROUP" \
--arg runReleaseSoak "$RUN_RELEASE_SOAK" \
--arg normalCiRunId "$NORMAL_CI_RUN_ID" \
--arg pluginPrereleaseRunId "$PLUGIN_PRERELEASE_RUN_ID" \
--arg releaseChecksRunId "$RELEASE_CHECKS_RUN_ID" \
--arg npmTelegramRunId "$NPM_TELEGRAM_RUN_ID" \
'{
version: 1,
workflowName: $workflowName,
runId: $runId,
runAttempt: $runAttempt,
workflowRef: $workflowRef,
targetRef: $targetRef,
targetSha: $targetSha,
releaseProfile: $releaseProfile,
rerunGroup: $rerunGroup,
runReleaseSoak: $runReleaseSoak,
childRuns: {
normalCi: $normalCiRunId,
pluginPrerelease: $pluginPrereleaseRunId,
releaseChecks: $releaseChecksRunId,
npmTelegram: $npmTelegramRunId
}
}' > "${manifest_dir}/full-release-validation-manifest.json"
- name: Upload release validation manifest
if: ${{ success() }}
uses: actions/upload-artifact@v7
with:
name: full-release-validation-${{ github.run_id }}
path: ${{ runner.temp }}/full-release-validation
if-no-files-found: error

View File

@@ -100,7 +100,7 @@ jobs:
install-smoke-fast:
needs: [preflight]
if: needs.preflight.outputs.run_fast_install_smoke == 'true' && needs.preflight.outputs.run_full_install_smoke != 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
@@ -208,7 +208,7 @@ jobs:
root_dockerfile_image:
needs: [preflight]
if: needs.preflight.outputs.run_full_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
outputs:
image_ref: ${{ steps.image.outputs.image_ref }}
env:
@@ -284,7 +284,7 @@ jobs:
qr_package_install_smoke:
needs: [preflight]
if: needs.preflight.outputs.run_full_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- name: Checkout CLI
uses: actions/checkout@v6
@@ -299,7 +299,7 @@ jobs:
root_dockerfile_smokes:
needs: [preflight, root_dockerfile_image]
if: needs.preflight.outputs.run_full_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- name: Checkout CLI
uses: actions/checkout@v6
@@ -401,7 +401,7 @@ jobs:
installer_smoke:
needs: [preflight, root_dockerfile_image]
if: needs.preflight.outputs.run_full_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
@@ -471,7 +471,7 @@ jobs:
bun_global_install_smoke:
needs: [preflight, root_dockerfile_image]
if: needs.preflight.outputs.run_full_install_smoke == 'true' && needs.preflight.outputs.run_bun_global_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- name: Checkout CLI
uses: actions/checkout@v6
@@ -505,7 +505,7 @@ jobs:
docker-e2e-fast:
needs: [preflight]
if: needs.preflight.outputs.run_fast_install_smoke == 'true' || needs.preflight.outputs.run_full_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 12
env:
DOCKER_BUILD_SUMMARY: "false"

View File

@@ -33,8 +33,11 @@ jobs:
authorize_actor:
name: Authorize workflow actor
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
@@ -48,14 +51,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
validate_selected_ref:
name: Validate selected ref
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
selected_revision: ${{ steps.validate.outputs.selected_revision }}

View File

@@ -21,7 +21,7 @@ on:
type: string
permissions:
contents: write
contents: read
issues: write
pull-requests: write
@@ -46,15 +46,17 @@ jobs:
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(
contains(github.event.comment.body, '@Mantis') ||
contains(github.event.comment.body, '@mantis') ||
contains(github.event.comment.body, '/mantis')
contains(github.event.comment.body, '@openclaw-mantis') ||
contains(github.event.comment.body, '/openclaw-mantis')
)
)
}}
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
@@ -68,14 +70,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
resolve_request:
name: Resolve Mantis request
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
baseline_ref: ${{ steps.resolve.outputs.baseline_ref }}
@@ -121,7 +127,7 @@ jobs:
const normalized = body.toLowerCase();
const requested =
(normalized.includes("@mantis") || normalized.includes("/mantis")) &&
(normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
normalized.includes("discord") &&
normalized.includes("status") &&
normalized.includes("reaction");
@@ -342,8 +348,8 @@ jobs:
--repo-root "$repo_root" \
--output-dir "$output_dir" \
--provider-mode live-frontier \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--model openai/gpt-5.5 \
--alt-model openai/gpt-5.5 \
--fast \
--credential-source convex \
--credential-role ci \
@@ -538,7 +544,6 @@ jobs:
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
@@ -546,9 +551,15 @@ jobs:
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' }}
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
run: |
set -euo pipefail
@@ -562,3 +573,44 @@ jobs:
--artifact-url "$ARTIFACT_URL" \
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--request-source "$REQUEST_SOURCE"
clear_issue_comment_reaction:
name: Clear Mantis command reaction
needs: [resolve_request, validate_refs, run_status_reactions]
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- name: Remove workflow eyes reaction
uses: actions/github-script@v8
with:
script: |
const { owner, repo } = context.repo;
const commentId = context.payload.comment?.id;
if (!commentId) {
core.info("No issue comment id found; skipping reaction cleanup.");
return;
}
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
owner,
repo,
comment_id: commentId,
per_page: 100,
});
const eyes = reactions.filter(
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
);
for (const reaction of eyes) {
await github.rest.reactions.deleteForIssueComment({
owner,
repo,
comment_id: commentId,
reaction_id: reaction.id,
});
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
}
if (eyes.length === 0) {
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
}

View File

@@ -21,7 +21,7 @@ on:
type: string
permissions:
contents: write
contents: read
issues: write
pull-requests: write
@@ -46,15 +46,17 @@ jobs:
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(
contains(github.event.comment.body, '@Mantis') ||
contains(github.event.comment.body, '@mantis') ||
contains(github.event.comment.body, '/mantis')
contains(github.event.comment.body, '@openclaw-mantis') ||
contains(github.event.comment.body, '/openclaw-mantis')
)
)
}}
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
@@ -68,14 +70,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
resolve_request:
name: Resolve Mantis request
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
baseline_ref: ${{ steps.resolve.outputs.baseline_ref }}
@@ -121,7 +127,7 @@ jobs:
const normalized = body.toLowerCase();
const requested =
(normalized.includes("@mantis") || normalized.includes("/mantis")) &&
(normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
normalized.includes("discord") &&
normalized.includes("thread") &&
(normalized.includes("attachment") ||
@@ -546,7 +552,6 @@ jobs:
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
@@ -554,9 +559,15 @@ jobs:
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' }}
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
run: |
set -euo pipefail
@@ -584,3 +595,44 @@ jobs:
run: |
echo "Mantis comparison failed." >&2
exit 1
clear_issue_comment_reaction:
name: Clear Mantis command reaction
needs: [resolve_request, validate_candidate, run_thread_attachment]
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- name: Remove workflow eyes reaction
uses: actions/github-script@v8
with:
script: |
const { owner, repo } = context.repo;
const commentId = context.payload.comment?.id;
if (!commentId) {
core.info("No issue comment id found; skipping reaction cleanup.");
return;
}
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
owner,
repo,
comment_id: commentId,
per_page: 100,
});
const eyes = reactions.filter(
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
);
for (const reaction of eyes) {
await github.rest.reactions.deleteForIssueComment({
owner,
repo,
comment_id: commentId,
reaction_id: reaction.id,
});
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
}
if (eyes.length === 0) {
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
}

View File

@@ -44,7 +44,7 @@ on:
- prehydrated
permissions:
contents: write
contents: read
issues: write
pull-requests: write
@@ -64,8 +64,11 @@ jobs:
authorize_actor:
name: Authorize workflow actor
runs-on: ubuntu-24.04
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
@@ -79,14 +82,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
validate_ref:
name: Validate candidate ref
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: ubuntu-24.04
outputs:
candidate_revision: ${{ steps.validate.outputs.candidate_revision }}
@@ -274,8 +281,8 @@ jobs:
--credential-role ci \
--provider-mode live-frontier \
--hydrate-mode "$HYDRATE_MODE" \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--model openai/gpt-5.5 \
--alt-model openai/gpt-5.5 \
--fast \
--scenario "$SCENARIO_ID" \
"${keep_args[@]}" \
@@ -368,7 +375,6 @@ jobs:
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
@@ -376,9 +382,15 @@ jobs:
if: ${{ always() && inputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' && steps.upload_artifact.outputs.artifact-url != '' }}
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
TARGET_PR: ${{ inputs.pr_number }}
ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: workflow_dispatch
TARGET_PR: ${{ inputs.pr_number }}
shell: bash
run: |
set -euo pipefail

View File

@@ -3,6 +3,8 @@ name: Mantis Telegram Desktop Proof
on:
issue_comment:
types: [created]
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned Mantis label trigger; trusted base workflow validates refs before checkout/use
types: [labeled]
workflow_dispatch:
inputs:
pr_number:
@@ -25,10 +27,18 @@ on:
description: Optional existing Crabbox desktop lease id or slug to reuse
required: false
type: string
publish_artifact_name:
description: Optional existing proof artifact name to publish without recapturing
required: false
type: string
publish_run_id:
description: Workflow run id that owns publish_artifact_name; required with publish_artifact_name
required: false
type: string
permissions:
actions: read
contents: write
contents: read
issues: write
pull-requests: write
@@ -47,6 +57,11 @@ jobs:
if: >-
${{
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'pull_request_target' &&
github.event.action == 'labeled' &&
github.event.label.name == 'mantis: telegram-visible-proof'
) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
@@ -58,11 +73,20 @@ jobs:
)
}}
runs-on: ubuntu-24.04
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
if (context.eventName === "pull_request_target") {
core.info(`Accepted Mantis label trigger from ${context.actor}.`);
core.setOutput("authorized", "true");
return;
}
const allowed = new Set(["admin", "maintain", "write"]);
const { owner, repo } = context.repo;
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
@@ -73,14 +97,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
resolve_request:
name: Resolve Mantis request
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: ubuntu-24.04
outputs:
baseline_ref: ${{ steps.resolve.outputs.baseline_ref }}
@@ -88,8 +116,11 @@ jobs:
crabbox_provider: ${{ steps.resolve.outputs.crabbox_provider }}
instructions: ${{ steps.resolve.outputs.instructions }}
lease_id: ${{ steps.resolve.outputs.lease_id }}
publish_artifact_name: ${{ steps.resolve.outputs.publish_artifact_name }}
publish_run_id: ${{ steps.resolve.outputs.publish_run_id }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
request_source: ${{ steps.resolve.outputs.request_source }}
should_run: ${{ steps.resolve.outputs.should_run }}
steps:
- name: Resolve refs and target PR
id: resolve
@@ -105,31 +136,70 @@ jobs:
const inputs = context.payload.inputs ?? {};
const prNumber =
eventName === "workflow_dispatch" ? inputs.pr_number : String(context.payload.issue?.number ?? "");
eventName === "workflow_dispatch"
? inputs.pr_number
: eventName === "pull_request_target"
? String(context.payload.pull_request?.number ?? "")
: String(context.payload.issue?.number ?? "");
if (!prNumber) {
core.setFailed("Mantis Telegram desktop proof requires a pull request.");
return;
}
const body =
eventName === "workflow_dispatch"
? inputs.instructions || ""
: eventName === "issue_comment"
? context.payload.comment?.body || ""
: "";
if (eventName === "issue_comment") {
const normalized = body.toLowerCase();
const requestedDesktopProof =
(normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
(normalized.includes("desktop proof") ||
normalized.includes("desktop-proof") ||
normalized.includes("telegram desktop") ||
normalized.includes("native telegram") ||
normalized.includes("visible proof") ||
normalized.includes("visible-proof") ||
normalized.includes("telegram-visible-proof"));
if (!requestedDesktopProof) {
core.notice("Comment mentioned Mantis but did not request Telegram desktop proof.");
setOutput("should_run", "false");
setOutput("baseline_ref", "");
setOutput("candidate_ref", "");
setOutput("pr_number", "");
setOutput("instructions", "");
setOutput("crabbox_provider", "");
setOutput("lease_id", "");
setOutput("publish_artifact_name", "");
setOutput("publish_run_id", "");
setOutput("request_source", "unsupported_issue_comment");
return;
}
}
const { owner, repo } = context.repo;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: Number(prNumber),
});
const body = eventName === "workflow_dispatch" ? inputs.instructions || "" : context.payload.comment?.body || "";
const provider = inputs.crabbox_provider || "aws";
if (!["aws", "hetzner"].includes(provider)) {
core.setFailed(`Unsupported Crabbox provider for Mantis Telegram desktop proof: ${provider}`);
return;
}
setOutput("should_run", "true");
setOutput("baseline_ref", pr.base.sha);
setOutput("candidate_ref", pr.head.sha);
setOutput("pr_number", String(pr.number));
setOutput("instructions", body);
setOutput("crabbox_provider", provider);
setOutput("lease_id", inputs.crabbox_lease_id || "");
setOutput("publish_artifact_name", inputs.publish_artifact_name || "");
setOutput("publish_run_id", inputs.publish_run_id || "");
setOutput("request_source", eventName);
if (eventName === "issue_comment") {
@@ -144,6 +214,7 @@ jobs:
validate_refs:
name: Validate selected refs
needs: resolve_request
if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name == ''
runs-on: ubuntu-24.04
outputs:
baseline_revision: ${{ steps.validate.outputs.baseline_revision }}
@@ -222,6 +293,7 @@ jobs:
run_telegram_desktop_proof:
name: Run agentic native Telegram proof
needs: [resolve_request, validate_refs]
if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name == ''
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 360
environment: qa-live-shared
@@ -380,11 +452,13 @@ jobs:
openai-api-key: ${{ secrets.OPENCLAW_MANTIS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
prompt-file: .github/codex/prompts/mantis-telegram-desktop-proof.md
model: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
effort: high
effort: medium
sandbox: danger-full-access
codex-args: '["-c","service_tier=\"fast\""]'
codex-home: /tmp/mantis-codex-home-${{ github.run_id }}
safety-strategy: unprivileged-user
codex-user: codex
allow-bot-users: clawsweeper[bot]
- name: Inspect Mantis evidence manifest
id: inspect
@@ -421,7 +495,6 @@ jobs:
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
@@ -430,6 +503,12 @@ jobs:
env:
ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
@@ -460,3 +539,133 @@ jobs:
run: |
echo "Mantis Telegram desktop proof failed: comparison=${COMPARISON_STATUS:-unset}." >&2
exit 1
publish_existing_telegram_desktop_proof:
name: Publish existing native Telegram proof
needs: resolve_request
if: needs.resolve_request.outputs.should_run == 'true' && needs.resolve_request.outputs.publish_artifact_name != ''
runs-on: ubuntu-24.04
environment: qa-live-shared
steps:
- name: Checkout harness ref
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Download existing proof artifact
env:
GH_TOKEN: ${{ github.token }}
PUBLISH_ARTIFACT_NAME: ${{ needs.resolve_request.outputs.publish_artifact_name }}
PUBLISH_RUN_ID: ${{ needs.resolve_request.outputs.publish_run_id }}
shell: bash
run: |
set -euo pipefail
if [[ -z "${PUBLISH_RUN_ID:-}" ]]; then
echo "publish_run_id is required when publish_artifact_name is set." >&2
exit 1
fi
run_id="$PUBLISH_RUN_ID"
gh run download "$run_id" \
--repo "$GITHUB_REPOSITORY" \
--name "$PUBLISH_ARTIFACT_NAME" \
--dir "$MANTIS_OUTPUT_DIR"
artifacts_json="$(
gh api \
-H "Accept: application/vnd.github+json" \
"repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts"
)"
artifact_id="$(jq -r --arg name "$PUBLISH_ARTIFACT_NAME" '.artifacts[] | select(.name == $name) | .id' <<<"$artifacts_json" | head -n 1)"
if [[ -z "$artifact_id" || "$artifact_id" == "null" ]]; then
echo "Could not resolve artifact id for '${PUBLISH_ARTIFACT_NAME}' in run ${run_id}." >&2
exit 1
fi
echo "PUBLISH_RUN_ID=${run_id}" >> "$GITHUB_ENV"
echo "PUBLISH_ARTIFACT_URL=https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts/${artifact_id}" >> "$GITHUB_ENV"
- name: Create Mantis GitHub App token
id: mantis_app_token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-issues: write
permission-pull-requests: write
- name: Comment PR with inline QA evidence
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
run: |
set -euo pipefail
root="$MANTIS_OUTPUT_DIR"
if [[ ! -f "$root/mantis-evidence.json" ]]; then
echo "Downloaded artifact does not contain ${root}/mantis-evidence.json." >&2
exit 1
fi
node scripts/mantis/publish-pr-evidence.mjs \
--manifest "$root/mantis-evidence.json" \
--target-pr "$TARGET_PR" \
--artifact-root "mantis/telegram-desktop/pr-${TARGET_PR}/published-${PUBLISH_RUN_ID}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
--marker "<!-- mantis-telegram-desktop-proof -->" \
--artifact-url "$PUBLISH_ARTIFACT_URL" \
--run-url "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${PUBLISH_RUN_ID}" \
--request-source "$REQUEST_SOURCE"
clear_issue_comment_reaction:
name: Clear Mantis command reaction
needs: [resolve_request, validate_refs, run_telegram_desktop_proof]
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- name: Remove workflow eyes reaction
uses: actions/github-script@v8
with:
script: |
const { owner, repo } = context.repo;
const commentId = context.payload.comment?.id;
if (!commentId) {
core.info("No issue comment id found; skipping reaction cleanup.");
return;
}
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
owner,
repo,
comment_id: commentId,
per_page: 100,
});
const eyes = reactions.filter(
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
);
for (const reaction of eyes) {
await github.rest.reactions.deleteForIssueComment({
owner,
repo,
comment_id: commentId,
reaction_id: reaction.id,
});
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
}
if (eyes.length === 0) {
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
}

View File

@@ -34,7 +34,7 @@ on:
permissions:
actions: read
contents: write
contents: read
issues: write
pull-requests: write
@@ -56,15 +56,17 @@ jobs:
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(
contains(github.event.comment.body, '@Mantis') ||
contains(github.event.comment.body, '@mantis') ||
contains(github.event.comment.body, '/mantis')
contains(github.event.comment.body, '@openclaw-mantis') ||
contains(github.event.comment.body, '/openclaw-mantis')
)
)
}}
runs-on: ubuntu-24.04
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
@@ -78,14 +80,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
resolve_request:
name: Resolve Mantis request
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: ubuntu-24.04
outputs:
candidate_ref: ${{ steps.resolve.outputs.candidate_ref }}
@@ -133,9 +139,18 @@ jobs:
}
const normalized = body.toLowerCase();
const requestedDesktopProof =
normalized.includes("desktop proof") ||
normalized.includes("desktop-proof") ||
normalized.includes("telegram desktop") ||
normalized.includes("native telegram") ||
normalized.includes("visible proof") ||
normalized.includes("visible-proof") ||
normalized.includes("telegram-visible-proof");
const requested =
(normalized.includes("@mantis") || normalized.includes("/mantis")) &&
normalized.includes("telegram");
(normalized.includes("@openclaw-mantis") || normalized.includes("/openclaw-mantis")) &&
normalized.includes("telegram") &&
!requestedDesktopProof;
if (!requested) {
core.notice("Comment mentioned Mantis but did not request Telegram live QA.");
setOutput("should_run", "false");
@@ -379,7 +394,7 @@ jobs:
output_rel=".artifacts/qa-e2e/mantis/telegram-live"
root="$candidate_repo/$output_rel"
echo "output_dir=${root}" >> "$GITHUB_OUTPUT"
model="${OPENCLAW_CI_OPENAI_MODEL:-openai/gpt-5.4}"
model="${OPENCLAW_CI_OPENAI_MODEL:-openai/gpt-5.5}"
scenario_args=()
if [[ -n "${SCENARIO_INPUT// }" ]]; then
@@ -480,7 +495,6 @@ jobs:
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
@@ -488,9 +502,15 @@ jobs:
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' }}
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
MANTIS_ARTIFACT_R2_ACCESS_KEY_ID: ${{ secrets.MANTIS_ARTIFACT_R2_ACCESS_KEY_ID }}
MANTIS_ARTIFACT_R2_BUCKET: openclaw-crabbox-artifacts
MANTIS_ARTIFACT_R2_ENDPOINT: ${{ vars.MANTIS_ARTIFACT_R2_ENDPOINT }}
MANTIS_ARTIFACT_R2_PUBLIC_BASE_URL: https://artifacts.openclaw.ai
MANTIS_ARTIFACT_R2_REGION: auto
MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY: ${{ secrets.MANTIS_ARTIFACT_R2_SECRET_ACCESS_KEY }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
shell: bash
run: |
set -euo pipefail
@@ -520,3 +540,44 @@ jobs:
run: |
echo "Mantis Telegram live failed: comparison=${COMPARISON_STATUS:-unset} telegram_exit=${TELEGRAM_EXIT:-unset}." >&2
exit 1
clear_issue_comment_reaction:
name: Clear Mantis command reaction
needs: [resolve_request, validate_ref, run_telegram_live]
if: ${{ always() && github.event_name == 'issue_comment' && needs.resolve_request.outputs.request_source == 'issue_comment' }}
runs-on: ubuntu-24.04
permissions:
issues: write
steps:
- name: Remove workflow eyes reaction
uses: actions/github-script@v8
with:
script: |
const { owner, repo } = context.repo;
const commentId = context.payload.comment?.id;
if (!commentId) {
core.info("No issue comment id found; skipping reaction cleanup.");
return;
}
const reactions = await github.paginate(github.rest.reactions.listForIssueComment, {
owner,
repo,
comment_id: commentId,
per_page: 100,
});
const eyes = reactions.filter(
(reaction) => reaction.content === "eyes" && reaction.user?.login === "github-actions[bot]",
);
for (const reaction of eyes) {
await github.rest.reactions.deleteForIssueComment({
owner,
repo,
comment_id: commentId,
reaction_id: reaction.id,
});
core.info(`Removed eyes reaction ${reaction.id} from comment ${commentId}.`);
}
if (eyes.length === 0) {
core.info(`No workflow eyes reaction found on comment ${commentId}.`);
}

View File

@@ -40,8 +40,18 @@ on:
description: Optional comma-separated Telegram scenario ids
required: false
type: string
advisory:
description: Treat package Telegram failures as advisory for the caller
required: false
default: false
type: boolean
workflow_call:
inputs:
advisory:
description: Treat package Telegram failures as advisory for the caller
required: false
default: false
type: boolean
package_spec:
description: Published OpenClaw package spec to test when no artifact is supplied
required: true
@@ -100,6 +110,7 @@ jobs:
run_package_telegram_e2e:
name: Run package Telegram E2E
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
timeout-minutes: 60
environment: qa-live-shared
permissions:

View File

@@ -86,8 +86,18 @@ on:
required: false
default: ""
type: string
advisory:
description: Treat failures as advisory for the caller
required: false
default: false
type: boolean
workflow_call:
inputs:
advisory:
description: Treat failures as advisory for the caller
required: false
default: false
type: boolean
ref:
description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
required: true
@@ -186,11 +196,12 @@ env:
PNPM_VERSION: "11.0.8"
OPENCLAW_REPOSITORY: openclaw/openclaw
TSX_VERSION: "4.21.0"
OPENCLAW_CROSS_OS_OPENAI_MODEL: ${{ inputs.openai_model || vars.OPENCLAW_CROSS_OS_OPENAI_MODEL || 'openai/gpt-5.4' }}
OPENCLAW_CROSS_OS_OPENAI_MODEL: ${{ inputs.openai_model || vars.OPENCLAW_CROSS_OS_OPENAI_MODEL || 'openai/gpt-5.5' }}
jobs:
prepare:
runs-on: ubuntu-24.04
continue-on-error: ${{ inputs.advisory }}
outputs:
baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }}
baseline_spec: ${{ steps.baseline.outputs.value }}
@@ -513,6 +524,7 @@ jobs:
cross_os_release_checks:
name: "${{ matrix.display_name }} / ${{ matrix.suite_label }}"
needs: prepare
continue-on-error: ${{ inputs.advisory }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}

View File

@@ -97,8 +97,18 @@ on:
- beta
- stable
- full
advisory:
description: Treat failures as advisory for the caller
required: false
default: false
type: boolean
workflow_call:
inputs:
advisory:
description: Treat failures as advisory for the caller
required: false
default: false
type: boolean
ref:
description: Ref, tag, or SHA to validate
required: true
@@ -455,7 +465,8 @@ jobs:
validate_release_live_cache:
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'live-cache')
runs-on: blacksmith-8vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: 20
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -505,7 +516,8 @@ jobs:
validate_repo_e2e:
needs: validate_selected_ref
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
runs-on: blacksmith-8vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
env:
OPENCLAW_VITEST_MAX_WORKERS: "2"
@@ -534,7 +546,8 @@ jobs:
validate_special_e2e:
needs: validate_selected_ref
if: inputs.include_repo_e2e && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e')
runs-on: blacksmith-8vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -608,7 +621,8 @@ jobs:
needs: [validate_selected_ref, prepare_docker_e2e_image]
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (${{ matrix.label }})
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -876,7 +890,8 @@ jobs:
plan_docker_lane_groups:
needs: validate_selected_ref
if: inputs.docker_lanes != ''
runs-on: blacksmith-4vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 5
outputs:
groups_json: ${{ steps.groups.outputs.groups_json }}
@@ -903,7 +918,8 @@ jobs:
needs: [validate_selected_ref, prepare_docker_e2e_image, plan_docker_lane_groups]
if: inputs.docker_lanes != ''
name: Docker E2E targeted lanes (${{ matrix.group.label }})
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1112,7 +1128,8 @@ jobs:
needs: [validate_selected_ref, prepare_docker_e2e_image]
if: inputs.include_openwebui && !inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (openwebui)
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -1239,7 +1256,8 @@ jobs:
prepare_docker_e2e_image:
needs: validate_selected_ref
if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != ''
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
permissions:
actions: read
@@ -1483,7 +1501,8 @@ jobs:
prepare_live_test_image:
needs: validate_selected_ref
if: inputs.include_live_suites && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-') || startsWith(inputs.live_suite_filter, 'docker-live-models'))
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
permissions:
contents: read
@@ -1556,7 +1575,8 @@ jobs:
name: Docker live models (${{ matrix.provider_label }})
needs: [validate_selected_ref, prepare_live_test_image]
if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
strategy:
fail-fast: false
@@ -1708,7 +1728,8 @@ jobs:
name: Docker live models (selected providers)
needs: [validate_selected_ref, prepare_live_test_image]
if: inputs.include_live_suites && inputs.live_model_providers != '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -1883,7 +1904,8 @@ jobs:
validate_live_provider_suites:
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || (startsWith(inputs.live_suite_filter, 'native-live-') && !startsWith(inputs.live_suite_filter, 'native-live-extensions-media') && inputs.live_suite_filter != 'native-live-extensions-a-k'))
runs-on: blacksmith-8vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -1911,7 +1933,7 @@ jobs:
- suite_id: native-live-src-gateway-profiles-anthropic-opus
suite_group: native-live-src-gateway-profiles-anthropic
label: Native live gateway profiles Anthropic Opus
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-opus-4-7,anthropic/claude-opus-4-6 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-opus-4-7 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 30
profile_env_only: false
advisory: true
@@ -2154,27 +2176,11 @@ jobs:
fi
case "${{ matrix.suite_id }}" in
live-cli-backend-docker)
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
# Keep the release-blocking CI lane on Codex API-key auth. The
# staged auth-file path remains supported for local maintainer
# reruns, but it can hang on stale subscription/session state in
# an otherwise healthy release run.
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
# Replace the staged config.toml with a minimal CI-safe config so
# the repo stays trusted for MCP/tool use without inheriting
# maintainer-local provider/profile overrides that do not exist
# inside CI.
# Codex's workspace-write sandbox relies on user namespaces that
# this Docker lane does not provide, so run Codex unsandboxed
# inside the already-isolated container to keep MCP cron/tool
# execution representative instead of failing on nested sandbox
# setup.
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
;;
live-codex-harness-docker)
# Keep CI on the API-key path for now. The staged Codex auth secret
@@ -2220,7 +2226,8 @@ jobs:
name: Docker live suites (${{ matrix.label }})
needs: [validate_selected_ref, prepare_live_test_image]
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-'))
runs-on: blacksmith-32vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -2395,14 +2402,11 @@ jobs:
fi
case "${{ matrix.suite_id }}" in
live-cli-backend-docker)
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
;;
live-codex-harness-docker)
echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV"
@@ -2442,7 +2446,8 @@ jobs:
name: Live media suites (${{ matrix.label }})
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'native-live-extensions-media') || inputs.live_suite_filter == 'native-live-extensions-a-k')
runs-on: blacksmith-8vcpu-ubuntu-2404
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
container:
image: ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04
credentials:

View File

@@ -16,6 +16,10 @@ on:
description: Existing successful preflight workflow run id to promote without rebuilding
required: false
type: string
full_release_validation_run_id:
description: Successful Full Release Validation run id for this tag/SHA, required for real publish
required: false
type: string
npm_dist_tag:
description: npm dist-tag to publish to
required: true
@@ -84,6 +88,28 @@ jobs:
ref: ${{ inputs.tag }}
fetch-depth: 0
- name: Validate Tideclaw alpha preflight target
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
env:
RELEASE_REF: ${{ inputs.tag }}
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_REF}" == *"-alpha."* && ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "Tideclaw alpha preflight runs must target an alpha prerelease tag or SHA." >&2
exit 1
fi
if [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
echo "Tideclaw alpha preflight runs must run from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2
exit 1
fi
alpha_branch="${WORKFLOW_REF#refs/heads/}"
git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}"
if ! git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then
echo "Alpha preflight target must be reachable from ${alpha_branch}." >&2
exit 1
fi
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
@@ -187,7 +213,7 @@ jobs:
id: packed_tarball
env:
OPENCLAW_PREPACK_PREPARED: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_REF: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
DEPENDENCY_EVIDENCE_DIR: ${{ steps.dependency_evidence.outputs.dir }}
run: |
@@ -255,6 +281,11 @@ jobs:
fi
RELEASE_SHA="$(git rev-parse HEAD)"
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
RELEASE_TAG="v${PACKAGE_VERSION}"
else
RELEASE_TAG="${RELEASE_REF}"
fi
TARBALL_NAME="$(basename "$PACK_PATH")"
TARBALL_SHA256="$(sha256sum "$PACK_PATH" | awk '{print $1}')"
ARTIFACT_DIR="$RUNNER_TEMP/openclaw-npm-preflight"
@@ -286,6 +317,21 @@ jobs:
);
NODE
echo "dir=$ARTIFACT_DIR" >> "$GITHUB_OUTPUT"
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
- name: Verify prepared npm tarball install
env:
PREFLIGHT_ARTIFACT_DIR: ${{ steps.packed_tarball.outputs.dir }}
run: |
set -euo pipefail
TARBALL_PATH="$(find "$PREFLIGHT_ARTIFACT_DIR" -maxdepth 1 -type f -name '*.tgz' -print | sort | tail -n 1)"
if [[ -z "$TARBALL_PATH" ]]; then
echo "Prepared preflight tarball not found." >&2
ls -la "$PREFLIGHT_ARTIFACT_DIR" >&2 || true
exit 1
fi
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
node --import tsx scripts/openclaw-npm-prepublish-verify.ts "$TARBALL_PATH" "$PACKAGE_VERSION"
- name: Upload dependency release evidence
uses: actions/upload-artifact@v7
@@ -294,6 +340,14 @@ jobs:
path: ${{ steps.dependency_evidence.outputs.dir }}
if-no-files-found: error
- name: Upload dependency release evidence tag alias
if: ${{ steps.packed_tarball.outputs.release_tag != inputs.tag }}
uses: actions/upload-artifact@v7
with:
name: openclaw-release-dependency-evidence-${{ steps.packed_tarball.outputs.release_tag }}
path: ${{ steps.dependency_evidence.outputs.dir }}
if-no-files-found: error
- name: Upload prepared npm publish bundle
uses: actions/upload-artifact@v7
with:
@@ -301,31 +355,50 @@ jobs:
path: ${{ steps.packed_tarball.outputs.dir }}
if-no-files-found: error
- name: Upload prepared npm publish bundle tag alias
if: ${{ steps.packed_tarball.outputs.release_tag != inputs.tag }}
uses: actions/upload-artifact@v7
with:
name: openclaw-npm-preflight-${{ steps.packed_tarball.outputs.release_tag }}
path: ${{ steps.packed_tarball.outputs.dir }}
if-no-files-found: error
validate_publish_request:
if: ${{ !inputs.preflight_only }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Require main or release workflow ref for publish
- name: Require trusted workflow ref for publish
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
echo "Real publish runs must be dispatched from main or release/YYYY.M.D. Use preflight_only=true for other branch validation."
tideclaw_alpha_publish=false
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
tideclaw_alpha_publish=true
fi
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ "${tideclaw_alpha_publish}" != "true" ]]; then
echo "Real publish runs must be dispatched from main, release/YYYY.M.D, or a Tideclaw alpha branch for alpha prereleases. Use preflight_only=true for other branch validation."
exit 1
fi
- name: Require preflight artifact promotion on real publish
env:
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
run: |
set -euo pipefail
if [[ -z "${PREFLIGHT_RUN_ID}" ]]; then
echo "Real publish requires preflight_run_id from a successful npm preflight run." >&2
exit 1
fi
if [[ -z "${FULL_RELEASE_VALIDATION_RUN_ID}" ]]; then
echo "Real publish requires full_release_validation_run_id from a successful Full Release Validation run." >&2
exit 1
fi
publish_openclaw_npm:
# KEEP THE REAL RELEASE/PUBLISH PATH ON A GITHUB-HOSTED RUNNER.
@@ -364,6 +437,28 @@ jobs:
ref: refs/tags/${{ inputs.tag }}
fetch-depth: 0
- name: Validate Tideclaw alpha publish target
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
env:
RELEASE_TAG: ${{ inputs.tag }}
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" == *"-alpha."* ]]; then
echo "Tideclaw alpha publish runs must target an alpha prerelease tag." >&2
exit 1
fi
if [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
echo "Tideclaw alpha publish runs must run from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2
exit 1
fi
alpha_branch="${WORKFLOW_REF#refs/heads/}"
git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}"
if ! git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then
echo "Alpha publish tag must be reachable from ${alpha_branch}." >&2
exit 1
fi
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
@@ -393,13 +488,64 @@ jobs:
RUN_JSON="$(gh run view "$PREFLIGHT_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,event,conclusion,url)"
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "OpenClaw NPM Release"], ["headBranch", process.env.EXPECTED_PREFLIGHT_BRANCH], ["event", "workflow_dispatch"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced npm preflight run ${process.env.PREFLIGHT_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using npm preflight run ${process.env.PREFLIGHT_RUN_ID}: ${run.url}`);'
- name: Verify full release validation run metadata
env:
GH_TOKEN: ${{ github.token }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
EXPECTED_WORKFLOW_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
RUN_JSON="$(gh run view "$FULL_RELEASE_VALIDATION_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,event,status,conclusion,url)"
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "Full Release Validation"], ["headBranch", process.env.EXPECTED_WORKFLOW_BRANCH], ["event", "workflow_dispatch"], ["status", "completed"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced full release validation run ${process.env.FULL_RELEASE_VALIDATION_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using full release validation run ${process.env.FULL_RELEASE_VALIDATION_RUN_ID}: ${run.url}`);'
- name: Download prepared npm tarball
env:
GH_TOKEN: ${{ github.token }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
download_preflight_artifact() {
local preferred_name fallback_name
preferred_name="openclaw-npm-preflight-${RELEASE_TAG}"
rm -rf preflight-tarball
mkdir -p preflight-tarball
if gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "${preferred_name}" \
--dir preflight-tarball; then
echo "Downloaded ${preferred_name}."
return 0
fi
echo "::warning::${preferred_name} not found; checking run artifacts for a single compatible preflight artifact."
mapfile -t matches < <(gh api -X GET "repos/${GITHUB_REPOSITORY}/actions/runs/${PREFLIGHT_RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.expired != true) | .name' |
grep '^openclaw-npm-preflight-' || true)
if [[ "${#matches[@]}" != "1" ]]; then
echo "Expected ${preferred_name}, or exactly one openclaw-npm-preflight-* fallback artifact in run ${PREFLIGHT_RUN_ID}." >&2
printf 'Available preflight candidates:\n' >&2
printf -- '- %s\n' "${matches[@]:-<none>}" >&2
exit 1
fi
fallback_name="${matches[0]}"
gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "${fallback_name}" \
--dir preflight-tarball
echo "Downloaded fallback preflight artifact ${fallback_name}."
}
download_preflight_artifact
- name: Download full release validation manifest
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: preflight-tarball
name: full-release-validation-${{ inputs.full_release_validation_run_id }}
path: full-release-validation
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
run-id: ${{ inputs.full_release_validation_run_id }}
github-token: ${{ github.token }}
- name: Validate release tag and package metadata
@@ -458,6 +604,32 @@ jobs:
exit 1
fi
- name: Verify full release validation target
run: |
set -euo pipefail
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
MANIFEST_FILE="full-release-validation/full-release-validation-manifest.json"
if [[ ! -f "$MANIFEST_FILE" ]]; then
echo "Full release validation manifest is missing." >&2
ls -la full-release-validation >&2 || true
exit 1
fi
WORKFLOW_NAME="$(jq -r '.workflowName // ""' "$MANIFEST_FILE")"
TARGET_SHA="$(jq -r '.targetSha // ""' "$MANIFEST_FILE")"
RERUN_GROUP="$(jq -r '.rerunGroup // ""' "$MANIFEST_FILE")"
if [[ "$WORKFLOW_NAME" != "Full Release Validation" ]]; then
echo "Full release validation manifest workflow mismatch: $WORKFLOW_NAME" >&2
exit 1
fi
if [[ "$TARGET_SHA" != "$EXPECTED_RELEASE_SHA" ]]; then
echo "Full release validation target SHA mismatch: expected $EXPECTED_RELEASE_SHA, got $TARGET_SHA" >&2
exit 1
fi
if [[ "$RERUN_GROUP" != "all" ]]; then
echo "Full release validation must run rerun_group=all before npm publish; got $RERUN_GROUP" >&2
exit 1
fi
- name: Resolve publish tarball
id: publish_tarball
run: |

View File

@@ -30,8 +30,8 @@ on:
required: false
default: false
type: boolean
live_gpt54:
description: Run the live OpenAI GPT 5.4 agent-turn lane
live_openai_candidate:
description: Run the live OpenAI GPT 5.5 agent-turn lane
required: false
default: false
type: boolean
@@ -57,7 +57,7 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
OCM_VERSION: v0.2.15
KOVA_REPOSITORY: openclaw/Kova
PERFORMANCE_MODEL_ID: gpt-5.4
PERFORMANCE_MODEL_ID: gpt-5.5
jobs:
kova:
@@ -82,8 +82,8 @@ jobs:
deep_profile: "true"
live: "false"
include_filters: "scenario:fresh-install scenario:gateway-performance scenario:agent-cold-warm-message"
- lane: live-gpt54
title: Kova live OpenAI GPT 5.4 agent turn
- lane: live-openai-candidate
title: Kova live OpenAI GPT 5.5 agent turn
auth: live
repeat: "1"
deep_profile: "false"
@@ -119,9 +119,9 @@ jobs:
run_lane=false
reason="deep_profile input is false"
fi
if [[ "$LANE_ID" == "live-gpt54" && "${{ github.event_name }}" != "schedule" && "${{ inputs.live_gpt54 || 'false' }}" != "true" ]]; then
if [[ "$LANE_ID" == "live-openai-candidate" && "${{ github.event_name }}" != "schedule" && "${{ inputs.live_openai_candidate || 'false' }}" != "true" ]]; then
run_lane=false
reason="live_gpt54 input is false"
reason="live_openai_candidate input is false"
fi
echo "run=$run_lane" >> "$GITHUB_OUTPUT"
if [[ "$run_lane" != "true" ]]; then
@@ -200,7 +200,7 @@ jobs:
chmod 0755 "$HOME/.local/bin/kova"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Pin Kova OpenAI model to GPT 5.4
- name: Pin Kova OpenAI model to GPT 5.5
if: steps.lane.outputs.run == 'true'
shell: bash
run: |
@@ -244,7 +244,7 @@ jobs:
run: |
set -euo pipefail
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
echo "OPENAI_API_KEY is not configured; live GPT 5.4 lane will be skipped." >> "$GITHUB_STEP_SUMMARY"
echo "OPENAI_API_KEY is not configured; live GPT 5.5 lane will be skipped." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
kova setup --ci --json
@@ -489,9 +489,7 @@ jobs:
reports_root=".artifacts/clawgrit-reports"
mkdir -p "$reports_root"
git -C "$reports_root" init -b main
git -C "$reports_root" remote add origin https://github.com/openclaw/clawgrit-reports.git
auth_header="$(printf 'x-access-token:%s' "$CLAWGRIT_REPORTS_TOKEN" | base64 -w0)"
git -C "$reports_root" config http.https://github.com/.extraheader "AUTHORIZATION: basic ${auth_header}"
git -C "$reports_root" remote add origin "https://x-access-token:${CLAWGRIT_REPORTS_TOKEN}@github.com/openclaw/clawgrit-reports.git"
if git -C "$reports_root" ls-remote --exit-code --heads origin main >/dev/null 2>&1; then
git -C "$reports_root" fetch --depth=1 origin main
git -C "$reports_root" checkout -B main FETCH_HEAD
@@ -501,10 +499,13 @@ jobs:
- name: Publish to clawgrit reports
if: ${{ steps.kova.outputs.report_json != '' && steps.clawgrit.outputs.present == 'true' }}
env:
CLAWGRIT_REPORTS_TOKEN: ${{ secrets.CLAWGRIT_REPORTS_TOKEN }}
shell: bash
run: |
set -euo pipefail
reports_root=".artifacts/clawgrit-reports"
git -C "$reports_root" remote set-url origin "https://x-access-token:${CLAWGRIT_REPORTS_TOKEN}@github.com/openclaw/clawgrit-reports.git"
ref_slug="$(printf '%s' "${TESTED_REF}" | tr -c 'A-Za-z0-9._-' '-')"
run_slug="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
dest="${reports_root}/openclaw-performance/${ref_slug}/${run_slug}/${LANE_ID}"
@@ -560,7 +561,14 @@ jobs:
exit 0
fi
if [[ "$attempt" == "5" ]]; then
exit 1
{
echo "### Clawgrit report publish skipped"
echo
echo "Kova artifacts were uploaded, but publishing the optional clawgrit report failed after ${attempt} attempts."
echo "Check the \`CLAWGRIT_REPORTS_TOKEN\` secret or the reports repository permissions."
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::Kova artifacts uploaded, but optional clawgrit report publish failed after ${attempt} attempts."
exit 0
fi
sleep $((attempt * 2))
git -C "$reports_root" fetch --depth=1 origin main

View File

@@ -113,13 +113,21 @@ jobs:
release_package_spec: ${{ steps.inputs.outputs.release_package_spec }}
package_acceptance_package_spec: ${{ steps.inputs.outputs.package_acceptance_package_spec }}
steps:
- name: Require main or release workflow ref for release checks
- name: Require trusted workflow ref for release checks
env:
RELEASE_REF: ${{ inputs.ref }}
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release-ci/[0-9a-f]{12}-[0-9]+$ ]]; then
echo "Release checks must be dispatched from main, release/YYYY.M.D, or a Full Release Validation release-ci/<sha>-<timestamp> ref so workflow logic and secrets stay controlled." >&2
tideclaw_alpha_check=false
if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
workflow_branch="${WORKFLOW_REF#refs/heads/}"
if [[ "${RELEASE_REF}" == *"-alpha."* || "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ || "${RELEASE_REF}" == "${workflow_branch}" || "${RELEASE_REF}" == "refs/heads/${workflow_branch}" ]]; then
tideclaw_alpha_check=true
fi
fi
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release-ci/[0-9a-f]{12}-[0-9]+$ ]] && [[ "${tideclaw_alpha_check}" != "true" ]]; then
echo "Release checks must be dispatched from main, release/YYYY.M.D, a Full Release Validation release-ci/<sha>-<timestamp> ref, or a Tideclaw alpha branch for alpha prereleases." >&2
exit 1
fi
@@ -219,6 +227,25 @@ jobs:
fi
echo "sha=${selected_sha,,}" >> "$GITHUB_OUTPUT"
- name: Validate Tideclaw alpha target matches workflow branch
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
working-directory: workflow
env:
SELECTED_SHA: ${{ steps.ref.outputs.sha }}
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
echo "Tideclaw alpha release checks must run from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2
exit 1
fi
alpha_branch="${WORKFLOW_REF#refs/heads/}"
git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}"
if ! git merge-base --is-ancestor "${SELECTED_SHA}" "refs/remotes/origin/${alpha_branch}"; then
echo "Alpha release target ${SELECTED_SHA} must be reachable from ${alpha_branch}." >&2
exit 1
fi
- name: Capture selected inputs
id: inputs
env:
@@ -507,6 +534,7 @@ jobs:
permissions: read-all
uses: ./.github/workflows/openclaw-cross-os-release-checks-reusable.yml
with:
advisory: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
ref: ${{ needs.resolve_target.outputs.revision }}
provider: ${{ needs.resolve_target.outputs.provider }}
mode: ${{ needs.resolve_target.outputs.mode }}
@@ -515,7 +543,10 @@ jobs:
candidate_file_name: openclaw-current.tgz
candidate_version: ${{ needs.prepare_release_package.outputs.package_version }}
candidate_source_sha: ${{ needs.prepare_release_package.outputs.source_sha }}
openai_model: openai/gpt-5.4
openai_model: openai/gpt-5.5
ubuntu_runner: ubuntu-24.04
windows_runner: windows-2025
macos_runner: macos-26
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -535,6 +566,7 @@ jobs:
pull-requests: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
advisory: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
ref: ${{ needs.resolve_target.outputs.revision }}
include_repo_e2e: true
include_release_path_suites: false
@@ -600,6 +632,7 @@ jobs:
pull-requests: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
advisory: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
ref: ${{ needs.resolve_target.outputs.revision }}
include_repo_e2e: false
include_release_path_suites: true
@@ -620,6 +653,7 @@ jobs:
pull-requests: read
uses: ./.github/workflows/package-acceptance.yml
with:
advisory: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
workflow_ref: ${{ github.ref_name }}
source: ${{ (needs.resolve_target.outputs.package_acceptance_package_spec != '' || needs.resolve_target.outputs.release_package_spec != '') && 'npm' || 'artifact' }}
package_spec: ${{ needs.resolve_target.outputs.package_acceptance_package_spec || needs.resolve_target.outputs.release_package_spec || 'openclaw@beta' }}
@@ -630,7 +664,7 @@ jobs:
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}
published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}
telegram_mode: mock-openai
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-status-command,telegram-other-bot-command-gating,telegram-context-command,telegram-mentioned-message-reply,telegram-reply-chain-exact-marker,telegram-stream-final-single-message,telegram-long-final-reuses-preview,telegram-mention-gating
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-status-command,telegram-other-bot-command-gating,telegram-context-command,telegram-mentioned-message-reply,telegram-long-final-reuses-preview,telegram-mention-gating
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -685,7 +719,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
@@ -694,9 +728,9 @@ jobs:
matrix:
include:
- lane: candidate
output_dir: gpt54
output_dir: openai-candidate
- lane: baseline
output_dir: opus46
output_dir: anthropic-baseline
env:
QA_PARITY_CONCURRENCY: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
@@ -742,7 +776,7 @@ jobs:
;;
baseline)
model="anthropic/claude-opus-4-7"
alt_model="anthropic/claude-sonnet-4-7"
alt_model="anthropic/claude-sonnet-4-6"
;;
*)
echo "Unknown QA parity lane: ${QA_PARITY_LANE}" >&2
@@ -772,7 +806,7 @@ jobs:
needs: [resolve_target, qa_lab_parity_lane_release_checks]
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
@@ -811,8 +845,8 @@ jobs:
run: |
pnpm openclaw qa parity-report \
--repo-root . \
--candidate-summary .artifacts/qa-e2e/gpt54/qa-suite-summary.json \
--baseline-summary .artifacts/qa-e2e/opus46/qa-suite-summary.json \
--candidate-summary .artifacts/qa-e2e/openai-candidate/qa-suite-summary.json \
--baseline-summary .artifacts/qa-e2e/anthropic-baseline/qa-suite-summary.json \
--candidate-label "${OPENCLAW_CI_OPENAI_MODEL}" \
--baseline-label anthropic/claude-opus-4-7 \
--output-dir .artifacts/qa-e2e/parity
@@ -826,12 +860,158 @@ jobs:
retention-days: 14
if-no-files-found: warn
qa_lab_runtime_parity_release_checks:
name: Run QA Lab runtime parity lane
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 30
permissions:
contents: read
env:
QA_PARITY_CONCURRENCY: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
OPENAI_API_KEY: ""
ANTHROPIC_API_KEY: ""
OPENCLAW_LIVE_OPENAI_KEY: ""
OPENCLAW_LIVE_ANTHROPIC_KEY: ""
OPENCLAW_LIVE_GEMINI_KEY: ""
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run runtime parity lane
id: runtime_parity_lane
run: |
set -euo pipefail
pnpm openclaw qa suite \
--provider-mode mock-openai \
--parity-pack agentic \
--concurrency "${QA_PARITY_CONCURRENCY}" \
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
--alt-model "openai/gpt-5.5-alt" \
--runtime-pair pi,codex \
--output-dir ".artifacts/qa-e2e/runtime-parity"
- name: Run standard runtime parity tier
if: ${{ always() && steps.runtime_parity_lane.outcome != 'skipped' && steps.runtime_parity_lane.outcome != 'cancelled' }}
run: |
set -euo pipefail
pnpm openclaw qa suite \
--provider-mode mock-openai \
--runtime-parity-tier standard \
--concurrency "${QA_PARITY_CONCURRENCY}" \
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
--alt-model "openai/gpt-5.5-alt" \
--runtime-pair pi,codex \
--output-dir ".artifacts/qa-e2e/runtime-parity-standard"
- name: Generate runtime parity report
if: always()
run: |
set -euo pipefail
pnpm openclaw qa parity-report \
--repo-root . \
--runtime-axis \
--summary .artifacts/qa-e2e/runtime-parity/qa-suite-summary.json \
--output-dir .artifacts/qa-e2e/runtime-parity-report
- name: Generate standard runtime parity report
if: always()
run: |
set -euo pipefail
pnpm openclaw qa parity-report \
--repo-root . \
--runtime-axis \
--summary .artifacts/qa-e2e/runtime-parity-standard/qa-suite-summary.json \
--output-dir .artifacts/qa-e2e/runtime-parity-standard-report
- name: Upload runtime parity artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: release-qa-runtime-parity-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
retention-days: 14
if-no-files-found: warn
runtime_tool_coverage_release_checks:
name: Enforce QA Lab runtime tool coverage
needs: [resolve_target, qa_lab_runtime_parity_release_checks]
if: always() && contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
actions: read
env:
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Download runtime parity artifacts
uses: actions/download-artifact@v4
with:
name: release-qa-runtime-parity-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
- name: Enforce standard runtime tool coverage
run: |
set -euo pipefail
pnpm openclaw qa coverage \
--repo-root . \
--tools \
--summary .artifacts/qa-e2e/runtime-parity-standard/qa-suite-summary.json \
--output .artifacts/qa-e2e/runtime-parity-standard-report/qa-runtime-tool-coverage-report.md
- name: Upload runtime tool coverage artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: release-qa-runtime-tool-coverage-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/runtime-parity-standard-report/
retention-days: 14
if-no-files-found: warn
qa_live_matrix_release_checks:
name: Run QA Lab live Matrix lane
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_matrix_enabled == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
@@ -911,7 +1091,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_telegram_enabled == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
@@ -1007,7 +1187,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_discord_enabled == 'true' && vars.OPENCLAW_RELEASE_QA_DISCORD_LIVE_CI_ENABLED == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
@@ -1103,8 +1283,11 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_whatsapp_enabled == 'true' && vars.OPENCLAW_RELEASE_QA_WHATSAPP_LIVE_CI_ENABLED == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 60
concurrency:
group: qa-live-whatsapp-shared
cancel-in-progress: false
permissions:
contents: read
pull-requests: read
@@ -1199,7 +1382,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_slack_enabled == 'true' && vars.OPENCLAW_RELEASE_QA_SLACK_LIVE_CI_ENABLED == 'true'
continue-on-error: true
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
@@ -1301,6 +1484,8 @@ jobs:
- package_acceptance_release_checks
- qa_lab_parity_lane_release_checks
- qa_lab_parity_report_release_checks
- qa_lab_runtime_parity_release_checks
- runtime_tool_coverage_release_checks
- qa_live_matrix_release_checks
- qa_live_telegram_release_checks
- qa_live_discord_release_checks
@@ -1313,9 +1498,15 @@ jobs:
steps:
- name: Verify release check results
shell: bash
env:
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
failed=0
tideclaw_alpha=false
if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
tideclaw_alpha=true
fi
for item in \
"prepare_release_package=${{ needs.prepare_release_package.result }}" \
"install_smoke_release_checks=${{ needs.install_smoke_release_checks.result }}" \
@@ -1325,6 +1516,8 @@ jobs:
"package_acceptance_release_checks=${{ needs.package_acceptance_release_checks.result }}" \
"qa_lab_parity_lane_release_checks=${{ needs.qa_lab_parity_lane_release_checks.result }}" \
"qa_lab_parity_report_release_checks=${{ needs.qa_lab_parity_report_release_checks.result }}" \
"qa_lab_runtime_parity_release_checks=${{ needs.qa_lab_runtime_parity_release_checks.result }}" \
"runtime_tool_coverage_release_checks=${{ needs.runtime_tool_coverage_release_checks.result }}" \
"qa_live_matrix_release_checks=${{ needs.qa_live_matrix_release_checks.result }}" \
"qa_live_telegram_release_checks=${{ needs.qa_live_telegram_release_checks.result }}" \
"qa_live_discord_release_checks=${{ needs.qa_live_discord_release_checks.result }}" \
@@ -1334,6 +1527,15 @@ jobs:
name="${item%%=*}"
result="${item#*=}"
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
if [[ "$tideclaw_alpha" == "true" ]]; then
case "$name" in
prepare_release_package|install_smoke_release_checks) ;;
*)
echo "::warning::${name} ended with ${result}; Tideclaw alpha treats non-package-safety release-check lanes as advisory."
continue
;;
esac
fi
if [[ "$name" == qa_* ]]; then
echo "::warning::${name} ended with ${result}; QA release-check lanes are advisory and do not block release validation."
continue

View File

@@ -11,6 +11,14 @@ on:
description: Successful OpenClaw NPM Release preflight run id, required when publish_openclaw_npm=true
required: false
type: string
full_release_validation_run_id:
description: Successful Full Release Validation run id for this tag/SHA, required when publish_openclaw_npm=true
required: false
type: string
npm_telegram_run_id:
description: Optional successful NPM Telegram Beta E2E run id to include in final release evidence
required: false
type: string
npm_dist_tag:
description: npm dist-tag for the OpenClaw package
required: true
@@ -72,11 +80,13 @@ jobs:
timeout-minutes: 20
outputs:
sha: ${{ steps.manifest.outputs.sha || steps.ref.outputs.sha }}
preflight_artifact_name: ${{ steps.preflight_artifact.outputs.name }}
steps:
- name: Validate inputs
env:
RELEASE_TAG: ${{ inputs.tag }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
PLUGINS: ${{ inputs.plugins }}
@@ -101,8 +111,16 @@ jobs:
echo "publish_openclaw_npm=true requires preflight_run_id." >&2
exit 1
fi
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${WORKFLOW_REF}" != "refs/heads/main" && ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
echo "publish_openclaw_npm=true requires dispatching this workflow from main or release/YYYY.M.D." >&2
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && -z "${FULL_RELEASE_VALIDATION_RUN_ID}" ]]; then
echo "publish_openclaw_npm=true requires full_release_validation_run_id." >&2
exit 1
fi
tideclaw_alpha_publish=false
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
tideclaw_alpha_publish=true
fi
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${WORKFLOW_REF}" != "refs/heads/main" && ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ && "${tideclaw_alpha_publish}" != "true" ]]; then
echo "publish_openclaw_npm=true requires dispatching this workflow from main, release/YYYY.M.D, or a Tideclaw alpha branch for alpha prereleases." >&2
exit 1
fi
if [[ "${PLUGIN_PUBLISH_SCOPE}" == "selected" && -z "${PLUGINS}" ]]; then
@@ -122,13 +140,52 @@ jobs:
esac
- name: Download OpenClaw npm preflight manifest
id: preflight_artifact
if: ${{ inputs.publish_openclaw_npm }}
env:
GH_TOKEN: ${{ github.token }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
preferred_name="openclaw-npm-preflight-${RELEASE_TAG}"
preflight_dir="${RUNNER_TEMP}/openclaw-npm-preflight-manifest"
rm -rf "${preflight_dir}"
mkdir -p "${preflight_dir}"
if gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "${preferred_name}" \
--dir "${preflight_dir}"; then
echo "name=${preferred_name}" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "::warning::${preferred_name} not found; checking run artifacts for a single compatible preflight artifact."
mapfile -t matches < <(gh api -X GET "repos/${GITHUB_REPOSITORY}/actions/runs/${PREFLIGHT_RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.expired != true) | .name' |
grep '^openclaw-npm-preflight-' || true)
if [[ "${#matches[@]}" != "1" ]]; then
echo "Expected ${preferred_name}, or exactly one openclaw-npm-preflight-* fallback artifact in run ${PREFLIGHT_RUN_ID}." >&2
printf 'Available preflight candidates:\n' >&2
printf -- '- %s\n' "${matches[@]:-<none>}" >&2
exit 1
fi
fallback_name="${matches[0]}"
gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "${fallback_name}" \
--dir "${preflight_dir}"
echo "name=${fallback_name}" >> "$GITHUB_OUTPUT"
- name: Download full release validation manifest
if: ${{ inputs.publish_openclaw_npm }}
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: ${{ runner.temp }}/openclaw-npm-preflight-manifest
name: full-release-validation-${{ inputs.full_release_validation_run_id }}
path: ${{ runner.temp }}/full-release-validation-manifest
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
run-id: ${{ inputs.full_release_validation_run_id }}
github-token: ${{ github.token }}
- name: Checkout release tag
@@ -186,7 +243,50 @@ jobs:
fi
echo "sha=$release_sha" >> "$GITHUB_OUTPUT"
- name: Validate release tag is reachable from main or release branch
- name: Validate full release validation manifest
if: ${{ inputs.publish_openclaw_npm }}
env:
GH_TOKEN: ${{ github.token }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
EXPECTED_SHA: ${{ steps.ref.outputs.sha }}
EXPECTED_RELEASE_PROFILE: ${{ inputs.release_profile }}
EXPECTED_WORKFLOW_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
RUN_JSON="$(gh run view "$FULL_RELEASE_VALIDATION_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,event,status,conclusion,url)"
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "Full Release Validation"], ["headBranch", process.env.EXPECTED_WORKFLOW_BRANCH], ["event", "workflow_dispatch"], ["status", "completed"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced full release validation run ${process.env.FULL_RELEASE_VALIDATION_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using full release validation run ${process.env.FULL_RELEASE_VALIDATION_RUN_ID}: ${run.url}`);'
manifest="${RUNNER_TEMP}/full-release-validation-manifest/full-release-validation-manifest.json"
if [[ ! -f "$manifest" ]]; then
echo "Full release validation manifest is missing." >&2
ls -la "${RUNNER_TEMP}/full-release-validation-manifest" >&2 || true
exit 1
fi
workflow_name="$(jq -r '.workflowName // ""' "$manifest")"
target_sha="$(jq -r '.targetSha // ""' "$manifest")"
release_profile="$(jq -r '.releaseProfile // ""' "$manifest")"
rerun_group="$(jq -r '.rerunGroup // ""' "$manifest")"
if [[ "$workflow_name" != "Full Release Validation" ]]; then
echo "Full release validation manifest workflow mismatch: $workflow_name" >&2
exit 1
fi
if [[ "$target_sha" != "$EXPECTED_SHA" ]]; then
echo "Full release validation target SHA mismatch: expected $EXPECTED_SHA, got $target_sha" >&2
exit 1
fi
if [[ "$release_profile" != "$EXPECTED_RELEASE_PROFILE" ]]; then
echo "Full release validation profile mismatch: expected $EXPECTED_RELEASE_PROFILE, got $release_profile" >&2
exit 1
fi
if [[ "$rerun_group" != "all" ]]; then
echo "Full release validation must run rerun_group=all before npm publish; got $rerun_group" >&2
exit 1
fi
- name: Validate release tag is reachable from a trusted release branch
env:
RELEASE_TAG: ${{ inputs.tag }}
WORKFLOW_REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
git fetch --no-tags origin \
@@ -200,7 +300,17 @@ jobs:
exit 0
fi
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
echo "Release tag must point to a commit reachable from main or release/*." >&2
if [[ "${RELEASE_TAG}" == *"-alpha."* ]]; then
if [[ ! "${WORKFLOW_REF_NAME}" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
echo "Alpha publish tags must be dispatched from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2
exit 1
fi
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
if git merge-base --is-ancestor HEAD "refs/remotes/origin/${WORKFLOW_REF_NAME}"; then
exit 0
fi
fi
echo "Release tag must point to a commit reachable from main, release/*, or the matching Tideclaw alpha branch for alpha prereleases." >&2
exit 1
- name: Summarize release target
@@ -208,6 +318,7 @@ jobs:
RELEASE_TAG: ${{ inputs.tag }}
TARGET_SHA: ${{ steps.manifest.outputs.sha || steps.ref.outputs.sha }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
run: |
{
echo "### Release target"
@@ -215,6 +326,9 @@ jobs:
echo "- Tag: \`${RELEASE_TAG}\`"
echo "- SHA: \`${TARGET_SHA}\`"
echo "- Release profile: \`${RELEASE_PROFILE}\`"
if [[ -n "${FULL_RELEASE_VALIDATION_RUN_ID// }" ]]; then
echo "- Full release validation: \`${FULL_RELEASE_VALIDATION_RUN_ID}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
publish:
@@ -230,6 +344,12 @@ jobs:
fetch-depth: 1
persist-credentials: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
cache-key-suffix: release-publish
- name: Dispatch publish workflows
env:
GH_TOKEN: ${{ github.token }}
@@ -237,11 +357,15 @@ jobs:
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
RELEASE_TAG: ${{ inputs.tag }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
PLUGINS: ${{ inputs.plugins }}
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
WAIT_FOR_CLAWHUB: ${{ inputs.wait_for_clawhub && 'true' || 'false' }}
PREFLIGHT_ARTIFACT_NAME: ${{ needs.resolve_release_target.outputs.preflight_artifact_name }}
NPM_TELEGRAM_RUN_ID: ${{ inputs.npm_telegram_run_id }}
POSTPUBLISH_EVIDENCE_DIR: ${{ runner.temp }}/openclaw-release-postpublish-evidence
run: |
set -euo pipefail
@@ -250,7 +374,10 @@ jobs:
shift
local before_json dispatch_output run_id
before_json="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
before_json="$(gh api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
-F event=workflow_dispatch \
-F per_page=100 \
--jq '[.workflow_runs[].id]')"
dispatch_output="$(gh workflow run --repo "$GITHUB_REPOSITORY" "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
printf '%s\n' "$dispatch_output" >&2
@@ -263,8 +390,10 @@ jobs:
if [[ -z "$run_id" ]]; then
for _ in $(seq 1 60); do
run_id="$(
BEFORE_IDS="$before_json" gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
BEFORE_IDS="$before_json" gh api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
-F event=workflow_dispatch \
-F per_page=50 \
--jq '.workflow_runs | map({databaseId:.id, createdAt:.created_at}) | map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
)"
if [[ -n "$run_id" ]]; then
break
@@ -285,6 +414,73 @@ jobs:
printf '%s\n' "${run_id}"
}
print_pending_deployments() {
local workflow="$1"
local run_id="$2"
local pending_json
pending_json="$(gh api -X GET "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/pending_deployments" 2>/dev/null || true)"
if [[ -z "${pending_json}" ]] || ! printf '%s' "${pending_json}" | jq -e 'length > 0' >/dev/null 2>&1; then
return 0
fi
echo "${workflow} pending environment approval:"
while IFS=$'\t' read -r env_id env_name can_approve; do
echo "- env=${env_name} canApprove=${can_approve}"
echo " approve: gh api -X POST repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/pending_deployments -F 'environment_ids[]=${env_id}' -f state=approved -f comment='Approve release gate'"
done < <(printf '%s' "${pending_json}" | jq -r '.[] | [.environment.id, .environment.name, .current_user_can_approve] | @tsv')
}
approve_pending_deployments() {
local workflow="$1"
local run_id="$2"
local pending_json approved
pending_json="$(gh api -X GET "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/pending_deployments" 2>/dev/null || true)"
if [[ -z "${pending_json}" ]] || ! printf '%s' "${pending_json}" | jq -e 'length > 0' >/dev/null 2>&1; then
return 0
fi
approved=0
while IFS=$'\t' read -r env_id env_name; do
if [[ -z "${env_id}" ]]; then
continue
fi
echo "${workflow}: approving pending environment ${env_name} (${env_id})"
gh api -X POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/pending_deployments" \
-F "environment_ids[]=${env_id}" \
-f state=approved \
-f comment="Approve release gate from OpenClaw Release Publish wrapper" >/dev/null
approved=1
done < <(printf '%s' "${pending_json}" | jq -r '.[] | select(.current_user_can_approve == true) | [.environment.id, .environment.name] | @tsv')
if [[ "${approved}" == "1" ]]; then
echo "${workflow}: approved available pending environment gates"
fi
}
print_failed_run_summary() {
local run_id="$1"
local failed_json
failed_json="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json jobs \
--jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {databaseId, name, conclusion, url}' || true)"
if [[ -z "${failed_json}" ]]; then
return 0
fi
echo "Failed child job summary:"
printf '%s\n' "${failed_json}"
while IFS=$'\t' read -r job_id job_name; do
if [[ -z "${job_id}" ]]; then
continue
fi
echo "--- ${job_name} (${job_id}) log tail ---"
gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --job "${job_id}" --log 2>/dev/null |
tail -200 || true
done < <(printf '%s\n' "${failed_json}" | jq -r '[.databaseId, .name] | @tsv' 2>/dev/null || true)
}
wait_for_run() {
local workflow="$1"
local run_id="$2"
@@ -302,6 +498,8 @@ jobs:
state="${status}:${updated_at}"
if [[ "$state" != "$last_state" ]]; then
echo "${workflow} still ${status} (updated ${updated_at}): ${url}"
print_pending_deployments "${workflow}" "${run_id}"
approve_pending_deployments "${workflow}" "${run_id}"
last_state="$state"
fi
sleep 30
@@ -329,7 +527,7 @@ jobs:
echo "- ${workflow}: ${conclusion} in ${duration_label} (${url})"
} >> "$GITHUB_STEP_SUMMARY"
if [[ "$conclusion" != "success" ]]; then
gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
print_failed_run_summary "${run_id}"
return 1
fi
}
@@ -402,17 +600,18 @@ jobs:
}
upload_dependency_evidence_release_asset() {
local release_version download_dir asset_path asset_name
local release_version download_dir asset_path asset_name artifact_name
release_version="${RELEASE_TAG#v}"
download_dir="${RUNNER_TEMP}/openclaw-release-dependency-evidence-asset"
asset_name="openclaw-${release_version}-dependency-evidence.zip"
asset_path="${RUNNER_TEMP}/${asset_name}"
artifact_name="${PREFLIGHT_ARTIFACT_NAME:-openclaw-npm-preflight-${RELEASE_TAG}}"
rm -rf "${download_dir}" "${asset_path}"
mkdir -p "${download_dir}"
gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "openclaw-npm-preflight-${RELEASE_TAG}" \
--name "${artifact_name}" \
--dir "${download_dir}"
if [[ ! -d "${download_dir}/dependency-evidence" ]]; then
@@ -428,6 +627,42 @@ jobs:
echo "- Dependency evidence asset: \`${asset_name}\`" >> "$GITHUB_STEP_SUMMARY"
}
verify_published_release() {
local release_version evidence_path
local -a verify_args
release_version="${RELEASE_TAG#v}"
evidence_path="${POSTPUBLISH_EVIDENCE_DIR}/release-postpublish-evidence.json"
mkdir -p "${POSTPUBLISH_EVIDENCE_DIR}"
verify_args=(
release:verify-beta
--
"${release_version}"
--tag "${RELEASE_TAG}"
--dist-tag "${RELEASE_NPM_DIST_TAG}"
--repo "${GITHUB_REPOSITORY}"
--workflow-ref "${CHILD_WORKFLOW_REF}"
--full-release-validation-run "${FULL_RELEASE_VALIDATION_RUN_ID}"
--plugin-npm-run "${plugin_npm_run_id}"
--plugin-clawhub-run "${plugin_clawhub_run_id}"
--openclaw-npm-run "${openclaw_npm_run_id}"
--evidence-out "${evidence_path}"
)
if [[ -n "${PLUGINS// }" ]]; then
verify_args+=(--plugins "${PLUGINS}")
fi
if [[ -n "${NPM_TELEGRAM_RUN_ID// }" ]]; then
verify_args+=(--npm-telegram-run "${NPM_TELEGRAM_RUN_ID}")
fi
pnpm "${verify_args[@]}"
{
echo "- Postpublish verification: passed"
echo "- Postpublish evidence: \`${evidence_path}\`"
} >> "$GITHUB_STEP_SUMMARY"
}
{
echo "### Publish sequence"
echo
@@ -436,11 +671,11 @@ jobs:
echo "- Release SHA: \`${TARGET_SHA}\`"
echo "- Plugin npm and ClawHub publish: dispatched in parallel"
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
echo "- OpenClaw npm publish: starts after plugin npm succeeds; ClawHub may still be running"
echo "- OpenClaw npm publish: starts after plugin npm succeeds; final verification waits for ClawHub"
else
echo "- OpenClaw npm publish: skipped by input"
fi
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
if [[ "${WAIT_FOR_CLAWHUB}" == "true" || "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
echo "- Workflow completion waits for ClawHub"
else
echo "- Workflow completion does not wait for ClawHub; monitor the dispatched ClawHub run separately"
@@ -473,6 +708,7 @@ jobs:
-f tag="${RELEASE_TAG}" \
-f preflight_only=false \
-f preflight_run_id="${PREFLIGHT_RUN_ID}" \
-f full_release_validation_run_id="${FULL_RELEASE_VALIDATION_RUN_ID}" \
-f npm_dist_tag="${RELEASE_NPM_DIST_TAG}")"
echo "- OpenClaw npm run ID: \`${openclaw_npm_run_id}\`" >> "$GITHUB_STEP_SUMMARY"
else
@@ -481,7 +717,7 @@ jobs:
clawhub_result=""
clawhub_pid=""
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
if [[ "${WAIT_FOR_CLAWHUB}" == "true" || "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
clawhub_result="$RUNNER_TEMP/clawhub-result.txt"
wait_run_pid=""
wait_for_run_background plugin-clawhub-release.yml "${plugin_clawhub_run_id}" "${clawhub_result}"
@@ -500,23 +736,39 @@ jobs:
fi
failed=0
if [[ -n "${clawhub_pid}" ]] && ! wait "${clawhub_pid}"; then
failed=1
fi
openclaw_failed=0
if [[ -n "${openclaw_pid}" ]] && ! wait "${openclaw_pid}"; then
failed=1
openclaw_failed=1
fi
if [[ -n "${openclaw_result}" && -f "${openclaw_result}" && "$(cat "${openclaw_result}")" != "success" ]]; then
failed=1
openclaw_failed=1
fi
if [[ -n "${openclaw_npm_run_id}" && "${openclaw_failed}" == "0" ]]; then
create_or_update_github_release
upload_dependency_evidence_release_asset
fi
if [[ -n "${clawhub_pid}" ]] && ! wait "${clawhub_pid}"; then
failed=1
fi
if [[ -f "${clawhub_result}" && "$(cat "${clawhub_result}")" != "success" ]]; then
failed=1
fi
if [[ -n "${openclaw_result}" && -f "${openclaw_result}" && "$(cat "${openclaw_result}")" != "success" ]]; then
failed=1
if [[ "${failed}" == "0" && -n "${openclaw_npm_run_id}" ]]; then
verify_published_release
fi
if [[ "${failed}" != "0" ]]; then
exit 1
fi
if [[ -n "${openclaw_npm_run_id}" ]]; then
create_or_update_github_release
upload_dependency_evidence_release_asset
fi
- name: Upload postpublish evidence
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: openclaw-release-postpublish-evidence-${{ inputs.tag }}
path: ${{ runner.temp }}/openclaw-release-postpublish-evidence
if-no-files-found: ignore

View File

@@ -6,6 +6,7 @@ on:
workflow_dispatch:
permissions:
actions: read
contents: read
packages: write
pull-requests: read
@@ -20,6 +21,7 @@ env:
jobs:
live_and_openwebui_checks:
permissions:
actions: read
contents: read
packages: write
pull-requests: read

View File

@@ -93,8 +93,18 @@ on:
required: false
default: ""
type: string
advisory:
description: Treat acceptance failures as advisory for the caller
required: false
default: false
type: boolean
workflow_call:
inputs:
advisory:
description: Treat acceptance failures as advisory for the caller
required: false
default: false
type: boolean
workflow_ref:
description: Trusted repo ref for workflow scripts and Docker E2E harness
required: false
@@ -284,7 +294,7 @@ env:
jobs:
resolve_package:
name: Resolve package candidate
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 60
outputs:
docker_lanes: ${{ steps.profile.outputs.docker_lanes }}
@@ -509,6 +519,7 @@ jobs:
needs: resolve_package
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
advisory: ${{ inputs.advisory }}
ref: ${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }}
include_repo_e2e: false
include_release_path_suites: ${{ needs.resolve_package.outputs.include_release_path_suites == 'true' }}
@@ -573,6 +584,7 @@ jobs:
if: needs.resolve_package.outputs.telegram_enabled == 'true'
uses: ./.github/workflows/npm-telegram-beta-e2e.yml
with:
advisory: ${{ inputs.advisory }}
package_spec: ${{ inputs.package_spec }}
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
package_label: openclaw@${{ needs.resolve_package.outputs.package_version }}
@@ -588,7 +600,7 @@ jobs:
name: Verify package acceptance
needs: [resolve_package, docker_acceptance, package_telegram]
if: always()
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify package acceptance results
@@ -599,6 +611,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
advisory="${{ inputs.advisory }}"
failed=0
for item in \
"resolve_package=${RESOLVE_RESULT}" \
@@ -608,6 +621,10 @@ jobs:
name="${item%%=*}"
result="${item#*=}"
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
if [[ "$advisory" == "true" && "$name" != "resolve_package" ]]; then
echo "::warning::${name} ended with ${result}; package acceptance is advisory for this caller."
continue
fi
echo "::error::${name} ended with ${result}"
failed=1
fi

View File

@@ -16,7 +16,7 @@ on:
required: false
type: string
ref:
description: Commit SHA on main or a release branch to publish from; defaults to the workflow ref
description: Commit SHA on main, a release branch, or the matching Tideclaw alpha branch to publish from; defaults to the workflow ref
required: false
default: ""
type: string
@@ -82,7 +82,9 @@ jobs:
fi
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate ref is on main or a release branch
- name: Validate ref is on a trusted publish branch
env:
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if git merge-base --is-ancestor HEAD origin/main; then
@@ -93,7 +95,14 @@ jobs:
exit 0
fi
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
echo "Plugin ClawHub publishes must target a commit reachable from main or release/*." >&2
if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
alpha_branch="${WORKFLOW_REF#refs/heads/}"
git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}"
if git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then
exit 0
fi
fi
echo "Plugin ClawHub publishes must target a commit reachable from main, release/*, or the matching Tideclaw alpha branch." >&2
exit 1
- name: Validate publishable plugin metadata
@@ -168,6 +177,19 @@ jobs:
echo "::error::One or more selected plugin versions already exist on ClawHub. Bump the version before running a real publish."
exit 1
- name: Validate Tideclaw alpha plugin channels
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
run: |
set -euo pipefail
invalid="$(
jq -r '.candidates[]? | select(.publishTag != "alpha" or .channel != "alpha") | "- \(.packageName)@\(.version) [\(.publishTag)]"' .local/plugin-clawhub-release-plan.json
)"
if [[ -n "${invalid}" ]]; then
echo "Tideclaw alpha ClawHub publishes may only publish alpha plugin versions." >&2
printf '%s\n' "${invalid}" >&2
exit 1
fi
- name: Verify OpenClaw ClawHub package ownership
if: steps.plan.outputs.has_candidates == 'true'
env:
@@ -444,10 +466,14 @@ jobs:
async function fetchWithRetry(url, options = {}) {
let lastStatus = "unknown";
for (let attempt = 1; attempt <= 12; attempt += 1) {
const response = await fetch(url, { redirect: "manual", ...options });
lastStatus = response.status;
if (response.status !== 429 && response.status < 500) {
return response;
try {
const response = await fetch(url, { redirect: "manual", ...options });
lastStatus = response.status;
if (response.status !== 429 && response.status < 500) {
return response;
}
} catch (error) {
lastStatus = error instanceof Error ? error.message : String(error);
}
await new Promise((resolve) => setTimeout(resolve, attempt * 5000));
}

View File

@@ -25,7 +25,7 @@ on:
- selected
- all-publishable
ref:
description: Commit SHA on main or a release branch to publish from (copy from the preview run)
description: Commit SHA on main, a release branch, or the matching Tideclaw alpha branch to publish from
required: true
type: string
plugins:
@@ -71,7 +71,9 @@ jobs:
id: ref
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate ref is on main or a release branch
- name: Validate ref is on a trusted publish branch
env:
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
git fetch --no-tags origin \
@@ -85,7 +87,14 @@ jobs:
exit 0
fi
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
echo "Plugin npm publishes must target a commit reachable from main or release/*." >&2
if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
alpha_branch="${WORKFLOW_REF#refs/heads/}"
git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}"
if git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then
exit 0
fi
fi
echo "Plugin npm publishes must target a commit reachable from main, release/*, or the matching Tideclaw alpha branch." >&2
exit 1
- name: Validate publishable plugin metadata
@@ -151,6 +160,19 @@ jobs:
echo "Already published / skipped:"
jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-npm-release-plan.json
- name: Validate Tideclaw alpha plugin channels
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
run: |
set -euo pipefail
invalid="$(
jq -r '.candidates[]? | select(.publishTag != "alpha" or .channel != "alpha") | "- \(.packageName)@\(.version) [\(.publishTag)]"' .local/plugin-npm-release-plan.json
)"
if [[ -n "${invalid}" ]]; then
echo "Tideclaw alpha plugin npm publishes may only publish alpha plugin versions." >&2
printf '%s\n' "${invalid}" >&2
exit 1
fi
preview_plugin_pack:
needs: preview_plugins_npm
if: needs.preview_plugins_npm.outputs.has_candidates == 'true'

View File

@@ -209,7 +209,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_plugin_prerelease_static == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: 45
strategy:
fail-fast: false
@@ -245,7 +245,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_plugin_prerelease_node == 'true'
runs-on: ${{ matrix.runner || 'ubuntu-24.04' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (matrix.runner || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -318,7 +318,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_plugin_prerelease_extensions == 'true'
runs-on: ${{ matrix.runner }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false

View File

@@ -60,13 +60,17 @@ jobs:
authorize_actor:
name: Authorize workflow actor
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
authorized: ${{ steps.permission.outputs.authorized }}
steps:
- name: Require maintainer-level repository access
id: permission
uses: actions/github-script@v8
with:
script: |
if (context.eventName === "schedule") {
core.info("Scheduled default-branch QA run; actor permission check is only required for manual dispatch.");
core.setOutput("authorized", "true");
return;
}
const allowed = new Set(["admin", "maintain", "write"]);
@@ -79,14 +83,18 @@ jobs:
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
core.notice(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
core.setOutput("authorized", "false");
return;
}
core.setOutput("authorized", "true");
validate_selected_ref:
name: Validate selected ref
needs: authorize_actor
if: needs.authorize_actor.outputs.authorized == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
selected_revision: ${{ steps.validate.outputs.selected_revision }}
@@ -178,6 +186,8 @@ jobs:
install-bun: "true"
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run OpenAI candidate lane
@@ -188,7 +198,7 @@ jobs:
--concurrency "${QA_PARITY_CONCURRENCY}" \
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
--alt-model openai/gpt-5.5-alt \
--output-dir .artifacts/qa-e2e/gpt54
--output-dir .artifacts/qa-e2e/openai-candidate
- name: Run Opus 4.7 lane
run: |
@@ -197,15 +207,15 @@ jobs:
--parity-pack agentic \
--concurrency "${QA_PARITY_CONCURRENCY}" \
--model anthropic/claude-opus-4-7 \
--alt-model anthropic/claude-sonnet-4-7 \
--output-dir .artifacts/qa-e2e/opus46
--alt-model anthropic/claude-sonnet-4-6 \
--output-dir .artifacts/qa-e2e/anthropic-baseline
- name: Generate parity report
run: |
pnpm openclaw qa parity-report \
--repo-root . \
--candidate-summary .artifacts/qa-e2e/gpt54/qa-suite-summary.json \
--baseline-summary .artifacts/qa-e2e/opus46/qa-suite-summary.json \
--candidate-summary .artifacts/qa-e2e/openai-candidate/qa-suite-summary.json \
--baseline-summary .artifacts/qa-e2e/anthropic-baseline/qa-suite-summary.json \
--candidate-label "${OPENCLAW_CI_OPENAI_MODEL}" \
--baseline-label anthropic/claude-opus-4-7 \
--output-dir .artifacts/qa-e2e/parity
@@ -219,6 +229,96 @@ jobs:
retention-days: 14
if-no-files-found: warn
run_live_runtime_token_efficiency:
name: Run live runtime token-efficiency lane
needs: [authorize_actor, validate_selected_ref]
if: github.event_name == 'schedule'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 45
environment: qa-live-shared
env:
QA_PARITY_CONCURRENCY: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Validate required QA credential env
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
shell: bash
run: |
set -euo pipefail
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
echo "Missing required OPENAI_API_KEY." >&2
exit 1
fi
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run live runtime parity lane
id: run_lane
shell: bash
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_LIVE_OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
set -euo pipefail
output_dir=".artifacts/qa-e2e/runtime-token-efficiency-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
pnpm openclaw qa suite \
--repo-root . \
--provider-mode live-frontier \
--runtime-parity-tier standard \
--runtime-parity-tier live-only \
--concurrency "${QA_PARITY_CONCURRENCY}" \
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
--runtime-pair pi,codex \
--fast \
--allow-failures \
--output-dir "${output_dir}/runtime-suite"
- name: Generate live runtime token-efficiency report
if: always() && steps.run_lane.outcome != 'skipped' && steps.run_lane.outcome != 'cancelled'
shell: bash
run: |
set -euo pipefail
pnpm openclaw qa parity-report \
--repo-root . \
--runtime-axis \
--token-efficiency \
--summary "${{ steps.run_lane.outputs.output_dir }}/runtime-suite/qa-suite-summary.json" \
--output-dir "${{ steps.run_lane.outputs.output_dir }}/runtime-report"
- name: Upload live runtime token-efficiency artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: qa-live-runtime-token-efficiency-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
retention-days: 14
if-no-files-found: warn
run_live_matrix:
name: Run Matrix live QA lane
needs: [authorize_actor, validate_selected_ref]
@@ -254,6 +354,8 @@ jobs:
fi
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run Matrix live lane
@@ -338,6 +440,8 @@ jobs:
fi
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run Matrix live lane shard
@@ -420,6 +524,8 @@ jobs:
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run Telegram live lane
@@ -513,6 +619,8 @@ jobs:
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run Discord live lane
@@ -547,8 +655,8 @@ jobs:
--repo-root . \
--output-dir "${output_dir}" \
--provider-mode live-frontier \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--model openai/gpt-5.5 \
--alt-model openai/gpt-5.5 \
--fast \
--credential-source convex \
--credential-role ci \
@@ -568,6 +676,9 @@ jobs:
needs: [authorize_actor, validate_selected_ref]
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
concurrency:
group: qa-live-whatsapp-shared
cancel-in-progress: false
environment: qa-live-shared
steps:
- name: Checkout selected ref
@@ -606,6 +717,8 @@ jobs:
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run WhatsApp live lane
@@ -699,6 +812,8 @@ jobs:
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run Slack live lane

View File

@@ -134,20 +134,29 @@ jobs:
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && inputs.sync_website)
runs-on: ubuntu-24.04
env:
OPENCLAW_GH_TOKEN: ${{ secrets.OPENCLAW_GH_TOKEN }}
steps:
- name: Skip website sync without token
if: env.OPENCLAW_GH_TOKEN == ''
run: echo "OPENCLAW_GH_TOKEN is not configured; installer verification passed, skipping website sync."
- name: Checkout OpenClaw
if: env.OPENCLAW_GH_TOKEN != ''
uses: actions/checkout@v6
with:
path: openclaw
- name: Checkout openclaw.ai
if: env.OPENCLAW_GH_TOKEN != ''
uses: actions/checkout@v6
with:
repository: openclaw/openclaw.ai
token: ${{ secrets.OPENCLAW_GH_TOKEN }}
token: ${{ env.OPENCLAW_GH_TOKEN }}
path: openclaw.ai
- name: Sync installer scripts
if: env.OPENCLAW_GH_TOKEN != ''
run: |
cp openclaw/scripts/install.sh openclaw.ai/public/install.sh
cp openclaw/scripts/install-cli.sh openclaw.ai/public/install-cli.sh
@@ -156,6 +165,7 @@ jobs:
chmod +x openclaw.ai/public/install.sh openclaw.ai/public/install-cli.sh
- name: Check for changes
if: env.OPENCLAW_GH_TOKEN != ''
id: changes
working-directory: openclaw.ai
run: |

View File

@@ -2,8 +2,12 @@ name: Workflow Sanity
on:
pull_request:
paths-ignore:
- "CHANGELOG.md"
push:
branches: [main]
paths-ignore:
- "CHANGELOG.md"
workflow_dispatch:
permissions:

7
.gitignore vendored
View File

@@ -41,6 +41,7 @@ apps/macos/.build/
apps/macos-mlx-tts/.build/
apps/shared/MoltbotKit/.build/
apps/shared/OpenClawKit/.build/
apps/shared/*/.build/
apps/shared/OpenClawKit/Package.resolved
**/ModuleCache/
bin/
@@ -50,6 +51,7 @@ apps/macos/.build-local/
apps/macos/.swiftpm/
apps/shared/MoltbotKit/.swiftpm/
apps/shared/OpenClawKit/.swiftpm/
apps/shared/*/.swiftpm/
Core/
apps/ios/*.xcodeproj/
apps/ios/*.xcworkspace/
@@ -108,6 +110,9 @@ USER.md
# local tooling
.serena/
# local QA evidence mirrors; CI publishes canonical Mantis files as Actions artifacts
mantis/
# Local project-agent skill installs. Only repo-owned skills are visible by
# default; promoting a new repo skill should require an intentional `git add -f`.
.agents/skills/*
@@ -134,6 +139,8 @@ USER.md
!.agents/skills/openclaw-refactor-docs/**
!.agents/skills/openclaw-qa-testing/
!.agents/skills/openclaw-qa-testing/**
!.agents/skills/openclaw-release-ci/
!.agents/skills/openclaw-release-ci/**
!.agents/skills/openclaw-release-maintainer/
!.agents/skills/openclaw-release-maintainer/**
!.agents/skills/openclaw-secret-scanning-maintainer/

View File

@@ -31,24 +31,34 @@ Skills own workflows; root owns hard policy and routing.
- Core/tests: no deep plugin internals (`extensions/*/src/**`, `onboard.js`). Use public barrels, SDK facade, generic contracts.
- Owner boundary: owner-specific repair/detection/onboarding/auth/defaults/provider behavior lives in owner plugin. Shared/core gets generic seams only.
- Dependency ownership follows runtime ownership: plugin-only deps stay plugin-local; root deps only for core imports or intentionally internalized bundled plugin runtime.
- Internal bundled plugins ship in core dist; bundled-only facade loader ok only for them.
- External official plugins own package/deps and are excluded from core dist; core uses registry-aware `facade-runtime` or generic contracts.
- Externalizing a bundled plugin: update package excludes, official catalogs, docs, tests, and prove core runtime paths resolve installed plugin roots before root-dep removal.
- Legacy config repair belongs in `openclaw doctor --fix`, not startup/load-time core migrations. Runtime paths use canonical contracts.
- New seams: backward-compatible, documented, versioned. Third-party plugins exist.
- Fix shape: prefer bounded owner-boundary refactors over local patches/shims when they remove stale abstractions, duplicate policy, or wrong ownership.
- Compat default: no new internal shims, aliases, fallback APIs, or legacy names just to reduce diff. Migrate callers and delete old paths.
- Public plugin API is the only compat exception: document/version breaks, aggressively deprecate unused SDK surface, and migrate ALL bundled/internal plugins to the modern API in the same change.
- Channels are implementation under `src/channels/**`; plugin authors get SDK seams. Providers own auth/catalog/runtime hooks; core owns generic loop.
- Hot paths should carry prepared facts forward: provider id, model ref, channel id, target, capability family, attachment class. Do not rediscover with broad plugin/provider/channel/capability loaders.
- Do not fix repeated request-time discovery with scattered caches. Move the canonical fact earlier; reuse prepared runtime objects; delete duplicate lookup branches.
- Inline code comments: brief notes for tricky, bug-prone, or previously buggy logic.
- Gateway protocol changes: additive first; incompatible needs versioning/docs/client follow-through.
- Protocol version bumps: explicit owner confirmation only; never automatic/generated.
- Config contract: exported types, schema/help, metadata, baselines, docs aligned. Retired public keys stay retired; compat in raw migration/doctor only.
- Prompt cache: deterministic ordering for maps/sets/registries/plugin lists/files/network results before model/tool payloads. Preserve old transcript bytes when possible.
- Agent tool schema cleanup: remove stale args cleanly; no hidden compat for model-facing params just to avoid churn.
## Commands
- Runtime: Node 22+. Keep Node + Bun paths working.
- Package manager/runtime: repo defaults only. No swaps without approval.
- Install: `pnpm install` (keep Bun lock/patches aligned if touched).
- Sharp/Homebrew libvips source-build fail: `SHARP_IGNORE_GLOBAL_LIBVIPS=1 pnpm install`.
- CLI: `pnpm openclaw ...` or `pnpm dev`; build: `pnpm build`.
- Tests: `pnpm test <path-or-filter> [vitest args...]`, `pnpm test:changed`, `pnpm test:serial`, `pnpm test:coverage`; never raw `vitest`.
- Checks: `pnpm check:changed`; lanes: `pnpm changed:lanes --json`; staged: `pnpm check:changed --staged`; full: `pnpm check`.
- Tests in a normal source checkout: `pnpm test <path-or-filter> [vitest args...]`, `pnpm test:changed`, `pnpm test:serial`, `pnpm test:coverage`; never raw `vitest`.
- Tests in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm test*`; use `node scripts/run-vitest.mjs <path-or-filter>` for tiny explicit-file proof, or Crabbox/Testbox for anything broader.
- Checks in a normal source checkout: `pnpm check:changed`; lanes: `pnpm changed:lanes --json`; staged: `pnpm check:changed --staged`; full: `pnpm check`.
- Checks in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm check*`; use `node scripts/crabbox-wrapper.mjs run ... --shell -- "pnpm check:changed"` so pnpm runs inside Testbox, not locally.
- Extension tests: `pnpm test:extensions`, `pnpm test extensions`, `pnpm test extensions/<id>`.
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); never add `tsc --noEmit`, `typecheck`, `check:types`.
- Formatting: `oxfmt`, not Prettier. Use repo wrappers (`pnpm format:*`, `pnpm lint:*`, `scripts/run-oxlint.mjs`).
@@ -57,10 +67,13 @@ Skills own workflows; root owns hard policy and routing.
## Validation
- Use `$openclaw-testing` for test/CI choice and `$crabbox` for remote/full/E2E proof.
- Small/narrow tests, lints, format checks, and type probes are fine locally.
- Crabbox request means real scenario proof: install/update/call/repro user path; not just copy tests and run them remotely.
- Small/narrow tests, lints, format checks, and type probes are fine locally only in a healthy normal checkout.
- In Codex worktrees, direct local `pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, and `scripts/committer` can trigger pnpm dependency reconciliation or install prompts. Prefer `node` wrappers locally and Crabbox/Testbox for pnpm-gated proof.
- Full suites, broad changed gates, Docker/package/E2E/live/cross-OS proof, or anything that bogs down the Mac: Crabbox/Testbox.
- One/few files local. If a local command fans out, stop and move broad proof to Crabbox/Testbox.
- Before handoff/push: prove touched surface. Before landing to `main`: issue proof plus appropriate full/broad proof unless scope is clearly narrow.
- Pre-land/pre-commit code changes: use `$autoreview` until no accepted/actionable findings remain, unless equivalent manual review already done, trivial/docs-only, or user opts out.
- If proof is blocked, say exactly what is missing and why.
- Do not land related failing format/lint/type/build/tests. If unrelated on latest `origin/main`, say so with scoped proof.
- Docs/changelog-only and CI/workflow metadata-only: `git diff --check` plus relevant docs/workflow sanity; escalate only if scripts/config/generated/package/runtime behavior changed.
@@ -73,19 +86,20 @@ Skills own workflows; root owns hard policy and routing.
- No unsolicited PR comments/reviews/labels/retitles/rebases/fixups/landing. Exception: close/duplicate action that needs a reason comment after explicit close/sweep/landing request.
- Maintainer decision closes the cluster: if deciding reported behavior/proposed fix is not planned, comment+close all directly associated open issues/PRs unless explicitly told to keep one open. Associated means linked PRs/issues, duplicates, companion workaround PRs, and the canonical issue for the rejected behavior.
- Do not leave associated issues open for hypothetical future repros. Close with rationale; ask for a new issue or reopen only if concrete new evidence appears. Close comment states: decision, why, supported alternative, and what evidence would change the decision.
- PR review answer: bug/behavior, URL(s), affected surface, best-fix judgment, evidence from code/tests/CI/current or shipped behavior.
- PR review answer: bug/behavior, URL(s), affected surface, provenance for regressions when traceable, best-fix judgment, evidence from code/tests/CI/current or shipped behavior.
- Issue/PR final answer: last line is the full GitHub URL.
- Changelog: PR landings/fixes need one unless pure test/internal. Do not mention missing changelog as a review finding; Codex handles it during fix/landing.
- PR verification: before merge, post exact local commands, CI/Testbox run IDs, before/after proof when used, and known proof gaps.
- Issue fixed on `main` with proof: comment proof + commit/PR, then close.
- After landing or requested close/sweep: search duplicates; comment proof + canonical commit/PR/release before closing.
- After landing/ship final: include 2-5 sentence recap of what landed: behavior change, key files/surface, proof run, issue/PR state. Do not answer with only status/links.
- `ship` that fixes an issue: after push, comment proof + commit link, then close the issue.
- GH comments with backticks, `$`, or shell snippets: use heredoc/body file, not inline double-quoted `--body`.
- PR create: real body required. Include Summary + Verification; mention refs, behavior, and proof.
- Real behavior proof section is parsed. Use exact `field: value` labels: `Behavior addressed`, `Real environment tested`, `Exact steps or command run after this patch`, `Evidence after fix`, `Observed result after fix`, `What was not tested`.
- PR artifacts/screenshots: attach to PR/comment/external artifact store. Do not commit `.github/pr-assets`.
- CI polling: exact SHA, relevant checks only, minimal fields. Skip routine noise (`Auto response`, `Labeler`, docs agents, performance/stale). Logs only after failure/completion or concrete need.
- Maintainers: ignore `Real behavior proof` failures that only say PR body lacks real after-fix evidence.
- Maintainers: may skip/ignore `Real behavior proof` when local tests or Crabbox verified behavior; record proof in PR verification.
- `/landpr`: use `~/.codex/prompts/landpr.md`; do not idle on `auto-response` or `check-docs`.
## Code

File diff suppressed because it is too large Load Diff

View File

@@ -293,4 +293,4 @@ USER node
HEALTHCHECK --interval=3m --timeout=10s --start-period=15s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
ENTRYPOINT ["tini", "-s", "--"]
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
CMD ["node", "openclaw.mjs", "gateway"]

View File

@@ -2,6 +2,480 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClaw</title>
<item>
<title>2026.5.12</title>
<pubDate>Fri, 15 May 2026 13:25:16 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026051290</sparkle:version>
<sparkle:shortVersionString>2026.5.12</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.5.12</h2>
<h3>Changes</h3>
<ul>
<li>Amazon Bedrock: externalize the Bedrock and Bedrock Mantle provider packages so core installs no longer pull AWS SDK dependencies unless those providers are installed.</li>
<li>Plugins: externalize Slack, OpenShell sandbox, and Anthropic Vertex so their runtime dependency cones install only when those plugins are installed.</li>
<li>Control UI/WebChat: add a persisted auto-scroll mode selector so users can keep the current near-bottom behavior, always follow streaming output, or turn automatic streaming scroll off and use the New messages button manually. Fixes #7648 and #81287. Thanks @BunsDev.</li>
<li>ACP: add <code>acp.fallbacks</code> so ACP turns can try configured backup runtime backends when the primary backend is unavailable before any output is emitted. (#69542) Thanks @kaseonedge.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Doctor/Codex: stop warning that the message tool is unavailable for source-reply paths where OpenClaw grants <code>message</code> at runtime, keeping update and doctor output aligned with the OpenAI happy path. Thanks @pashpashpash.</li>
<li>Channels/Weixin: bump the external Weixin catalog entry to <code>@tencent-weixin/openclaw-weixin@2.4.3</code> with the matching package integrity. (#81730) Thanks @scotthuang.</li>
<li>Agents/subagents: apply <code>agents.defaults.subagents.model</code> before target agent primary models during <code>sessions_spawn</code>, so model-scoped runtimes such as <code>claude-cli</code> stay attached to default child runs. Fixes #81395. (#81783) Thanks @joshavant.</li>
<li>Telegram: keep Bot API polling alive during main event-loop stalls by moving ingress to an isolated worker with a durable local spool. Fixes #81132. (#81746) Thanks @joshavant.</li>
<li>Telegram: preserve rendered HTML formatting through lazy cron announce delivery so Markdown links stay clickable instead of falling back to literal anchor tags. Fixes #81742. (#81758)</li>
<li>Telegram: skip unmentioned group media before download when <code>requireMention</code> is active, avoiding failed media-download replies for messages that should be ignored. Fixes #81181. (#81785) Thanks @joshavant.</li>
<li>CLI/plugins: keep bare plugin and parent-command help on the lightweight path, avoiding plugin registry discovery before rendering help.</li>
<li>Gateway/session history: carry monotonic transcript message sequence through live updates and refresh SSE history when stale sequence input would otherwise append bad incremental state. (#81474) Thanks @samzong.</li>
<li>Security/sandbox: include Windows <code>USERPROFILE</code> in the sandbox blocked home roots so credential-bearing binds (such as <code>.codex</code>, <code>.openclaw</code>, or <code>.ssh</code> under the Windows user profile) are denied even when <code>HOME</code> points at a different shell home. (#63074) Thanks @luoyanglang.</li>
<li>Models config/auth: stop inferring provider env-var markers from broad <code>^[A-Z_][A-Z0-9_]*$</code> strings, and resolve config-backed provider <code>apiKey</code> values only through structured env SecretRefs (<code>secrets.providers[id]</code> / <code>secrets.defaults</code>), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom.</li>
<li>Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd.</li>
<li>CLI/onboarding: forward provider-specific auth flags (e.g. <code>--openai-api-key</code>) through the onboarding wizard so they reach provider auth methods via <code>ctx.opts</code>, letting <code>--openai-api-key "$OPENAI_API_KEY"</code> skip the redundant "use existing env var?" prompt in non-interactive harnesses. (#81669) Thanks @sjf.</li>
<li>CLI/migrate: drop trailing periods from Codex migrate item messages and <code>REASON_CODE_MESSAGES</code> strings so plan/result rows read as labels instead of sentence fragments. (#81705) Thanks @sjf.</li>
<li>Slack: treat malformed private-file redirect <code>Location</code> headers as unfollowable redirects instead of failing Slack media downloads.</li>
<li>Plugins: discover provider plugins from <code>setup.providers[].envVars</code> credentials during provider discovery while keeping the deprecated <code>providerAuthEnvVars</code> fallback. (#81542) Thanks @JARVIS-Glasses.</li>
<li>Docs/Codex harness: clarify that per-agent <code>CODEX_HOME</code> isolates <code>~/.codex</code> while inherited <code>HOME</code> intentionally keeps <code>.agents</code> discovery and subprocess user-home state available.</li>
<li>Auth: reclaim dead-owner stale file locks before retrying locked writes, so crashed OAuth refreshes no longer wedge <code>auth-profiles.json</code> until manual cleanup.</li>
<li>CLI tables: preserve muted/color styling on wrapped continuation lines after multiline cells, keeping <code>openclaw plugins list</code> descriptions readable.</li>
<li>Process execution: collapse case-insensitive duplicate child environment keys on Windows so caller-provided overrides such as <code>PATH</code> cannot be shadowed by host <code>Path</code>.</li>
<li>Gateway/diagnostics: suppress cold-start liveness warnings during the startup grace window while still sampling liveness metrics. Fixes #79915. (#81699) Thanks @joshavant.</li>
<li>Codex harness: keep <code>oauthRef</code>-backed Codex OAuth profiles usable and stop high-confidence app-server OAuth refresh invalidation from retry-spamming raw token-refresh errors without turning entitlement or usage-limit payloads into re-auth prompts.</li>
<li>Browser CLI: request the existing <code>operator.admin</code> gateway scope explicitly for browser control commands, avoiding unnecessary scope-upgrade approval loops. Fixes #81555. (#81716) Thanks @joshavant.</li>
<li>Gateway/diagnostics: suppress cold-start liveness warnings during the startup grace window while still sampling liveness metrics. Fixes #79915. (#81699) Thanks @joshavant.</li>
<li>Plugin SDK: restore the deprecated <code>openclaw/plugin-sdk/memory-core</code> package subpath as an alias of <code>memory-host-core</code>, so published memory companion plugins that still import it resolve on current hosts.</li>
<li>Control UI/i18n: use the installed workspace pi runtime for locale refreshes, update the fallback package pin, prefer the Anthropic CI provider when available, and skip invalid provider credentials instead of failing main.</li>
<li>Codex harness: classify native app-server token-refresh logout and relogin failures as authentication refresh errors, so users get re-authentication guidance instead of a raw runtime failure.</li>
<li>Codex startup: treat selectable configured OpenAI agent models as Codex runtime requirements during plugin auto-enable, startup planning, and doctor install repair, so Anthropic-primary configs can still switch to OpenAI/Codex cleanly.</li>
<li>Agents: preserve source-reply delivery metadata when merging tool-returned media into the final reply, keeping message-tool-only replies deliverable and mirrored. Thanks @pashpashpash and @vincentkoc.</li>
<li>Replies: treat rich presentation, interactive controls, and channel-native payload data as outbound content across follow-up, heartbeat, cron, ACP, and block-streaming delivery paths, preventing card/button-only replies from being dropped as empty.</li>
<li>WebChat/TUI: route Codex <code>tools.message</code> source replies to the active internal UI turn and mirror them to session history, so message-tool-only harness replies, including rich presentation and button-only replies, no longer disappear while WebChat and TUI remain non-targetable outbound channels. (#81586) Thanks @pashpashpash.</li>
<li>Replies: deliver rich-only block replies even when block-streaming coalescing is enabled, keeping card and button payloads from being dropped by the text coalescer. Thanks @pashpashpash.</li>
<li>macOS/companion: require system TLS trust before pinning a first-use direct <code>wss://</code> gateway certificate and honor <code>gateway.remote.tlsFingerprint</code> as the explicit pin for remote node-mode sessions, so fresh endpoints fail closed when macOS cannot trust the certificate unless configured out of band. Fixes #50642. Thanks @BunsDev.</li>
<li>Update: snapshot config before update-time repair and restart writes, preserve plugin install records through doctor cleanup, and keep update-time config size drops from blocking the update while pointing users to the pre-update backup. Fixes #80077. (#80257) Thanks @Jerry-Xin and @vincentkoc.</li>
<li>Sessions/status: classify ACP spawn-child sessions as <code>kind: "spawn-child"</code> instead of <code>"direct"</code> in <code>openclaw sessions</code> and status output; extract the duplicated session-kind classifier into a shared helper (<code>src/sessions/classify-session-kind.ts</code>) so both surfaces stay in sync. Fixes catalog #19. (#79544)</li>
<li>Sessions/Gateway: report <code>agentRuntime.id: "acpx"</code> (or stored backend id) with <code>source: "session-key"</code> for ACP control-plane session rows in <code>openclaw sessions --json</code>, <code>openclaw status</code>, and Gateway session RPC responses instead of the incorrect <code>"auto"</code> / <code>"pi"</code> implicit fallback. Fixes catalog #18. (#79550)</li>
<li>Telegram: delete tool-progress-only draft bubbles before rotating to the real answer, preventing orphaned progress messages in streamed replies.</li>
<li>Codex app-server: keep per-agent <code>CODEX_HOME</code> isolation without rewriting <code>HOME</code> by default, so Codex-run subprocesses can still find normal user-home config, tokens, and CLI state unless the launch explicitly overrides <code>HOME</code>. Thanks @pashpashpash.</li>
<li>iMessage: stop sending visible <code><media:image></code> placeholder text for media-only native image sends while preserving the internal echo key that prevents self-echo duplicate replies. (#81209) Thanks @homer-byte.</li>
<li>Agents/sessions: create configured agent main sessions before first <code>sessions_send</code> or gateway send, so agent-to-agent messages no longer fail when the target agent has not started yet.</li>
<li>gateway: pass Talk session scope to resolver [AI]. (#81379) Thanks @pgondhi987.</li>
<li>Gateway protocol: require v4 clients and stream explicit chat <code>deltaText</code>/<code>replace</code> frames so SDK clients can consume assistant updates without local diffing. (#80725) Thanks @samzong.</li>
<li>GitHub Copilot: exchange OAuth tokens for Copilot API tokens on image understanding requests and route Gemini image payloads through Chat Completions, fixing Copilot Gemini image descriptions. (#80393, #80442) Thanks @afunnyhy.</li>
<li>Gateway: hide pending Node pairing commands, capabilities, and permissions until approval, and refresh the live approved surface when pairings change. (#80741) Thanks @samzong.</li>
<li>Plugins/Feishu/WhatsApp/Line: enforce inbound media size caps while reading download streams, avoiding full buffering of oversized attachments. (#81044, #81050) Thanks @samzong.</li>
<li>Plugins/install: limit install-time code safety scans to plugin-owned runtime entrypoints while keeping dependency manifest denylist checks, so trusted packages with large dependency trees no longer get blocked or warned on third-party runtime internals.</li>
<li>Config: serialize and retry semantic config mutations centrally, so concurrent commands can rebase safe changes instead of clobbering or hand-rolling command-local retry loops. (#76601)</li>
<li>Installer: honor <code>--no-git-update</code> for existing git checkouts before resolving release refs, preventing pinned source installs from moving during reinstall.</li>
<li>Plugins/install: refresh OpenClaw-managed peer dependency pins when installed plugin peer ranges change, while preserving user-owned dependency pins.</li>
<li>Require approval for setup-code device pairing [AI]. (#81292) Thanks @pgondhi987.</li>
<li>Plugins/install: preserve third-party peer dependencies in the managed npm root when later plugin installs or updates recalculate the shared dependency tree. Thanks @shakkernerd.</li>
<li>Plugins/memory: prefer the npm-installed memory-lancedb plugin over the bundled fallback during duplicate resolution, keeping Active Memory's <code>memory_recall</code> tool visible after managed installs. Fixes #81193. Thanks @julio-arcila.</li>
<li>Plugins/uninstall: prune managed third-party peer dependencies after their owning npm plugin is removed, without blocking plugin cleanup on peer-prune failures.</li>
<li>Docker: pin setup-time container paths so stale host <code>.env</code> OpenClaw paths cannot leak into Linux containers. Fixes #80381. (#81105) Thanks @brokemac79.</li>
<li>Channels/WeCom: refresh the official onboarding install to <code>@wecom/wecom-openclaw-plugin@2026.5.7</code> and update existing managed npm installs instead of failing on the package directory. Fixes #79884. (#80390) Thanks @brokemac79.</li>
<li>Anthropic: reseed Claude CLI fresh-session retries from bounded OpenClaw transcript history after session rotation, preventing conversation amnesia. Fixes #80905. (#80934) Thanks @bitloi.</li>
<li>Require explicit browser device pairing [AI]. (#81289) Thanks @pgondhi987.</li>
<li>Require Control UI pairing before proxy-scoped access [AI]. (#81288) Thanks @pgondhi987.</li>
<li>Installer: honor <code>--version</code> for git installs and install from the checked-in lockfile, preventing recent dependency pins from tripping pnpm's minimum-release-age gate during tag installs.</li>
<li>Agents: deliver same-process subagent completion handoffs through the in-process agent dispatcher instead of opening a Gateway RPC loopback.</li>
<li>Harden trusted-proxy source validation [AI]. (#81290) Thanks @pgondhi987.</li>
<li>Agents: add permissive item schemas to array tool parameters before provider submission, preventing OpenAI-compatible schema validation from rejecting plugin tools that omit <code>items</code>. Fixes #81175. (#81217) Thanks @JARVIS-Glasses.</li>
<li>Agents: escalate LLM idle watchdog timeouts through profile rotation and configured model fallback instead of leaving agent turns stuck after a silent model stream. Fixes #76877. (#80449) Thanks @jimdawdy-hub.</li>
<li>Discord voice: treat OpenAI Realtime startup auth failures as fatal, suppress duplicate realtime error logs, and stop autoJoin from retrying the same broken voice channel until credentials are fixed.</li>
<li>ACPX: stop forwarding unsupported timeout config options to Claude ACP while preserving OpenClaw's own turn timeout. (#80812) Thanks @sxxtony.</li>
<li>Session transcripts: redact sensitive message content in the centralized JSONL append path so CLI turns, gateway transcript injection, transcript mirrors, and guarded tool results use the same configured redaction behavior. Fixes #73565. Refs #73563. (#79645) Thanks @Ziy1-Tan.</li>
<li>Channels/iMessage: ignore Apple link-preview plugin payload attachments when users paste URLs, keeping the URL text while avoiding phantom media context. (#79374) Thanks @homer-byte.</li>
<li>Telegram: detect polling stalls from <code>getUpdates</code> liveness only, so outbound API calls no longer mask dead inbound polling; log polling-cycle starts after transport rebuilds. Fixes #78473.</li>
<li>fix: scan plugin runtime entries during install [AI]. (#80998) Thanks @pgondhi987.</li>
<li>fix(plugins): scan installed dependency runtime code [AI]. (#81066) Thanks @pgondhi987.</li>
<li>Inherit tool restrictions for delegated sessions [AI]. (#80979) Thanks @pgondhi987.</li>
<li>Telegram: discard legacy long-poll update offsets that cannot be tied to the current bot token, so token rotation no longer leaves bots silently skipping new messages. (#80671) Thanks @sxxtony.</li>
<li>browser: enforce navigation checks for act interactions [AI]. (#81070) Thanks @pgondhi987.</li>
<li>Validate node exec event provenance [AI]. (#81071) Thanks @pgondhi987.</li>
<li>Gateway: keep active reply runs visible to stuck-session diagnostics and clear no-active-work recovery state, preventing stale queued lanes after compaction or tool failures. Fixes #80677. (#81302)</li>
<li>Codex app-server: rotate incompatible context-engine-managed native threads so Lossless-managed sessions do not resume stale hidden Codex history. (#81223) Thanks @jalehman.</li>
<li>Codex cron: execute scheduled command-style automation payloads before workspace bootstrap or memory review, preserving existing isolated cron jobs after Codex harness migration. (#81510) Thanks @jalehman.</li>
<li>Plugin LLM completions: honor Codex agent-runtime policy for canonical OpenAI model refs, so context-engine summarizers can use Codex OAuth instead of requiring direct <code>OPENAI_API_KEY</code> auth. (#81511) Thanks @jalehman.</li>
<li>Gateway/OpenAI HTTP: return OpenAI-compatible 400 errors for invalid sampling params and provider validation failures instead of collapsing them to 500s. (#81275) Thanks @Lellansin.</li>
<li>Telegram: publish plugin and skill command description localizations to native command menus while filtering unsupported locale codes and preserving Telegram command limits. (#81351) Thanks @jzakirov.</li>
<li>Limit hook CLI tool authority [AI]. (#81065) Thanks @pgondhi987.</li>
<li>Require admin scope for node device token management [AI]. (#81067) Thanks @pgondhi987.</li>
<li>Restrict chat sender allowlist matching [AI]. (#80898) Thanks @pgondhi987.</li>
<li>Update: suppress the false newer-config warning during restart health probing after an update handoff, while keeping future-version mutation guards intact. (#78652)</li>
<li>Sessions: redact persisted tool result detail metadata before writing transcripts so diagnostic secrets do not survive tool output redaction. (#80444) Thanks @nimbleenigma.</li>
<li>Codex runtime: allow the official installed <code>@openclaw/codex</code> package to use its private task-runtime and MCP projection SDK helpers, fixing <code>MODULE_NOT_FOUND</code> during migrated OpenAI/Codex beta runs.</li>
<li>Codex migration: make Enter activate the highlighted checkbox row before continuing, so <code>Skip for now</code> and bulk-selection rows work even when planned items start preselected.</li>
<li>Codex harness: keep auth-profile-backed media tools such as <code>image_generate</code> available when OpenAI auth lives in the agent's auth-profile store instead of environment variables.</li>
<li>WhatsApp/install: allow Baileys' pinned libsignal git subdependency under pnpm 11 so source installs and local checks can complete.</li>
<li>Require auth for sandbox browser CDP relay [AI]. (#81002) Thanks @pgondhi987.</li>
<li>fix: detect carried exec command forms [AI]. (#81000) Thanks @pgondhi987.</li>
<li>Reject truncated exec approval commands [AI]. (#81001) Thanks @pgondhi987.</li>
<li>Enforce inline shell wrapper payload matching [AI]. (#80978) Thanks @pgondhi987.</li>
<li>fix(node-pairing): replace changed pending requests [AI]. (#80894) Thanks @pgondhi987.</li>
<li>Rate limit Google Chat webhook requests [AI]. (#80974) Thanks @pgondhi987.</li>
<li>Docker: mount the auth-profile secret key directory so OAuth-backed auth profiles survive container rebuilds. (#80991)</li>
<li>Onboarding: accept Codex auth profiles for canonical OpenAI model checks, avoiding false missing-auth warnings. (#80913) Thanks @rubencu.</li>
<li>fix(feishu): normalize webhook rate-limit client keys [AI]. (#80975) Thanks @pgondhi987.</li>
<li>fix(auth): prevent bootstrap pairing scope changes [AI]. (#80976) Thanks @pgondhi987.</li>
<li>Validate Control UI loopback retry endpoints [AI]. (#80900) Thanks @pgondhi987.</li>
<li>Harden exported markdown link rendering [AI]. (#80902) Thanks @pgondhi987.</li>
<li>fix(gateway): honor minimal discovery mode for wide-area DNS-SD [AI]. (#80903) Thanks @pgondhi987.</li>
<li>slack: enforce reaction notification policy [AI]. (#80907) Thanks @pgondhi987.</li>
<li>Enforce gateway command scopes by caller context [AI]. (#80891) Thanks @pgondhi987.</li>
<li>Telegram/groups: in single-account setups, treat an explicit empty <code>accounts.<id>.groups: {}</code> map the same as undefined so the root <code>channels.telegram.groups</code> allowlist still applies, instead of silently dropping every group update under the default <code>groupPolicy: "allowlist"</code>. Multi-account semantics are unchanged so per-account explicit-empty groups still scope-disable a single account without affecting siblings; the explicit way to block all groups for any account remains <code>groupPolicy: "disabled"</code>. Fixes #79427. (#81030) Thanks @kinjitakabe.</li>
<li>Codex (app-server): project user-configured <code>mcp.servers</code> into new Codex thread configs, matching the codex-cli runtime's existing <code>-c mcp_servers=...</code> behavior so app-server-runtime agents see the same user MCP servers the CLI runtime already exposes. Plugin-curated apps remain attached via the separate <code>apps</code> config patch. Fixes #80814. Thanks @kinjitakabe.</li>
<li>Enforce Slack plugin approval button authorization [AI]. (#80899) Thanks @pgondhi987.</li>
<li>Recognize PowerShell -ec inline commands [AI]. (#80893) Thanks @pgondhi987.</li>
<li>fix(qqbot): authorize approval button callbacks [AI]. (#80892) Thanks @pgondhi987.</li>
<li>Telegram: render supported HTML tags in streamed and durable replies instead of showing literal markup. (#80977)</li>
<li>Scrub streamable MCP redirect headers [AI]. (#80906) Thanks @pgondhi987.</li>
<li>fix(memory-wiki): require admin scope for ingest [AI]. (#80897) Thanks @pgondhi987.</li>
<li>memory-wiki: require write scope for Obsidian search [AI]. (#80904) Thanks @pgondhi987.</li>
<li>WhatsApp/install: allow Baileys' pinned libsignal git subdependency under pnpm 11 so source installs and local checks can complete.</li>
<li>WhatsApp: externalize the channel as a ClawHub/npm plugin outside the core npm runtime bundle, and bump Baileys to <code>7.0.0-rc11</code> so libsignal resolves from the registry instead of a GitHub tarball.</li>
<li>WhatsApp: keep optional audio decoding dependencies local to the external plugin so the core npm install no longer pulls WhatsApp-only media helpers.</li>
<li>Build: skip copied metadata for bundled plugins that are excluded from build entries, preventing update/status rebuilds from advertising missing QQ Bot runtime files. (#80925)</li>
<li>Control UI/sessions: nest subagent sessions under their parent session in the session picker dropdown using a visual <code>└─ </code> prefix, making the parent-child relationship clear. Fixes #77628. (#78623) Thanks @chinar-amrutkar.</li>
<li>Auto-reply: surface a visible error when the configured model backend fails and fallback produces no visible reply, while preserving intentional silent turns and side-effect-only deliveries. (#80917) Thanks @dutifulbob.</li>
<li>Agents/exec: skip redundant heartbeat wake-ups for subagent session exec completions, preventing spurious LLM invocations on parent sessions. Fixes #66748. (#66749) Thanks @ggzeng.</li>
<li>Provider streams: keep OpenAI-compatible SSE and JSON fallback streams draining across split chunks and fail Azure Responses streams with a bounded first-event diagnostic instead of stalling. Refs #80926. (#80927) Thanks @galiniliev and @CaptainTimon.</li>
<li>Agents: rewrite generic provider internal errors with support request IDs into user-friendly transient error copy. (#49401) Thanks @y471823206.</li>
<li>WhatsApp: finish handling pending debounced inbound messages before closing the socket. (#81246) Thanks @mcaxtr.</li>
<li>CLI/commitments: write <code>--json</code> output to stdout instead of diagnostic logs so automation can parse commitment list and dismiss results. (#81215) Thanks @giodl73-repo.</li>
<li>Update: allow pnpm GitHub-source OpenClaw updates to approve the OpenClaw package build, so source installs complete their prepare/prepack lifecycle. (#81294) Thanks @fuller-stack-dev.</li>
<li>Telegram: preserve supported HTML tags in visible replies and durable mirrors so formatted messages render correctly instead of degrading to escaped text. (#80977) Thanks @obviyus.</li>
<li>Plugins/runtime: attribute deprecated runtime config load/write warnings to the plugin id and source that triggered them so logs and plugin doctor runs are actionable. Refs #81394. (#81425) Thanks @BKF-Gitty.</li>
<li>Agents/cron: honor a cron payload's explicit <code>timeoutSeconds</code> for the LLM idle watchdog even when it numerically equals <code>agents.defaults.timeoutSeconds</code>, preserving explicit per-run timeout intent and preventing stalled streaming replies from being cut to the implicit 120s cap. (#79426) Thanks @legolaz8451.</li>
<li>Codex app-server: keep the short post-tool completion watchdog armed across dynamic tool completion bookkeeping so embedded Codex runs fail fast and release their session lane when Codex goes quiet after a tool result. (#81697) Thanks @mbelinky.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Gateway/OpenAI HTTP: honor <code>max_completion_tokens</code> and <code>max_tokens</code> on inbound <code>/v1/chat/completions</code> requests so client-provided token caps reach the upstream provider via <code>streamParams.maxTokens</code>, with <code>max_completion_tokens</code> taking precedence when both are sent. Thanks @Lellansin.</li>
<li>Models/OpenAI CLI auth: make <code>openclaw models auth login --provider openai</code> start the ChatGPT/Codex account login by default, while <code>--method api-key</code> remains the explicit OpenAI API-key setup path.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside explicit SDK OAuth auth-result config patches, so provider helpers emit <code>google/gemini-3.1-pro-preview</code> for Gemini 3.1 testing.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside SDK OAuth auth-result default config patches, so helper-built provider auth flows emit <code>google/gemini-3.1-pro-preview</code> for Gemini 3.1 testing.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids returned by direct <code>openclaw models auth login --set-default</code> provider auth flows before writing config, so Gemini testing targets <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids in per-agent config defaults and auth patches, so agent-specific emitted config keeps targeting <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids in provider catalog rows when API-key onboarding only reapplies the agent default, so emitted config keeps testing <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids in <code>config set</code> mutation output for agent overrides and provider catalog rows, so current config emits <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: canonicalize provider-qualified retired Gemini 3 Pro Preview refs during Google forward-compatible model resolution, so emitted config uses <code>google/gemini-3.1-pro-preview</code> for Gemini 3.1 testing.</li>
<li>Google/Gemini: normalize proxy-prefixed retired Gemini 3 Pro Preview catalog rows, so emitted configs use <code>google/gemini-3.1-pro-preview</code> for Gemini 3.1 testing.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside per-agent model overrides before writing config, so agent-specific config emits <code>google/gemini-3.1-pro-preview</code> for Gemini 3.1 testing.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids in subagent, heartbeat, compaction, and subagent-tool model config during writes, so current config keeps emitting <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Docs/subagents: document <code>agents.defaults.subagents.announceTimeoutMs</code> in the sub-agent and configuration references. (#75509) Thanks @akrimm702.</li>
<li>Cron: add direct <code>cron.get</code>, <code>openclaw cron get <id></code>, and agent-tool <code>get</code> support for inspecting one stored cron job by id. (#75117) Thanks @samzong.</li>
<li>Agents/tools: add per-sender tool policies with canonical channel-scoped sender keys, so operators can restrict dangerous tools by requester identity across global, agent, group, core, bundled, and plugin tool surfaces. (#66933) Thanks @JerranC.</li>
<li>ACP: expose Gateway session lineage metadata through ACP session listings and session info snapshots so clients can render subagent graphs without private Gateway side channels. (#73458) Thanks @samzong.</li>
<li>Channels/iMessage: add <code>openclaw channels status --channel <name></code> filtering and document the BlueBubbles-to-imsg cutover path so operators can probe iMessage without starting both channel monitors. (#80706) Thanks @omarshahine.</li>
<li>CI: add a non-blocking <code>plugin-inspector-advisory</code> artifact to Plugin Prerelease so release runs capture bundled plugin compatibility triage without changing the blocking gate.</li>
<li>Runtime/Fly: detect Fly Machines as container environments from their runtime env vars, so gateway bind and Bonjour defaults match remote container launches. (#80209) Thanks @liorb-mountapps.</li>
<li>Providers/fal: route GPT Image 2 and Nano Banana 2 reference-image edit requests to <code>/edit</code> with <code>image_urls</code> array, enforce NB2 edit geometry using <code>aspect_ratio</code> and <code>resolution</code> params, lift Fal edit mode input-image caps to 10 for GPT Image 2 and 14 for Nano Banana 2, and allow aspect-ratio hints in edit mode. (#77295) Thanks @leoge007.</li>
<li>Control UI: show a plain HTML recovery panel when the app module never registers, giving blank dashboard pages a retry path and browser-extension troubleshooting link. Fixes #44107. Thanks @BunsDev.</li>
<li>Docs: rename the broad tools nav to Capabilities, keep automation and agent coordination as sections, and keep the tools overview focused on tools, skills, and plugins. https://docs.openclaw.ai/tools</li>
</ul>
<ul>
<li>Build: enable additional low-churn oxlint rules for promise, TypeScript, and runtime footgun checks.</li>
<li>Build: enable stricter Vitest lint rules for focused, disabled, conditional, hook, matcher, and expectation hazards.</li>
<li>Build: pin explicit oxfmt defaults in the shared formatter config to keep formatting behavior stable across upgrades.</li>
<li>TypeScript: enable stricter compiler checks for implicit returns, side-effect imports, overrides, and unused production code.</li>
<li>Logging: add targeted model transport, payload, SSE, and code-mode diagnostics with redacted URL handling.</li>
<li>Agents: allow <code>session.agentToAgent.maxPingPongTurns</code> up to 20 while keeping the default at 5 for longer agent-to-agent reply chains. Fixes #52382. (#52400) Thanks @thirumaleshp.</li>
<li>Agents: add per-agent <code>tools.message.crossContext</code> overrides so sandboxed/public agents can restrict message sends to the current conversation without changing the global bot policy.</li>
<li>Agents: add per-agent <code>tools.message.actions.allow</code> overrides so sandboxed/public agents can expose and enforce send-only message tools.</li>
<li>Agents: omit the sandbox workspace marker from compact command progress previews while keeping internal sandbox diagnostics unchanged.</li>
<li>Agents: widen progress draft command preview lines by 50% so Discord inline tool updates preserve more useful command context.</li>
<li>Codex app-server: retire timed-out app-server clients after bounded turn interrupts so Discord agents do not reuse a CPU-spinning Codex process after an attempt timeout.</li>
<li>Codex app-server: default migrated native plugin destructive-action policy to enabled while preserving explicit global and per-plugin false overrides.</li>
<li>Build: upgrade workspace package management to pnpm 11 and keep Docker, install, update, and release workflows on the pnpm 11 config surface. (#79414) Thanks @altaywtf.</li>
<li>Build: align Telegram QA workflows and git source installs with the pnpm 11 workspace build allowlist surface. (#80588) Thanks @altaywtf.</li>
<li>Models: add provider-level <code>localService</code> startup for on-demand local model servers before OpenAI-compatible requests, including one-shot model probes.</li>
<li>Agents: trim default system prompt guidance and send-only message tool schemas to reduce prompt tokens while preserving GPT-5 personality guidance.</li>
<li>Context: add <code>/context map</code> to send a treemap image of the current session context contributors. (#79867)</li>
<li>Slack: add <code>unfurlLinks</code> and <code>unfurlMedia</code> config for bot <code>chat.postMessage</code> replies, including per-account overrides, so Slack link and media previews can be suppressed without workspace-wide settings. Fixes #48435. (#80145) Thanks @esegev1 and @HemantSudarshan.</li>
<li>Slack: add explicit <code>replyBroadcast</code> support for text and Block Kit thread replies so agents can opt into Slack's parent-channel <code>reply_broadcast</code> behavior. (#64365) Thanks @tony88331.</li>
<li>Slack: preserve mention target/source metadata in inbound prompt context so agents can distinguish direct bot mentions from implicit thread wakes that mention someone else. Fixes #79025. (#75356) Thanks @tmimmanuel.</li>
<li>Slack: canonicalize outbound delivery-mirror routes for native DM channel IDs to the peer user session so <code>message.send</code> calls to <code>D...</code> targets do not split the same Slack DM thread into a channel session. Fixes #80091. (#80111) Thanks @bek91.</li>
<li>Plugin SDK: deprecate public subpaths that existed for at least one month and have no bundled extension production imports, keep legacy barrel/test/zod subpath package exports for backwards compatibility, and track both sets in the SDK surface report.</li>
<li>Plugin SDK: deprecate public subpaths currently used by only one or two bundled plugin owners, keeping them importable while steering new plugin code to focused shared SDK seams or plugin-owned APIs.</li>
<li>Plugin SDK: remove the owner-specific <code>provider-auth-login</code> public subpath after moving Chutes, GitHub Copilot, and OpenAI Codex auth flows back to provider-owned modules.</li>
<li>Plugin SDK: remove provider-specific model, stream, and xAI compatibility helpers from public exports after moving bundled callers to provider-owned modules.</li>
<li>Plugin SDK: expose runtime-supplied active model metadata to native plugin tool factories for diagnostics and plugin-owned policy decisions. Fixes #77857. Thanks @jamiezigelbaum.</li>
<li>QA/Mantis: add Telegram live PR evidence automation with Convex-leased credentials, Crabbox transcript capture, motion GIF previews, and inline PR comments.</li>
<li>QA/Mantis: add a Telegram desktop scenario builder that leases Crabbox, installs native Telegram Desktop, configures an OpenClaw Telegram gateway with leased bot credentials, and records VNC screenshot/video artifacts.</li>
<li>Discord/voice: add realtime voice diagnostics for speaker turns, playback resets, barge-in detection, and audio cutoff analysis.</li>
<li>Talk: add <code>talk.realtime.instructions</code> so operators can append realtime voice style instructions while preserving OpenClaw's built-in agent-consult guidance. (#79081) Thanks @VACInc.</li>
<li>Discord/voice: default test and source installs to the pure-JS <code>opusscript</code> decoder by ignoring optional native <code>@discordjs/opus</code> builds, avoiding slow native addon compiles outside dedicated voice-performance lanes.</li>
<li>Discord/voice: add an opt-in native <code>@discordjs/opus</code> install script and decoder preference for live voice-performance lanes without charging unrelated Docker/tests for native addon builds.</li>
<li>Discord/voice: add <code>voice.allowedChannels</code> to restrict voice joins and bot voice-state moves to configured channels while preserving open voice behavior when unset.</li>
<li>Gateway/skills: add an opt-in private skill archive upload install path gated by <code>skills.install.allowUploadedArchives</code>, so trusted Gateway clients can stage and install zip-backed skills only when operators explicitly enable the code-install surface. (#74430) Thanks @samzong.</li>
<li>Codex app-server: enable Codex native code-mode-only for harness threads so deferred OpenClaw dynamic tools run through Codex's own searchable code execution surface instead of a PI-style wrapper.</li>
<li>Dependencies: refresh workspace pins and patch targets, including ACPX <code>@agentclientprotocol/claude-agent-acp</code> <code>0.33.1</code>, Codex ACP <code>0.14.0</code>, Baileys <code>7.0.0-rc10</code>, Google GenAI <code>2.0.1</code>, OpenAI <code>6.37.0</code>, AWS SDK <code>3.1045.0</code>, Kysely <code>0.29.0</code>, Tlon skill <code>0.3.6</code>, Aimock <code>1.19.5</code>, and tsdown <code>0.22.0</code>.</li>
<li>Dependencies: refresh workspace pins for Anthropic SDK, Smithy shared ini loading, Playwright, YAML, Aimock, TypeScript native preview, Vitest, Oxlint/Oxfmt, Vite, and pnpm 11.1.0.</li>
<li>Dependencies: hard-pin non-peer direct dependency specs across bundled packages and add a changed-check guard so runtime installs resolve the exact versions tested by maintainers.</li>
<li>Dependencies: move embedded Pi packages to the <code>@earendil-works</code> namespace, refresh Twitch Twurple packages, and move <code>@openclaw/fs-safe</code> from the GitHub release pin to the published npm package.</li>
<li>Build: route Testbox changed-check delegation through Crabbox and remove the OpenClaw-specific Blacksmith Testbox helper scripts.</li>
<li>Agents/compaction: preserve scoped background exec/process session references across embedded compaction and after-turn runtime contexts without exposing sessions from unrelated scopes. Fixes #79284. (#79307) Thanks @TurboTheTurtle.</li>
<li>Agents/process: tell agents to inspect background sessions with <code>process log</code> before sending interactive input and to use <code>waitingForInput</code>/<code>stdinWritable</code> hints from <code>log</code>/<code>poll</code>.</li>
<li>CLI/onboarding: improve setup, onboarding, configure, and channel command wayfinding so terminal flows explain the next useful command instead of relying on terse setup labels.</li>
<li>Agents/Codex: remove the configurable Codex dynamic-tools profile so Codex app-server always owns workspace, edit, patch, exec, process, and plan tools while OpenClaw integration tools remain available.</li>
<li>macOS app: update the Peekaboo bridge dependency to Peekaboo 3.0.0.</li>
<li>Dependencies: refresh workspace pins and move the WhatsApp plugin from <code>@whiskeysockets/baileys</code> to <code>baileys</code> while keeping the <code>7.0.0-rc10</code> runtime.</li>
<li>Plugin SDK: add bundled-plugin session actions, <code>sendSessionAttachment</code>, and Cron-backed <code>scheduleSessionTurn</code>/tag cleanup under the grouped session namespace. Replaces #75578/#75581/#75588 and part of #73384/#74483. Thanks @100yenadmin.</li>
<li>Plugin SDK/media-understanding: add <code>extractStructuredWithModel(...)</code> plus the optional provider-side <code>extractStructured(...)</code> seam so trusted plugins can run bounded image-first structured extraction with optional supplemental text context through provider-owned runtimes such as Codex.</li>
<li>Exec approvals: add <code>tools.exec.commandHighlighting</code> so parser-derived command highlighting in approval prompts can be enabled globally or per agent. (#79348) Thanks @jesse-merhi.</li>
<li>Codex app-server: mirror native Codex subagent spawn lifecycle events into Task Registry so app-server child agents appear in task/status surfaces without relying on transcript text. (#79512) Thanks @mbelinky.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>CLI/media: render terminal QR codes with full-block characters by default so the bundled <code>qrcode</code> terminal renderer does not emit a pathologically dense ANSI final row in compact half-block mode that breaks scanning in some terminals. Fixes #77820. Thanks @KrasimirKralev.</li>
<li>Agents/compaction: read post-compaction AGENTS.md refresh context from the queued run workspace instead of the runner process cwd, so CLI-backed follow-up turns re-inject the correct workspace startup rules after compaction. Fixes #70541. (#75532) Thanks @vyctorbrzezowski.</li>
<li>Agents/read tool: treat positive offsets beyond EOF as empty ranges instead of surfacing the upstream read error, so stale pagination cursors no longer crash tool calls while unrelated read failures still fail loud. Fixes #62466. (#75536) Thanks @vyctorbrzezowski.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview refs left in Google API-key onboarding model allowlists and fallbacks, so setup-emitted config keeps testing <code>google/gemini-3.1-pro-preview</code> instead of <code>google/gemini-3-pro-preview</code>.</li>
<li>Telegram/context: bound selected topic context to the active session so messages from before <code>/new</code> or <code>/reset</code> are not replayed into later turns. (#80848) Thanks @VACInc.</li>
<li>Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids when resolving exact configured proxy-provider refs, so <code>kilocode/google/gemini-3-pro-preview</code> resolves to <code>kilocode/google/gemini-3.1-pro-preview</code> for Gemini 3.1 testing.</li>
<li>CLI: strip generic OSC terminal escape payloads from sanitized output fields, preventing clipboard/title escape bodies from leaking into commitment tables and other terminal-safe text. Thanks @shakkernerd.</li>
<li>Codex app-server: match connector-backed plugin approval elicitations by stable connector id so enabled destructive actions no longer fall through to display-name-only rejection.</li>
<li>Build: replace selected build utility <code>tsx</code> preloads with Node native type stripping so Node 26 build paths no longer emit <code>DEP0205</code> module loader deprecation warnings. (#78584) Thanks @keshavbotagent.</li>
<li>Media generation: honor configured music and video generation timeouts when tool calls omit <code>timeoutMs</code>, matching image generation behavior. (#80687)</li>
<li>CLI/update/status: label beta-channel plugin fallback and model-pricing refresh failures as warnings, keeping mixed beta/latest plugin cohorts visible without making core update or Gateway reachability look failed. Fixes #80689. Thanks @BKF-Gitty.</li>
<li>Doctor/plugins: relink managed npm plugin <code>openclaw</code> peer dependencies during <code>doctor --fix</code>, while refusing to follow package-local <code>node_modules</code> symlinks outside the plugin package. (#77412) Thanks @TheCrazyLex.</li>
<li>iMessage: route inbound tapbacks as reaction system events instead of normal messages, defaulting to bot-authored-message notifications while allowing <code>reactionNotifications: "off" | "own" | "all"</code> overrides. Fixes #60274; refs #39031 and #39322. Thanks @hyperclaw.</li>
<li>Control UI/performance: scope Nodes polling to the active Nodes tab, debounce stale session-list reconciliation, and bound chat-side session refreshes so long-running dashboards avoid background reload churn. Thanks @BunsDev.</li>
<li>Plugins/channels: explain bundled channel entry files that reach the legacy plugin loader as setup-runtime loader mismatches instead of generic missing-register failures. Thanks @chinar-amrutkar.</li>
<li>Plugins/session-end: fire a typed <code>session_end</code> plugin hook with reason <code>shutdown</code> (or <code>restart</code> when a restart is expected) for every session that was still active when the gateway process stops. Previously SIGTERM/SIGINT/restart paths closed the gateway without enumerating active sessions, leaving downstream <code>session_end</code> plugins (e.g. claude-mem) with ghost rows accumulating across restarts. The new shutdown finalizer drains an in-memory tracker that is populated by <code>session_start</code> and forgotten by replace / reset / delete / compaction emitters, so previously-finalized sessions are never double-fired. The drain is bounded to a 2 s total budget so a slow plugin cannot block process exit. Adds <code>"shutdown"</code> and <code>"restart"</code> to <code>PluginHookSessionEndReason</code>. Fixes #57790. Thanks @pandadev66.</li>
<li>Codex app-server: clamp Codex code-mode sandboxing to workspace-write when an OpenClaw sandbox is active, preventing Docker gateway socket access from becoming a danger-full-access Codex turn.</li>
<li>TUI: exit immediately on Ctrl+C/SIGINT after gateway disconnect and bound shutdown drain so terminal teardown cannot strand sessions. Fixes #75379. (#75381) Thanks @udaymanish6.</li>
<li>Matrix: default outbound markdown tables to bullet lists instead of fenced code blocks. Fixes #78990. (#80890) Thanks @kinjitakabe.</li>
<li>Bonjour/Gateway: treat active ciao probing and fresh name-conflict renames as in-progress so the mDNS watchdog waits for probe settlement before retrying, preventing rapid re-advertise loops on Windows, WSL, and other multicast-hostile hosts. (#74778) Refs #74242. Thanks @fuller-stack-dev.</li>
<li>Providers/MiniMax: send a minimal Anthropic-compatible user fallback when message conversion filters a turn to an empty payload, so MiniMax M2.7 no longer returns <code>chat content is empty</code> after tool-heavy sessions. Fixes #74589. Thanks @neeravmakwana and @DerekEXS.</li>
<li>Tools/media: preserve implicit allow-all semantics from <code>tools.alsoAllow</code>-only policies when preconstructing built-in media generation and PDF tools, so configured media tools become live without forcing <code>tools.allow: ["*", ...]</code>. Fixes #77841. Thanks @trialanderrorstudios.</li>
<li>Codex/Telegram: separate code-mode tool progress from final replies, render bridged tool calls with native tool labels, and repair persisted missing tool results for safer follow-up turns. (#80663) Thanks @jalehman.</li>
<li>Memory/search: load the platform-specific <code>sqlite-vec-<platform>-<arch></code> variant directly when the meta <code>sqlite-vec</code> package is missing from a global install, so vector recall keeps working on <code>npm install -g openclaw@latest</code> upgrades where optionalDependencies left only the platform variant on disk. Fixes #77838. Thanks @corevibe555 and @Simon2256928.</li>
<li>Cron: keep long manual cron runs active in the task registry until completion, preventing transient <code>lost</code> markers before durable recovery reconciles. Fixes #78233. (#78243) Thanks @Feelw00.</li>
<li>Doctor/GitHub CLI: surface a <code>GH_CONFIG_DIR</code> hint when the GitHub skill is usable but <code>gh</code> auth lives under a different operator HOME than the agent process, without warning for disabled or filtered skills. Fixes #78063. (#78095) Thanks @tmimmanuel.</li>
<li>Gateway: dedupe concurrent <code>send</code>, <code>poll</code>, and <code>message.action</code> requests while delivery is still in flight, preventing duplicate outbound work for the same idempotency key. (#68341) Thanks @thesomewhatyou.</li>
<li>Cron: keep main-session <code>systemEvent</code> heartbeat wakes on their bound session route for both direct and queued wake paths by dropping inherited explicit heartbeat destinations when forcing <code>target: "last"</code>. Fixes #73900. Thanks @richardmqq.</li>
<li>Telegram: honor forced document delivery for video media so <code>--force-document</code> sends MP4s as documents instead of typed videos. Fixes #80389. (#80405) Thanks @jbetala7.</li>
<li>Gateway: clear speculative node wake state when APNs registration is missing, preventing unregistered or mistyped node IDs from retaining wake throttle entries. Fixes #68847. (#68848) Thanks @Feelw00.</li>
<li>Auto-reply: keep late follow-up queue drain finalizers from deleting a replacement queue registered after <code>/stop</code>, preventing immediate follow-up messages from being orphaned. Fixes #68838. (#68839) Thanks @Feelw00.</li>
<li>Feishu: make manual App ID/App Secret setup the default channel-binding path while keeping QR scan-to-create as an optional best-effort flow, and document the manual fallback for domestic Feishu mobile clients that do not react to the QR code. Fixes #80591. Thanks @wei-wei-zhao.</li>
<li>Memory: cap dreaming promotion writes to <code>MEMORY.md</code> by compacting oldest auto-promoted sections while preserving user-authored notes, keeping active memory below the bootstrap budget. Fixes #73691. (#74088) Thanks @YB0y.</li>
<li>Telegram: show resolved thinking defaults in native <code>/status</code> and <code>/think</code> menus while preserving explicit session overrides. (#80341) Thanks @VACInc.</li>
<li>Channels: cache selected channel registry lookups against the active fallback snapshot so pinned-empty registries refresh native command and alias routing after active registry swaps. (#80333) Thanks @samzong.</li>
<li>Codex app-server: reuse native Codex CLI OAuth for isolated app-server harness login, refresh, and app inventory cache keys so ChatGPT-authenticated Codex runs no longer fall back to unauthenticated OpenAI API calls. (#79877) Thanks @jeffjhunter.</li>
<li>Gateway: scope <code>sessions.resolve</code> sessionId and label store loads to the requested agent so large unrelated agent stores are not parsed for scoped lookups. Fixes #51264. (#79474) Thanks @samzong.</li>
<li>Gateway: share serialized streaming event envelopes across eligible WebSocket and node subscribers while preserving per-client sequence numbers. (#80299) Thanks @samzong.</li>
<li>Gateway: consolidate duplicate <code>openclaw doctor</code> service config panels while preserving the declined-repair <code>--force</code> hint. Fixes #80287. (#78688) Thanks @YB0y.</li>
<li>Browser: report Chrome MCP existing-session page readiness in browser status without letting status probes exceed the client timeout. Fixes #80268. (#80280) Thanks @ai-hpc.</li>
<li>WhatsApp: route opening-phase Baileys 428 connectionClosed through the WhatsApp reconnect policy and keep post-open 428 closes retryable, so transient setup socket closes retry with WhatsApp diagnostics instead of escaping as a bare <code>channel exited</code> error. Fixes #75736; mitigates #77443. Thanks @dataCenter430.</li>
<li>Agents: disable Pi's default filesystem resource discovery for embedded runs while keeping OpenClaw inline extension factories active, avoiding Windows event-loop stalls during first WhatsApp-triggered agent startup. Fixes #77443. Thanks @dataCenter430.</li>
<li>Providers/self-hosted: read model-scoped llama.cpp runtime context from <code>/props.default_generation_settings.n_ctx</code> while keeping top-level <code>n_ctx</code> as a fallback, so session budgeting reflects the loaded context window. Fixes #73664. (#74057) Thanks @brokemac79.</li>
<li>Memory: reject symlinked directory components in configured extra memory paths before reading Markdown files. (#80331) Thanks @samzong.</li>
<li>Sessions/transcripts: replace whole-file <code>readFile</code> scans with shared streaming helpers (<code>streamSessionTranscriptLines</code> and <code>streamSessionTranscriptLinesReverse</code>) for idempotency lookup, latest/tail assistant text reads, delivery-mirror dedupe, and compaction fork loading, so long-running sessions no longer materialize the full transcript in memory. Forward scans use <code>readline</code> over a bounded <code>createReadStream</code>; reverse scans read bounded chunks from the file end and decode complete JSONL lines newest-first without a fixed tail cap. Synthetic 200 MiB transcript: peak RSS delta drops from +252 MiB to +27 MiB while preserving malformed-line tolerance and idempotency-key return semantics. Fixes #54296. Thanks @jack-stormentswe.</li>
<li>Browser/CDP: filter browser-internal targets from raw CDP and persistent Playwright tab selection so navigation opens real page tabs. Fixes #55734. Thanks @Demine4.</li>
<li>WhatsApp: apply hot-reloaded <code>dmPolicy</code> and <code>allowFrom</code> settings to the active Web listener before processing new inbound DMs. Fixes #80538. Thanks @Ampaskopi129.</li>
<li>Plugins: let <code>openclaw doctor --fix</code> repair managed plugin installs whose package entrypoints fail package-directory boundary validation after local state moves. Fixes #80592. Thanks @wei-wei-zhao.</li>
<li>Voice-call: resume voice-originated exec approval follow-ups as internal non-delivery turns instead of rejecting them as <code>unknown channel: voice</code>. Fixes #80540. Thanks @patrickmch.</li>
<li>Control UI: preserve the composer draft when Stop is tapped during an active chat run, preventing accidental prompt loss on mobile. Fixes #80586. Thanks @KCALLC.</li>
<li>Infra/retry: keep jittered retry delays at or above server-supplied Retry-After lower bounds when the hint can be honored. Fixes #68541. (#68543) Thanks @Feelw00.</li>
<li>Docs: clarify that <code>/model provider/model</code> is an exact session route, while duplicate bare model ids only use configured fallback order on non-session override paths. Refs #80562. Thanks @gaodaabao.</li>
<li>Redact persisted secret-shaped payloads [AI]. (#79006) Thanks @pgondhi987.</li>
<li>Agents: label <code>.openclaw/sandboxes</code> exec workdirs as sandbox runs in compact tool summaries instead of showing the full path.</li>
<li>OpenAI Codex: surface browser OAuth and device-code login failures instead of treating failed logins as empty successful auth results. Refs #80363.</li>
<li>CLI agents: carry runtime-only current-turn sender/reply context into CLI model prompts while keeping prompt-build hook input and transcript text clean.</li>
<li>Control UI: keep workspace file presence checks from treating <code>fs-safe</code> stat helper failures as missing files, restoring Agents file status for existing Windows workspace files. Fixes #79953. Thanks @lovelefeng-glitch.</li>
<li>Microsoft Foundry: report an explicit error when the Azure subscription prompt returns an id that is not present in the enabled subscription list, instead of continuing from an unsafe subscription assertion. (#62742) Thanks @oliviareid-svg.</li>
<li>fix(matrix): gate name-based allowlist resolution [AI]. (#79007) Thanks @pgondhi987.</li>
<li>Slack: include the bot's own root/parent message in new thread sessions so in-thread replies reach the agent with the parent text the user is responding to, instead of only <code>reply_to_id</code> metadata. Fixes #79338. Thanks @sxxtony.</li>
<li>Docker: keep image builds on the source pnpm workspace policy so pnpm 11 can prune production dependencies without a Docker-only workspace rewrite.</li>
<li>Agents/compaction: restore info-level gateway logs for embedded compaction start, completion, and incomplete outcomes. (#71961) Thanks @rubencu.</li>
<li>Telegram: build reply-aware inbound turns through the shared channel context path so agents see the current reply target inline with the current message.</li>
<li>Telegram: recover legacy message cache files that mixed JSON-array and line-delimited entries so restarted gateways preserve reply-window context. (#80567)</li>
<li>Telegram: update the reply-context cache when messages are edited, so streamed bot replies appear in later agent context with their final text instead of the first draft.</li>
<li>Skills/Windows: normalize compacted skill prompt locations to forward slashes after home-prefix compaction so Windows skill paths remain readable by model file tools. (#52200) Thanks @chienchandler.</li>
<li>Control UI/Windows: update <code>@openclaw/fs-safe</code> so agent workspace file presence checks fall back correctly on Windows, preventing existing AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md, HEARTBEAT.md, and MEMORY.md files from showing as missing. Fixes #79953. Thanks @lovelefeng-glitch.</li>
<li>Memory: skip managed dreaming cron reconciliation warnings for ordinary cron and heartbeat hook contexts that cannot manage Gateway cron. (#77027) Thanks @rubencu.</li>
<li>Cron: treat Codex app-server turn acceptance, CLI process spawn, and tool starts as execution milestones, preventing isolated runs from tripping the early startup watchdog after work has begun.</li>
<li>Codex app-server: treat current-turn <code><turn_aborted></code> raw markers as terminal so interrupted native-tool turns release Discord agent sessions instead of waiting for the outer timeout.</li>
<li>Yuanbao: bump <code>openclaw-plugin-yuanbao</code> to 2.13.1 to support <code>sourceReplyDeliveryMode: "automatic"</code> for group chat. (#79814) Thanks @loongfay.</li>
<li>Memory: keep <code>memory_search</code> result <code>corpus</code> labels aligned with the hit source, so session transcript hits surface as <code>sessions</code> and memory-file hits stay <code>memory</code>. Fixes #72885. (#71898, #72886) Thanks @rubencu.</li>
<li>Codex app-server: default native plugin app tool approvals to automatic so non-destructive read tools run when destructive actions are disabled.</li>
<li>Plugins: allow untracked local source plugins in the global extensions directory to load TypeScript package entries while keeping managed installs strict about compiled runtime output. Fixes #80503. Thanks @Kaspre.</li>
<li>Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids while converting manifest catalog rows into emitted provider config, so <code>google/gemini-3.1-pro-preview</code> is used for testing instead of <code>google/gemini-3-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids inside saved model allowlists and fallback chains, so proxy routes like <code>openrouter/google/gemini-3-pro-preview</code> are persisted as Gemini 3.1 Pro Preview.</li>
<li>Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids in configured proxy/provider-auth model catalogs, so regenerated config keeps testing <code>google/gemini-3.1-pro-preview</code> instead of <code>google/gemini-3-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids while onboarding provider catalog presets, so setup-emitted proxy configs test <code>google/gemini-3.1-pro-preview</code> instead of <code>google/gemini-3-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids in provider catalog rows during generic config writes, so unrelated config changes keep testing <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Models: keep configured fallback chains ahead of configured primary models for override selections with duplicate model ids, preventing fallback jumps to the wrong provider. Fixes #80562.</li>
<li>Native apps: advertise the Gateway protocol compatibility range so chat and node sessions can connect to v3 gateways after additive v4 client updates.</li>
<li>Gateway/agents: keep stale <code>sessions_send</code> ACP manager and <code>web_fetch</code> runtime chunks importable after package updates, preventing live gateways from breaking before restart. Fixes #78804. Thanks @Gomesy72.</li>
<li>Gateway/install: preserve service environment value-source metadata in <code>openclaw gateway install</code>, so systemd reinstall paths keep env-file-backed secrets out of inline unit metadata. Refs #77406, #77427. Thanks @stainlu and @brokemac79.</li>
<li>Auto-reply/reset: include inbound sender context in bare <code>/new</code> and <code>/reset</code> model prompts while keeping startup instructions out of transcript prompts, so agents see sender identity on the first reset turn. Fixes #77360. Thanks @srb11e.</li>
<li>Gateway: avoid synchronous restart-sentinel state probes during post-attach startup, preventing slow Windows or redirected state directories from blocking channel turns. Fixes #79264. Thanks @liyi58.</li>
<li>Agents/auth: update successful model auth profile status with one locked store write, reducing post-model reply latency from duplicate <code>auth-profiles.json</code> saves. Thanks @mcaxtr.</li>
<li>Agents/image: honor explicit <code>image</code> tool model overrides even when <code>agents.defaults.imageModel</code> is unset, restoring one-off vision calls for configured multimodal providers. Fixes #79341. Thanks @haumanto.</li>
<li>Doctor/update: leave live systemd gateway units unchanged during noninteractive update-mode service repair, so update-time doctor does not silently overwrite operator-owned unit directives. Refs #80462.</li>
<li>Update: accept optional leading <code>v</code> prefixes when verifying exact npm package install targets, so <code>openclaw update --tag v2026...</code> does not roll back after installing the matching bare package version. Refs #74069; #80480. Thanks @Kaspre.</li>
<li>Doctor: treat missing plugin ids in <code>plugins.deny</code> as stale config warnings instead of fatal validation errors, and remove them during stale plugin cleanup so update repair does not restore last-known-good config for deny-only stale plugin refs. Refs #77802. Thanks @Kaspre.</li>
<li>Codex app-server: preserve prompt-local current-turn context through context-engine prompt projection, so replied-to Telegram messages stay visible to the Codex model input.</li>
<li>Telegram: pass agent-scoped media roots through gateway message actions so workspace-local media from the active agent is not rejected as cross-agent access. Thanks @frankekn.</li>
<li>CLI/gateway: keep <code>gateway status --deep</code> plugin-aware so configured plugin manifest warnings, including missing channel config metadata, stay visible during install and update smoke checks.</li>
<li>Doctor/status: clarify gateway token source conflict warnings and suppress them inside the managed Gateway service credential context.</li>
<li>Feishu: accept Schema 2 card callbacks whose operator identity is nested under <code>operator.user_id</code>, so card buttons dispatch instead of being dropped as malformed. Fixes #71670. (#71787) Thanks @rubencu.</li>
<li>Feishu: fall back to a top-level group send when normal group quoted replies target a withdrawn or missing message, preventing replies from disappearing silently while preserving native topic safety. Fixes #79349. Thanks @arlen8411.</li>
<li>Doctor: stop flagging the live compatibility agent directory as orphaned when the configured default agent is not <code>main</code>. Fixes #74313. (#74438) Thanks @carlos4s.</li>
<li>Auth/Claude CLI: persist fresher managed external CLI OAuth credentials back to <code>auth-profiles.json</code>, preventing stale <code>anthropic:claude-cli</code> profiles from repeatedly bootstrapping and flooding debug logs. Fixes #80129. Thanks @Caulderein.</li>
<li>Context: render <code>/context map</code> only from actual run context and persist Codex app-server run reports without counting deferred tool-search schemas as prompt-loaded tool schemas.</li>
<li>Codex app-server: report Codex-native tool execution to diagnostics so long-running native <code>bash</code>, web, file, and MCP tools no longer look like stale embedded runs to the watchdog. (#80217)</li>
<li>Codex app-server: refresh Codex account rate limits after subscription usage-limit failures so Discord and other channel replies can show the next reset time instead of saying Codex returned none. Thanks @pashpashpash.</li>
<li>Agents/auth: let Codex-backed OpenAI agent turns use <code>auth.order.openai</code> entries for Codex-compatible OAuth and API-key profiles while keeping existing <code>openai-codex</code> profile ordering valid.</li>
<li>Codex app-server: emit async <code>after_tool_call</code> observations for native tool completions not covered by the native hook relay so observability plugins can record Codex-native tools. (#80372) Thanks @VACInc.</li>
<li>Tasks: route group and channel task completions through the requester session so the parent agent can send the visible summary instead of stopping at a generic task-status line. Fixes #77251. (#77365) Thanks @funmerlin.</li>
<li>Telegram: preserve blank lines between manually indented bullet blocks and following numbered sections in rendered replies. Fixes #76998. Thanks @evgyur.</li>
<li>Agents/sandbox: allow read-only sandbox sessions to read the <code>/agent</code> workspace mount while keeping write/edit/apply_patch workspace-only guarded, restoring <code>read /agent/...</code> for <code>workspaceAccess: "ro"</code>. Fixes #39497. Thanks @stainlu and @teosborne.</li>
<li>Slack: pass configured agent identity through draft preview sends so partial streaming replies keep custom username/avatar on the initial Slack message. Fixes #38235. (#38237) Thanks @lacymorrow.</li>
<li>Slack: support <code>allowBots: "mentions"</code> for bot-authored messages that mention the receiving bot, matching the documented Discord-style mode without accepting every bot message. Fixes #43587. (#43588) Thanks @raw34.</li>
<li>Slack: refresh private file URLs with <code>files.info</code> when inbound DM file events omit or stale attachment URLs, preventing file attachments from being dropped before media hydration. Fixes #50129. (#50200) Thanks @smartchainark.</li>
<li>Slack: add scoped message-tool formatting hints so agents use Markdown for plain sends and direct mrkdwn for Block Kit fields. Fixes #34609. (#50979) Thanks @carrotRakko.</li>
<li>Slack: describe <code>download-file</code> file ids separately from message timestamps and return a targeted recovery error when agents pass <code>messageId</code> instead of <code>fileId</code>. (#74155) Thanks @jarvis-ai-gregmoser.</li>
<li>Slack: retain processed room messages for <code>requireMention=false</code> channels so always-on Slack rooms keep recent conversation context between turns. (#38658) Thanks @syedamaann.</li>
<li>Slack: compile interactive reply directives for direct outbound sends without bypassing the <code>interactiveReplies</code> capability gate, preserving Block Kit for Slack CLI and cron deliveries. (#78220) Thanks @kazamak.</li>
<li>Slack: keep DM last-route updates scoped to the active non-main DM session, including threaded DM turns, so isolated Slack DM sessions do not overwrite the shared main route. (#73085) Thanks @clawSean.</li>
<li>Slack/ACP: route Slack channel and DM messages through configured ACP bindings when no runtime binding exists, keeping bound thread replies pinned to the persistent ACP session and dropping unavailable configured targets instead of falling back to <code>main</code>. (#73101) Thanks @Raasl.</li>
<li>Slack: mark unresolved thread replies as ambiguous and skip them instead of treating them as root channel messages, keeping thread continuation on the SDK-backed participation store. (#75630) Thanks @soichiyo.</li>
<li>Slack: let same-channel message tool sends opt out of inherited thread context with <code>topLevel: true</code> or <code>threadId: null</code>, allowing agents to post a new parent-channel message from inside a Slack thread. Fixes #79807. Thanks @vexclawx31.</li>
<li>Slack: prefer full rich-text block content over truncated socket-mode message previews so long inbound Slack messages reach agents intact. Fixes #79027. Thanks @BobAccentWebDev.</li>
<li>Slack: include structured Slack API error details in setup, probe, streaming, and reply logs while preserving token redaction. (#53966) Thanks @deucemask.</li>
<li>Gateway/agents: keep structured reasons when active-run queueing fails and deprecate the legacy boolean queue helper, so steering and subagent wake diagnostics distinguish completed, non-streaming, and compacting runs. Fixes #80156. Thanks @markus-lassfolk.</li>
<li>System events: dedupe keyed events across the queue while preserving unkeyed, delivery-route, and trust-boundary event identity. (#73040) Thanks @statxc.</li>
<li>Agents/UI: compact exec and tool progress rows by hiding redundant shell tool names, replacing known workspace paths with short context markers, and preserving Discord trace scrubbing for compact command lines.</li>
<li>ACPX: run and await the embedded ACP backend startup probe by default so the gateway <code>ready</code> signal no longer fires before the acpx runtime has either become usable or reported a probe failure; set <code>OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=0</code> to restore lazy startup. Fixes #79596. Thanks @bzelones.</li>
<li>Gateway/status: surface model-pricing bootstrap and refresh failures as degraded health/status warnings while keeping Gateway liveness healthy. Fixes #79599. Thanks @bzelones.</li>
<li>OpenAI-compatible models: strip prior assistant reasoning fields from replayed Chat Completions history by default, preventing oMLX/vLLM Qwen follow-up turns from rejecting or stalling on stale <code>reasoning</code> payloads. Fixes #46637. Thanks @zipzagster and @lexhoefsloot.</li>
<li>CLI/onboarding: give non-Azure custom providers a safe generated context window and heal legacy 4k wizard entries without overwriting explicit valid small model limits, preventing first-turn compaction loops. Fixes #79428. (#79911) Thanks @Jefsky.</li>
<li>OpenAI-compatible models: add <code>compat.strictMessageKeys</code> to strip Chat Completions replay messages to <code>role</code> and <code>content</code> for strict providers that reject OpenAI-style tool and metadata keys. Fixes #50374. Thanks @choutos.</li>
<li>Bedrock Mantle: add <code>plugins.entries.amazon-bedrock-mantle.config.discovery.enabled=false</code> to suppress automatic Mantle discovery and IAM bearer-token generation while keeping the plugin enabled. Fixes #67288. Thanks @kanekoh.</li>
<li>Ollama: stop native <code>/api/chat</code> requests from copying catalog <code>contextWindow</code> or <code>maxTokens</code> into <code>options.num_ctx</code> unless <code>params.num_ctx</code> is explicitly configured, avoiding pathological prompt-ingestion latency on local large-context models. Fixes #62267. Thanks @BenSHPD.</li>
<li>Ollama: keep the model idle watchdog enabled for <code>*:cloud</code> models routed through a local Ollama host, so cloud-backed tool-loop stalls fail over visibly instead of inheriting local-model no-idle behavior. Fixes #79350. Thanks @geek111.</li>
<li>Voice/Ollama: honor routed voice agent <code>tools.allow</code> for classic embedded voice responses, including empty allowlists, so no-tool Ollama agents do not receive tool schemas. Fixes #79506. Thanks @donkeykong91.</li>
<li>Agents/doctor: warn when channel-routed agents cannot call the <code>message</code> tool, so operators can fix tool policy mismatches before explicit channel actions such as attachments or thread replies fail. Refs #80128. Thanks @jeffjhunterai.</li>
<li>Gateway: reread config from disk after the first in-process restart loop startup, preventing SIGUSR1 restarts from reusing a stale startup snapshot and dropping config written after boot. Fixes #79947. Thanks @TheLevti.</li>
<li>Codex app-server: deliver native image-generation outputs from Codex <code>savedPath</code> events as reply media, so blank-text image generation turns still attach the generated file. Thanks @keshavbotagent.</li>
<li>Network/SSRF: keep pinned automatic DNS lookups on IPv4 when dual-stack hosts also publish AAAA records, and treat <code>EADDRNOTAVAIL</code> as a transient gateway network failure instead of a fatal crash. Fixes #80078. Thanks @takamasa-aiso.</li>
<li>Control UI: show compact one-line live/idle/terminal run status badges in the Sessions table and rename the active-minute filter to its updated-within meaning. Fixes #78307. Thanks @BunsDev.</li>
<li>Control UI: scope chat session-list refreshes by agent and skip disk-only agent store discovery for configured-only lists, preventing post-first-message session switching stalls on large Windows stores. Fixes #79675. Thanks @lovelefeng-glitch, @BunsDev.</li>
<li>Control UI: allow Appearance tweakcn theme imports through the served CSP so browser-local custom theme links no longer fail with a <code>connect-src</code> violation. Fixes #78504. Thanks @BunsDev.</li>
<li>Control UI/config: remove plugin allowlist entries that the form auto-added when a plugin enable toggle is reverted before saving, so reverting the visible toggle clears dirty state without persisting unintended allowlist changes. (#78329) Thanks @samzong.</li>
<li>Gateway/mobile: reuse bootstrap-issued device-token scopes on handoff reconnects and surface device-token scope mismatches separately from token mismatches while preserving full shared-token dashboard/native sessions. Fixes #79292. Thanks @BunsDev.</li>
<li>Media/host-read: allow buffer-verified gzip, tar, and 7z archives in the shared host-local media validator alongside ZIP and document attachments.</li>
<li>Plugins/install: retry managed npm plugin installs without npm alias overrides after npm's <code>Invalid comparator: npm:</code> failure, so older npm versions can install official plugins instead of aborting. (#80539) Thanks @rubencu.</li>
<li>Plugins/doctor: invalidate persisted plugin registry snapshots when plugin diagnostics point at deleted source paths, so <code>openclaw doctor</code> stops repeating stale warnings after a local extension is replaced by a managed npm plugin. Fixes #80087. (#80134) Thanks @hclsys.</li>
<li>Doctor/OpenAI Codex: preserve Codex auth intent when auto-repairing legacy <code>openai-codex/*</code> model refs to canonical <code>openai/*</code> by adding provider/model-scoped Codex runtime policy, preventing repaired configs from falling through to direct OpenAI API-key auth. Fixes #78533 and #78570. Thanks @superck110 and @Azmodump.</li>
<li>CLI/agents: surface durable message delivery status from <code>sendDurableMessageBatch</code> in <code>deliverAgentCommandResult</code> and <code>openclaw agent --json --deliver</code>, preserving suppressed hook outcomes as terminal no-retry results while exposing partial and failed sends for automation. Supersedes #53961 and #57755. Thanks @Kaspre.</li>
<li>Agents: apply the LLM idle watchdog while provider stream setup is still pending, preventing silent pre-stream model hangs from waiting for the full agent timeout.</li>
<li>Cron: let isolated self-cleanup runs inspect their own job run history while keeping other cron jobs and mutation actions blocked. Fixes #80019. Thanks @hclsys.</li>
<li>Cron: report isolated agent-turn setup and pre-model stalls with phase-specific timeout errors instead of waiting for the full job budget when no model call starts. Fixes #74803. Thanks @jeffsteinbok-openclaw and @dgkim311.</li>
<li>CLI/plugins: treat arbitrary unknown subcommands outside plugin CLI metadata as normal unknown commands instead of suggesting <code>plugins.allow</code>, while preserving allowlist guidance for real plugin command roots. Fixes #80109. (#80123) Thanks @kagura-agent.</li>
<li>CLI/config: persist explicit <code>config set</code> and <code>config patch</code> values that equal runtime defaults instead of reporting success while dropping them. Fixes #79856. (#80106) Thanks @abodanty and @hclsys.</li>
<li>OpenAI/realtime voice: accept Codex-compatible legacy audio and transcript event aliases so provider protocol drift does not drop assistant audio or captions.</li>
<li>Discord/voice: keep default agent-proxy realtime sessions from auto-speaking filler before the forced OpenClaw consult answer, finish Discord playback on realtime response completion, and queue later exact-speech answers until playback idles to avoid mid-sentence replacement.</li>
<li>Gateway: return deterministic <code>400 invalid_request_error</code> responses for malformed encoded session-kill HTTP paths instead of letting route-shaped requests fall through to later Gateway handlers. (#72439) Thanks @rubencu.</li>
<li>Control UI: serve root PWA and favicon assets from <code>/__openclaw__/</code> SPA routes so tab icons, install metadata, and the service worker do not 404 after internal navigation. Fixes #80072. Thanks @CodeNovice2017.</li>
<li>Exec/safe bins: compare trusted safe-bin dirs with path-specific case folding on case-insensitive filesystems so Windows and default macOS paths match without weakening case-sensitive mounts. (#42131) Thanks @hkochar.</li>
<li>OpenAI/realtime voice: honor disabled input-audio interruption locally so server VAD speech-start events do not clear Discord playback after operators set <code>interruptResponseOnInputAudio: false</code>.</li>
<li>Telegram: keep no-response DM turns quiet instead of rewriting them into visible silent-reply chatter. Fixes #78188. (#78228) Thanks @Beandon13.</li>
<li>Telegram: handle managed select button callbacks before the raw callback fallback while preserving delimiter-containing option values such as <code>env|prod</code>. (#79816) Thanks @moeedahmed.</li>
<li>OpenAI-compatible models: handle JSON chat-completion bodies returned to streaming requests, preserving reasoning fields and visible text instead of completing an empty agent turn. Fixes #77870.</li>
<li>Discord/models: defer model picker component interactions before loading route, model, and preference data, preventing "This interaction failed" timeouts under gateway load. Fixes #77283. Thanks @colin-chang.</li>
<li>xAI: expose <code>/think low|medium|high</code> for reasoning-capable Grok models and keep <code>reasoning.effort</code> on native Responses payloads while preserving off-only behavior for non-reasoning routes. Fixes #79210. Thanks @colinmcintosh.</li>
<li>CLI/media: let explicit image description model refs use bundled static provider catalogs and generic model-backed image hooks, so <code>openclaw infer image describe --model zai/glm-4.6v</code> works like direct model runs and Anthropic auth probes avoid stale Claude 3 Haiku catalog entries.</li>
<li>Models/Anthropic: add <code>anthropic/claude-haiku-4-5</code> to Anthropic API-key agent allowlist defaults when an Anthropic default model is configured, so cron model overrides can select the current Haiku alias. Fixes #78000.</li>
<li>Agents/compaction: initialize built-in context engines before CLI transcript compaction resolves the default engine, preventing clean-process <code>legacy</code> engine registration failures during CLI session persistence. Fixes #79446. Thanks @TurboTheTurtle.</li>
<li>Agents/Anthropic-compatible: strip replayed thinking blocks for custom Anthropic-compatible models that explicitly declare <code>supportsReasoningEffort: false</code>, preventing Kimi-compatible providers from resending unsupported <code>thinking</code> content. Fixes #47452.</li>
<li>Kimi: keep Anthropic-compatible thinking streams valid by supplying required thinking budgets and enough output room for hidden reasoning plus final text. (#80481) Thanks @InTheCloudDan.</li>
<li>Browser: wait longer for existing-session Chrome MCP status and non-deep doctor probes so slow first attaches do not falsely report offline while keeping raw CDP status probes short. (#77473) Thanks @rubencu.</li>
<li>Gateway/logging: install console capture before foreground Gateway fast-path parsing and suppress known libsignal session dumps even in verbose mode, preventing raw terminal logs from printing WhatsApp session key material. (#76306) Thanks @rubencu.</li>
<li>Exec approvals: keep <code>exec.approval.list</code> on the lightweight policy-summary path so listing pending approvals no longer loads the rich tree-sitter command explainer. (#76943) Thanks @rubencu.</li>
<li>Agents: surface concise default-visible warnings when <code>exec</code>/<code>bash</code> tool calls fail after the assistant claims success, while keeping raw stderr hidden unless verbose details are enabled. Fixes #60497. (#80003) Thanks @jbetala7.</li>
<li>Channels/iMessage: keep redacted failed probe details in non-sensitive health snapshots so Full Disk Access failures no longer appear as configured/OK in status output. Fixes #79795.</li>
<li>Agents: stop blank model-emitted tool calls before dispatch while preserving id-based tool-name recovery, preventing Kimi/NVIDIA blank-name retry loops without creating a callable <code>_blank</code> sentinel. Fixes #34129. (#56391) Thanks @smartchainark.</li>
<li>Agents/Telegram: deliver the canonical final assistant answer instead of replaying accumulated pre-tool text blocks, preventing duplicate Telegram replies and raw-looking tool-output fragments from leaking into chat delivery. Fixes #79621 and #79986. Thanks @nonzeroclaw and @dudaefj.</li>
<li>Auto-reply/TUI: keep fallback timeout recovery deliverable after a primary model lifecycle error by emitting fallback progress and deferring terminal TUI errors until recovery has a chance to finish. Fixes #80000. (#80009) Thanks @TurboTheTurtle.</li>
<li>Heartbeat: clear stale auto fallback model overrides when the configured default model changes, so heartbeat runs follow updated <code>agents.defaults.model.primary</code> without requiring a manual reset. Fixes #74284. Thanks @brtkwr and @bitloi.</li>
<li>CLI/agent: let <code>openclaw agent --model</code> use the backend/admin Gateway scope without cached device-token scopes silently downscoping the request. (#78837) Thanks @VACInc.</li>
<li>CLI/help: keep help and version invocations configless while improving shared port, channel, plugin, task, session, message, pairing, and auth recovery text.</li>
<li>CLI/config: explain strict JSON parse failures with a valid example and the plain-string escape hatch.</li>
<li>CLI/secrets: turn offline Gateway reload failures into actionable recovery text.</li>
<li>CLI/channels: explain missing or ambiguous channel selections with next commands.</li>
<li>CLI/channels: defer guided channel status collection until a channel is selected, keeping <code>openclaw channels add</code> first screen quieter.</li>
<li>CLI/channels: exit guided channel setup cleanly on cancellation instead of printing the internal wizard error.</li>
<li>Plugins/CLI: route disabled Matrix and LanceDB memory command roots to plugin-enable guidance instead of generic unknown-command errors.</li>
<li>Browser/Docker: detect Playwright-managed Chromium from <code>PLAYWRIGHT_BROWSERS_PATH</code> and the default Playwright cache on Linux, so Docker installs that persist <code>/home/node/.cache/ms-playwright</code> no longer need <code>browser.executablePath</code>.</li>
<li>Ollama: keep DeepSeek V4 cloud models thinking-capable even when Ollama Cloud <code>/api/show</code> omits the <code>thinking</code> capability, so <code>/think high</code> no longer rejects <code>ollama/deepseek-v4-*:cloud</code>.</li>
<li>ACPX/Claude ACP: keep foreground prompts waiting for their own result when autonomous task-notification results arrive during the same session, and retarget the patch for Claude Agent ACP <code>0.33.1</code>.</li>
<li>WhatsApp: keep Baileys media uploads from passing non-Dispatcher agents to undici in <code>7.0.0-rc10</code>, and patch the bundled Baileys declaration so the latest tsdown build stays warning-clean.</li>
<li>Build: keep tsdown <code>0.22.0</code> warning-clean by externalizing known third-party declaration edges and replacing relative channel config module augmentations with explicit built-in channel fields.</li>
<li>ACP sessions: map canonical runtime options to backend-advertised ACP config keys like Claude's <code>effort</code> while keeping persisted OpenClaw state canonical. (#79926) Thanks @InTheCloudDan.</li>
<li>Models/Discord: support <code>provider/*</code> entries in <code>agents.defaults.models</code> so <code>/model</code>, <code>/models</code>, and model pickers can show dynamically discovered models for selected providers without exact model allowlists. Fixes #79485. Thanks @rendrag-git.</li>
<li>Gateway/watch: rebuild or restage missing bundled-plugin dist and runtime-postbuild outputs before launching the Gateway from a source checkout, preventing incomplete watch-mode runtime trees. (#70805) Thanks @rubencu.</li>
<li>CLI/update: allow restart health probes from the previous gateway protocol during self-update, and make plugin dry-runs report exact npm target versions instead of <code>unknown</code> while preserving unchanged status.</li>
<li>OpenAI/Codex: forward persisted <code>openai-codex</code> OAuth profile metadata into Codex plugin harness attempts after canonical <code>openai/*</code> migration, so OAuth-only installs keep using native Codex auth instead of falling through to direct OpenAI API-key auth. Fixes #79978.</li>
<li>OpenAI/Codex: point gateway missing-key recovery and wizard docs at the canonical <code>openai/gpt-5.5</code> plus Codex OAuth route, and fix trajectory export errors so they suggest the valid <code>openclaw sessions</code> command.</li>
<li>Google/Gemini: normalize retired <code>google/gemini-3-pro-preview</code> primary, fallback, and model-map refs during config load and unrelated config writes so saved config keeps targeting Gemini 3.1 Pro Preview.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside emitted Google provider model config, so regenerated models.json rows test <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids for explicit OpenAI-compatible Google and Gemini CLI provider configs, so emitted config targets <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids preserved from existing merged models.json providers so config emission keeps targeting <code>google/gemini-3.1-pro-preview</code>.</li>
<li>Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside provider auth config patches so setup-emitted provider catalogs test <code>google/gemini-3.1-pro-preview</code>.</li>
<li>GitHub Copilot: mint short-lived Copilot API tokens with the same <code>vscode-chat</code> integration identity used by runtime requests, and refresh legacy cached tokens missing that identity so image-capable Copilot models no longer inherit the <code>copilot-language-server</code> scope. Fixes #79946, #80074. Thanks @TurboTheTurtle.</li>
<li>Plugins/doctor: drop stale managed npm install records when <code>openclaw doctor --fix</code> removes npm packages that shadow bundled plugins, so the rebuilt registry no longer resurrects the removed package metadata.</li>
<li>Doctor: warn when a per-agent model config omits the <code>fallbacks</code> key and <code>agents.defaults.model.fallbacks</code> is non-empty. Covers both string-form (<code>"model": "..."</code>) and partial-object form (<code>"model": { "primary": "..." }</code>) — both silently clobber the defaults chain at runtime. Use <code>"fallbacks": []</code> to explicitly opt out of fallbacks, or add <code>"fallbacks": [...]</code> to inherit or override. Fixes #79369. Thanks @Kaspre.</li>
<li>Discord/voice: reuse or suppress late realtime consult tool calls without stealing newer speaker context or speaking forced fallback answers twice.</li>
<li>Discord/voice: skip likely incomplete realtime forced-consult transcript fragments and non-actionable closings so stale partial speech does not queue delayed answers over the next turn.</li>
<li>Discord/voice: keep realtime forced consults from clearing active exact-speech playback, so back-to-back voice answers queue instead of cutting each other off.</li>
<li>Discord/voice: synthesize realtime playback timestamps from emitted Discord PCM so OpenAI realtime barge-in truncation no longer sees <code>audioEndMs=0</code> and skips legitimate interruptions.</li>
<li>Plugin SDK: keep activated linked plugin runtime facades loadable when bundled plugin fallback is disabled. Thanks @shakkernerd.</li>
<li>Feishu: auto-thread <code>message(action="send")</code> replies inside the topic when the active session is group_topic or group_topic_sender, and propagate <code>replyInThread</code> through text, card, and media outbound adapters so topic-scoped sessions no longer post at the group root. Fixes #74903. (#77151) Thanks @ai-hpc.</li>
<li>WhatsApp: pass routing context into voice-note transcript echo preflight so echoed transcripts can deliver to the originating chat. Fixes #79778. (#79788) Thanks @hclsys.</li>
<li>Cron/failover: classify structured OpenAI-compatible <code>server_error</code> payloads as <code>server_error</code>, expose that reason in cron state, and let one-shot cron retry policy honor <code>retryOn: ["server_error"]</code> without requiring raw <code>5xx</code> text. (#45594) Thanks @clovericbot.</li>
<li>Slack: wake the resolved thread session after interactive reply button/select clicks and carry Slack delivery context through the queued interaction event, so clicks continue the visible conversation. Fixes #79676 and #61502. (#79836) Thanks @velvet-shark, @tianxiaochannel-oss88, and @Saicheg.</li>
<li>WhatsApp/streaming: send only the new suffix when text-end block replies repeat prior preambles across tool-call cycles, preventing cumulative WhatsApp preamble messages. Fixes #78946. (#79120) Thanks @brokemac79 and @papawattu.</li>
<li>Tests/security audit: sandbox <code>audit-exec-surface.test.ts</code> under a per-case OpenClaw home tempdir, redirecting <code>OPENCLAW_HOME</code> (which wins over <code>HOME</code>/<code>USERPROFILE</code> in <code>resolveRawHomeDir</code>) alongside <code>HOME</code> and <code>USERPROFILE</code>, so its <code>saveExecApprovals(...)</code> calls never touch the live <code>~/.openclaw/exec-approvals.json</code> on the host running the suite. Sibling exec-approvals tests already used the tempdir pattern; this file did not, so running <code>pnpm test</code> against a contributor's local checkout was silently truncating their real approvals to <code>{ "version": 1, "agents": {} }</code>. (#79885) Thanks @omarshahine.</li>
<li>ACP/gateway: preserve <code>AcpRuntimeError</code> cause chain (code/method/JSON-RPC detail) through the lifecycle boundary so gateway logs, telegram replies, and tool-result text show the actual upstream failure instead of opaque <code>Internal error</code>/<code>[object Object]</code>, with redaction applied before the chain reaches log or reply surfaces.</li>
<li>Channels/iMessage: wire <code>action: "reply"</code> attachments through <code>imsg send-rich --file</code> when the installed imsg build advertises that capability (probed once via <code>imsg send-rich --help</code> and cached on the private-API status). Reply now hydrates <code>media</code>/<code>mediaUrl</code>/<code>fileUrl</code>/<code>mediaUrls[0]</code>/<code>filePath</code>/<code>path</code>/base64 <code>buffer</code>+<code>filename</code> through the shared outbound resolver, stages buffers via the existing <code>withTempFile</code> helper, rejects <code>http(s)://</code> URL attachments with a targeted error pointing callers at <code>send</code>'s full attachment-resolver pipeline, and falls back to the explicit <code>imsg#114 not landed yet</code> error on older imsg builds. Depends on the upstream <code>openclaw/imsg#114</code> capability landing in an installable release; until then the new path stays gated and users see the same explicit fallback <code>#79822</code> introduced. (#79864) Thanks @omarshahine.</li>
<li>Telegram: preserve the first-preview debounce while appending true partial-stream deltas, so edited draft previews no longer duplicate earlier text when providers emit incremental output. (#80045) Thanks @TurboTheTurtle.</li>
<li>Agents/Anthropic: report 1M session context for Claude Opus/Sonnet 4 models even when local model config still advertises 200k, matching model discovery and preventing premature status/UI overflow. Fixes #66766.</li>
<li>Models/OpenRouter: hide missing-auth direct provider rows in <code>/model status</code> when they are only duplicated by a nested OpenRouter model id such as <code>openrouter/google/...</code>, while preserving explicitly configured direct providers. Fixes #62317.</li>
<li>Models: preserve an explicitly selected provider/model such as <code>opencode-go/deepseek-v4-pro</code> when another provider owns the same bare model alias. Fixes #79325.</li>
<li>Models/config: explain missing <code>models.providers.<provider>.models[]</code> registration when a model exists only in <code>agents.defaults.models</code>, instead of returning a bare unknown-model error. Fixes #80089.</li>
<li>MCP/tools: prefix bundle MCP server/tool fragments that would start with digits, keeping generated tool names valid for Moonshot/Kimi and other strict providers. Fixes #79179.</li>
<li>Models/OpenRouter: treat <code>403 API key budget limit exceeded</code> as billing so model fallback advances instead of retrying the exhausted primary. Fixes #60191. Thanks @omgitsgela.</li>
<li>Models/OpenRouter: repair stale session overrides that lost the outer <code>openrouter/</code> provider wrapper, so sessions return to the configured OpenRouter model instead of failing as an unknown direct-provider model. Fixes #78161. Thanks @hjamal7-bit.</li>
<li>Google/Gemini: default API-key onboarding back to <code>google/gemini-3.1-pro-preview</code> so fresh Gemini test configs exercise Gemini 3.1 Pro Preview.</li>
<li>Telegram: show full provider/model labels for nested OpenRouter model ids in the model picker, so <code>openrouter/openai/gpt-5.4-mini</code> no longer displays as <code>openai/gpt-5.4-mini</code>. Fixes #67792. (#72752) Thanks @iot2edge.</li>
<li>Models/OpenRouter: preserve live <code>supported_parameters</code> tool support metadata so non-tool Perplexity Sonar models no longer receive agent tool payloads and fall back unnecessarily. Fixes #64175. Thanks @Catfish-75.</li>
<li>Models/OpenRouter: add MoonshotAI Kimi K2.5 to the bundled OpenRouter catalog so onboarding/model pickers can offer it without waiting for live discovery. Fixes #14601.</li>
<li>Models/OpenRouter: keep keyRef/tokenRef-backed auth profiles visible to read-only PI model discovery, so OpenRouter models stay available in model pickers without storing plaintext keys. Fixes #58106. Thanks @ThalynLabs.</li>
<li>Models/list: include explicit configured provider rows and read-only auth-backed catalog rows in the default configured view without loading PI's full registry, keeping Control UI pickers aligned with usable model auth. Refs #79381. Thanks @ismael-81.</li>
<li>Security/audit: honor <code>tools.byProvider["provider/model"].deny</code> when reporting small-model web/browser exposure, so per-model OpenRouter mitigations clear the <code>models.small_params</code> exposure signal. Fixes #80118.</li>
<li>Models/Moonshot: accept direct <code>moonshotai/...</code> and <code>moonshot-ai/...</code> refs as aliases for canonical <code>moonshot/...</code>, so copied OpenRouter Kimi ids no longer fail as unknown direct models. Fixes #73876. (#74946) Thanks @jeffrey701.</li>
<li>Kimi Code: use Kimi's stable <code>kimi-for-coding</code> API model id in bundled catalog, onboarding, and docs while normalizing legacy <code>kimi-code</code> and <code>k2p5</code> refs. Fixes #79965.</li>
<li>Telegram: render cached reply targets and nearby group chatter as one selected conversation context window, so stale replies no longer split JSON reply chains from local chat context.</li>
<li>Volcengine/Kimi: strip provider-unsupported tool schema length and item constraint keywords for direct and coding-plan models so hosted Kimi runs do not reject message tools with <code>minLength</code>. Fixes #38817.</li>
<li>DeepSeek: backfill V4 <code>reasoning_content</code> replay fields for unowned OpenAI-compatible proxy providers, preventing follow-up request failures outside the bundled DeepSeek and OpenRouter routes. Fixes #79608.</li>
<li>iMessage: emit a WARN log when an action is blocked because the imsg private API bridge is not attached, so operators see the silent-drop in <code>~/.openclaw/logs/openclaw.log</code> instead of having to read per-session trajectory JSONL <code>tool.result</code> payloads. Common after a gateway restart un-injects the dylib from Messages.app. (#80035) Thanks @omarshahine.</li>
<li>Codex: cross-fill missing <code>thread.id</code> and <code>thread.sessionId</code> before schema validation so live Codex app-server responses that omit <code>sessionId</code> no longer fail <code>thread/start</code> or <code>thread/resume</code>. Fixes #80124. (#80137) Thanks @kagura-agent.</li>
<li>Agents/Pi: wait for embedded abort cleanup to settle before releasing the session write lock, preventing follow-up turns from racing previous prompt teardown. (#80239) Thanks @samzong.</li>
<li>WhatsApp: downgrade OpenClaw watchdog-triggered Web reconnects from runtime errors to recovery warnings and clear the recovered reconnect status after the next healthy connection. (#77026) Thanks @rubencu.</li>
<li>ACPX/Windows: hide the MCP proxy target child process window on Windows so ACP-backed agents do not flash or fail because of terminal window handling. Fixes #60672. (#60678) Thanks @KChow-ctrl.</li>
<li>Agents: abort generic repeated no-progress tool loops at the critical threshold when identical calls keep returning identical outcomes. (#80668) Thanks @frankekn.</li>
<li>Exec approvals: omit generated command highlights for non-POSIX Windows and shell-wrapper approval commands until those command languages have native highlighting support. (#80566) Thanks @jesse-merhi.</li>
<li>Telegram: keep verbose tool progress and result drafts separate from the final assistant answer so tool output no longer blends into the final Telegram message. (#80294) Thanks @jalehman.</li>
<li>Plugin SDK/Windows: enable the native require fast path for root <code>openclaw/plugin-sdk</code> dist aliases instead of forcing Jiti transforms. (#80878) Thanks @medns.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.5.12/OpenClaw-2026.5.12.zip" length="52670462" type="application/octet-stream" sparkle:edSignature="RhckloxLoZhtGQZ+0jrH0qWN3py61an+kiAdgEJY9IZGekTKmJ+DWHXY3ixQJYvKf2WLgxOzaY6Jy27pF+kECw=="/>
</item>
<item>
<title>2026.5.7</title>
<pubDate>Thu, 07 May 2026 22:36:27 +0000</pubDate>
@@ -449,368 +923,5 @@
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.5.2/OpenClaw-2026.5.2.zip" length="51078259" type="application/octet-stream" sparkle:edSignature="NwoecacHxJOYpltNmB/y7LV5I8ZIh5pENWSydbOM1vsfgSrcb7pRP+Zm2nih1IAq7hh1tOmQ0XWnsohic7U4DA=="/>
</item>
<item>
<title>2026.4.29</title>
<pubDate>Thu, 30 Apr 2026 21:47:22 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026042990</sparkle:version>
<sparkle:shortVersionString>2026.4.29</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.4.29</h2>
<h3>Highlights</h3>
<ul>
<li>Messaging and automation get active-run steering by default, visible-reply enforcement, spawned subagent routing metadata, and opt-in follow-up commitments for heartbeat-delivered reminders. Thanks @vincentkoc, @scoootscooob, @samzong, and @vignesh07.</li>
<li>Memory grows into a people-aware wiki with provenance views, per-conversation Active Memory filters, partial recall on timeout, and bounded REM preview diagnostics. Thanks @vincentkoc, @quengh, @joeykrug, and @samzong.</li>
<li>Provider/model coverage expands with NVIDIA onboarding/catalogs plus faster manifest-backed model/auth paths, Bedrock Opus 4.7 thinking parity, and safer Codex/OpenAI-compatible replay and streaming behavior. Thanks @eleqtrizit, @shakkernerd, @prasad-yashdeep, @woodhouse-bot, and @LyHug.</li>
<li>Gateway and packaged-plugin reliability focuses on slow-host startup, reusable model catalogs, event-loop readiness diagnostics, runtime-dependency repair, stale-session recovery, and version-scoped update caches. Thanks @lpendeavors, @DerFlash, @vincentkoc, @pashpashpash, and @jhsmith409.</li>
<li>Channel fixes cluster around Slack Block Kit limits, Telegram proxy/webhook/polling/send resilience, Discord startup/rate-limit handling, WhatsApp delivery/liveness, and Microsoft Teams/Matrix/Feishu edge cases. Thanks @slackapi, @SymbolStar, @djgeorg3, @TinyTb, @dseravalli, @nklock, and @alex-xuweilong.</li>
<li>Security and operations add OpenGrep scanning, sharper GHSA triage policy, safer exec/pairing/owner-scope handling, Docker/onboarding automation, and web-fetch IPv6 ULA opt-in for trusted proxy stacks. Thanks @jesse-merhi, @pgondhi987, @mmaps, @jinjimz, and @jeffrey701.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Security/tools: configured tool sections (<code>tools.exec</code>, <code>tools.fs</code>) no longer implicitly widen restrictive profiles (<code>messaging</code>, <code>minimal</code>). Users who need those tools under a restricted profile must add explicit <code>alsoAllow</code> entries; a startup warning identifies affected configs. Fixes #47487. Thanks @amknight.</li>
<li>Agents/commitments: add opt-in inferred follow-up commitments with hidden batched extraction, per-agent/per-channel scoping, heartbeat delivery, CLI management, a simple <code>commitments.enabled</code>/<code>commitments.maxPerDay</code> config, and heartbeat-interval due-time clamping so magical check-ins do not echo immediately. (#74189) Thanks @vignesh07.</li>
<li>Messages/queue: make <code>steer</code> drain all pending Pi steering messages at the next model boundary, keep legacy one-at-a-time steering as <code>queue</code>, and add a dedicated steering queue docs page. Thanks @vincentkoc.</li>
<li>Messages/queue: default active-run queueing to <code>steer</code> with a 500ms followup fallback debounce, and document the queue modes, precedence, and drop policies on the command queue page. Thanks @vincentkoc.</li>
<li>Messages: add global <code>messages.visibleReplies</code> so operators can require visible output to go through <code>message(action=send)</code> for any source chat, while <code>messages.groupChat.visibleReplies</code> stays available as the group/channel override. Thanks @scoootscooob.</li>
<li>Gateway/events: surface <code>spawnedBy</code> on subagent chat and agent broadcast payloads so clients can route child session events without an extra session lookup. (#63244) Thanks @samzong.</li>
<li>Memory/wiki: add agent-facing people wiki metadata, canonical aliases, person cards, relationship graphs, privacy/provenance reports, evidence-kind drilldown, and search modes for person lookup, question routing, source evidence, and raw claims. Thanks @vincentkoc.</li>
<li>Active Memory: add optional per-conversation <code>allowedChatIds</code> and <code>deniedChatIds</code> filters so operators can enable recall only for selected direct, group, or channel conversations while keeping broad sessions skipped. (#67977) Thanks @quengh.</li>
<li>Active Memory: return bounded partial recall summaries when the hidden memory sub-agent times out, including the default temporary-transcript path, so useful recovered context is not discarded. (#73219) Thanks @joeykrug.</li>
<li>Gateway/memory: add a read-only <code>doctor.memory.remHarness</code> RPC so operator clients can preview bounded REM dreaming output without running mutation paths. (#66673) Thanks @samzong.</li>
<li>Providers/NVIDIA: add the NVIDIA provider with API-key onboarding, setup docs, static catalog metadata, and literal model-ref picker support so NVIDIA hosted models can be selected with their provider prefix intact. (#71204) Thanks @eleqtrizit.</li>
<li>Models: suppress explicitly configured openai-codex/gpt-5.4-mini inline entries so a stale models config written by <code>openclaw doctor --fix</code> cannot bypass the manifest capability block and cause repeated assistant-turn failures when the runtime switches to that model on ChatGPT-backed Codex accounts. Conditional suppressions (e.g. qwen Coding Plan endpoint guards) remain bypassable by explicit user configuration. (#74451) Thanks @0xCyda, @hclsys, and @Marvae.</li>
<li>Added SQLite-backed plugin state store (<code>api.runtime.state.openKeyedStore</code>) for restart-safe keyed registries with TTL, eviction, and automatic plugin isolation. Thanks @amknight.</li>
<li>Plugin SDK: mark remaining legacy alias exports and diffs tool/config aliases with deprecation metadata, and add a guard so future legacy alias comments require <code>@deprecated</code> tags. Thanks @vincentkoc.</li>
<li>CLI/QR/dependencies: internalize small terminal progress and QR wrapper helpers while keeping the real QR encoder dependency direct, reducing the default runtime dependency graph without changing QR output behavior. Thanks @vincentkoc.</li>
<li>Dependencies: refresh workspace runtime, plugin, and tooling packages, including ACP, Pi, AWS SDK, TypeBox, pnpm, oxlint, oxfmt, jsdom, pdfjs, ciao, and tokenjuice, while keeping patched ACP behavior and lint gates current. Thanks @mariozechner.</li>
<li>Gateway/dev: run <code>pnpm gateway:watch</code> through a named tmux session by default, with <code>gateway:watch:raw</code> and <code>OPENCLAW_GATEWAY_WATCH_TMUX=0</code> for foreground mode, so repeated starts respawn an inspectable watcher without trapping the invoking agent shell. Thanks @vincentkoc.</li>
<li>Gateway/diagnostics: emit an opt-in startup diagnostics timeline that records gateway lifecycle and plugin-load phases behind a config flag, so slow-start diagnosis no longer requires bespoke instrumentation. Thanks @shakkernerd.</li>
<li>Control UI/i18n: extend the locale registry with new Persian (fa), Dutch (nl), Vietnamese (vi), Italian (it), Arabic (ar), and Thai (th) entries and ship <code>fa</code>, <code>nl</code>, <code>vi</code>, and <code>zh-TW</code> docs glossaries, so the docs translation pipeline and the Control UI language picker stay aligned across surfaces. Thanks @vincentkoc.</li>
<li>Channels: add Yuanbao channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay.</li>
<li>Channels/Yuanbao: update plugin GitHub location to YuanbaoTeam/yuanbao-openclaw-plugin and add "yuanbao" alias to channel catalog. (#74253) Thanks @loongfay.</li>
<li>Docker setup: add <code>OPENCLAW_SKIP_ONBOARDING</code> so automated Docker installs can skip the interactive onboarding step while still applying gateway defaults. (#55518) Thanks @jinjimz.</li>
<li>Security policy: classify media/base64 decode and format-conversion overhead after configured acceptance limits as performance-only for GHSA triage unless a report demonstrates a limit bypass, crash, exhaustion, data exposure, or another boundary bypass. (#74311)</li>
<li>Security/OpenGrep: add a precise OpenGrep rulepack, source-rule compiler, provenance metadata check, and PR/full scan workflows that validate first-party code and rulepack-only changes while uploading SARIF to GitHub Code Scanning. (#69483) Thanks @jesse-merhi.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Providers/OpenAI Codex: preserve existing wrapped Codex streams during OpenAI attribution so PI OAuth bearer injection reaches ChatGPT/Codex Responses, and strip native Codex-only unsupported payload fields without touching custom compatible endpoints. (#75111) Thanks @keshavbotagent.</li>
<li>Agents/tool-result guard: use the resolved runtime context token budget for non-context-engine tool-result overflow checks, so long tool-heavy sessions no longer compact early when <code>contextTokens</code> is larger than native <code>contextWindow</code>. Fixes #74917. Thanks @kAIborg24.</li>
<li>Gateway/systemd: exit with sysexits 78 for supervised lock and <code>EADDRINUSE</code> conflicts so <code>RestartPreventExitStatus=78</code> stops <code>Restart=always</code> restart loops instead of repeatedly reloading plugins against an occupied port. Fixes #75115. Thanks @yhyatt.</li>
<li>Agents/runtime: skip blank visible user prompts at the embedded-runner boundary before provider submission while still allowing internal runtime-only turns and media-only prompts, so Telegram/group sessions no longer leak raw empty-input provider errors when replay history exists. Fixes #74137. Thanks @yelog, @Gracker, and @nhaener.</li>
<li>Auto-reply/group chats: fall back to automatic source delivery when a channel precomputes message-tool-only replies but the <code>message</code> tool is unavailable, so Discord/Slack-style group turns do not silently complete without a visible reply. Fixes #74868. Thanks @kagura-agent.</li>
<li>Browser/gateway: share one browser control runtime across the HTTP control server and <code>browser.request</code>, and refresh browser profile config from the source snapshot, so CLI status/start honors configured <code>browser.executablePath</code>, <code>headless</code>, and <code>noSandbox</code> instead of falling back to stale auto-detection. Fixes #75087; repairs #73617. Thanks @civiltox and @martingarramon.</li>
<li>Agents/subagents: bound automatic orphan recovery with persisted recovery attempts and a wedged-session tombstone, and teach task maintenance/doctor to reconcile those sessions so restart loops no longer require manual <code>sessions.json</code> surgery. Fixes #74864. Thanks @solosage1.</li>
<li>Gateway/startup: skip pre-bind web-fetch provider discovery for credential-free <code>tools.web.fetch</code> config, so Docker/Kubernetes gateways bind even when optional fetch limits are present. Fixes #74896. Thanks @KoykL.</li>
<li>Infra/tmp: tolerate concurrent temp-dir permission repairs by rechecking directories that another process already tightened, so parallel ACP subprocess startup no longer throws <code>Unsafe fallback OpenClaw temp dir</code>. Fixes #66867. Thanks @Kane808-AI and @jarvisz8.</li>
<li>Signal: match group allowlists against inbound Signal group ids as well as sender ids, and process explicitly configured Signal groups without requiring mentions unless <code>requireMention</code> is set. Fixes #53308. Thanks @minupla and @juan-flores077.</li>
<li>Slack: require bot-authored room messages with <code>allowBots=true</code> to come from an explicitly channel-allowlisted bot or from a room where an explicit Slack owner is present, so broad bot relays cannot run unattended. Fixes #59284. Thanks @andrewhong-translucent.</li>
<li>Signal: bound <code>signal-cli</code> installer release and archive downloads with explicit timeouts, declared and streamed size checks, and partial-file cleanup. Fixes #54153. Thanks @jinduwang1001-max and @juan-flores077.</li>
<li>Signal: derive <code>getAttachment</code> HTTP response caps from <code>channels.signal.mediaMaxMb</code> with base64 headroom, so inbound photos and videos no longer drop behind the 1 MiB RPC default. Fixes #73564. Thanks @heyhudson.</li>
<li>Signal: keep the long-lived receive SSE monitor open while idle instead of applying the 10s RPC/check deadline, so <code>signal-cli</code> 0.14.3 event streams no longer reconnect before inbound messages arrive. Fixes #74741. Thanks @fgabelmannjr and @k7n4n5t3w4rt.</li>
<li>Models/OpenAI Codex: restore <code>openai-codex/gpt-5.4-mini</code> for ChatGPT/Codex OAuth PI runs after live OAuth proof, and align the manifest, forward-compat metadata, docs, and regression tests so stale cron and heartbeat configs resolve again. Fixes #74451. Thanks @0xCyda, @hclsys, and @Marvae.</li>
<li>Memory/runtime-deps: retain the native <code>node-llama-cpp</code> runtime only when local memory search is configured, so packaged installs can repair local embeddings without relying on unreachable global npm installs. Fixes #74777. Thanks @LLagoon3.</li>
<li>Plugins/runtime-deps: replace stale symlinked mirror target roots before writing runtime-mirror temp files and skip rewriting already materialized hardlinks, so cross-version container upgrades no longer crash-loop on read-only image-layer paths while warm mirrors do less churn. Fixes #75108; refs #75069. Thanks @coletebou and @xiaohuaxi.</li>
<li>Plugins/runtime-deps: keep bundled provider policy config loading from staging plugin runtime dependencies, so config reads no longer fail on locked-down <code>/var/lib/openclaw/plugin-runtime-deps</code> directories. Fixes #74971. Thanks @eurojojo.</li>
<li>Plugins/runtime-deps: always write a dependency map in generated runtime-deps install manifests, so npm does not crash or prune staged bundled-plugin packages when the plan is empty. Fixes #74949. Thanks @hclsys.</li>
<li>Security/outbound: strip re-formed HTML tags during plain-text sanitization so nested tag fragments cannot leave a CodeQL-detected <code><script></code> sequence behind. Thanks @vincentkoc.</li>
<li>Security/secrets: compare credential bytes with padded timing-safe buffers instead of hashing candidate passwords before equality checks. Thanks @vincentkoc.</li>
<li>Security/QQBot: sanitize debug log arguments before writing to <code>console.*</code>, so gateway payload fields cannot forge extra log lines when debug logging is enabled. Thanks @vincentkoc.</li>
<li>QQBot: unify slash command auth and c2cOnly gating in the command registry, pass <code>allowQQBotDataDownloads</code> when sending slash command file attachments, align clear-storage with actual downloads directory, and add <code>/bot-me</code> to display sender user ID. (#73616) Thanks @cxyhhhhh.</li>
<li>CLI/agents/status: keep <code>openclaw agents</code>, text <code>agents list</code>, and plain text <code>status</code> on read-only metadata paths so human output no longer preloads plugin runtimes or live channel scans before printing. Fixes #74195. Thanks @NianJiuZst.</li>
<li>Agents/local models: derive context-window guard thresholds from the effective model window with 4k/8k safety floors, so small local models are no longer rejected by fixed 16k/32k preflight cutoffs. Fixes #42999. Thanks @chengjialu8888.</li>
<li>PDF extraction: resolve PDF.js standard fonts from the installed package root and pass a filesystem path to the Node fallback extractor, so built-in font PDFs render without <code>file://</code> URL lookup failures. Fixes #51455; carries forward #70936, #54447, and #62175. Thanks @anyech, @JuanRdBO, and @solomonneas.</li>
<li>Media: treat legacy Word/OLE attachments with <code>application/msword</code> or <code>application/x-cfb</code> MIME as binary so printable-looking <code>.doc</code> files are not embedded into prompts as text. Fixes #54176; carries forward #54380. Thanks @andyliu.</li>
<li>Config: accept documented <code>browser.tabCleanup</code> keys in strict root config validation, so configured tab cleanup no longer fails before runtime reads it. Fixes #74577. Thanks @lonexreb and @ezdlp.</li>
<li>Cron: validate disabled job schedule edits before persisting updates, so invalid cron changes no longer partially mutate stored jobs. Fixes #74459. Thanks @yfge.</li>
<li>CLI/cron: warn when <code>openclaw cron add --message</code> omits a nonblank <code>--agent</code>, including blank agent values and session-key jobs, so scheduled agent-turn jobs make default-agent fallback explicit while system events stay quiet. Fixes #42196; carries forward #42245. Thanks @ethanclaw.</li>
<li>CLI/progress: suppress nested progress spinners and line clears while TUI input owns raw stdin, so Crestodian <code>/status</code> no longer disturbs the active input row. (#75003) Thanks @velvet-shark.</li>
<li>Channels/status: keep Telegram, Slack, and Google Chat read-only allowlist/default-target accessors on config-only paths, so status and channel summaries do not resolve SecretRef-backed runtime credentials. Thanks @eusine.</li>
<li>Telegram: use durable message edits for streaming previews instead of native draft state, so generated replies no longer flicker through draft-to-message transitions that look like duplicates. (#75073) Thanks @obviyus.</li>
<li>Telegram: clamp low long-polling client timeouts so configured <code>timeoutSeconds</code> values below the <code>getUpdates</code> poll window no longer force a fresh HTTPS connection every few seconds. Fixes #75114. Thanks @hpinho77.</li>
<li>Active Memory: clarify the deprecated <code>modelFallbackPolicy</code> warning and config help so <code>modelFallback</code> is described as a chain-resolution last resort, not runtime failover. (#74602) Thanks @jeffrey701.</li>
<li>Channels/Discord: keep read-only allowlist/default-target accessors from resolving SecretRef-backed bot tokens, so status and channel summaries no longer fail when tokens are only available in gateway runtime. (#74737) Thanks @eusine.</li>
<li>Gateway/sessions: align session abort wait semantics across <code>chat</code>, <code>agent</code>, and <code>sessions</code> server methods so abort RPCs return after the targeted sessions actually halt instead of resolving early while runs are still draining. (#74751) Thanks @BunsDev.</li>
<li>Agents/output: drop copied inbound metadata-only assistant replay turns before provider replay instead of synthesizing a placeholder, so Telegram and other channels cannot receive <code>[assistant copied inbound metadata omitted]</code> as model output. Fixes #74745. Thanks @adamwdear and @Marvae.</li>
<li>Doctor/memory: suppress skipped embedding-readiness warnings for key-optional providers such as Ollama and LM Studio while preserving timeout and not-ready diagnostics. Fixes #74608 and #73882. Thanks @hclsys.</li>
<li>Channels/groups: preserve observe-only turn suppression for prepared dispatch paths and restore deprecated channel turn runtime aliases, so passive observer/group flows stay silent while older plugins keep compiling. Thanks @vincentkoc.</li>
<li>Feishu: skip empty-text messages (e.g. <code>{"text":""}</code>) that carry no media, so no blank user turn is written to the session and downstream LLM providers cannot reject the request with "messages must not be empty". (#74634) Thanks @xdengli and @hclsys.</li>
<li>Feishu/Bitable: clean up newly created placeholder rows whose fields contain only default empty values while preserving meaningful link, attachment, user, number, boolean, and location values during create-app cleanup. (#73920) Carries forward #40602. Thanks @boat2moon.</li>
<li>macOS app: keep attach-only mode and the Debug Settings launchd toggle marker-only, so launching with <code>--attach-only</code>/<code>--no-launchd</code> no longer uninstalls the Gateway LaunchAgent or drops active sessions. (#72174) Thanks @DolencLuka.</li>
<li>macOS Canvas: stop auto-reloading the current A2UI host during push/eval/snapshot flows, so pushed A2UI content remains visible instead of returning to the empty Canvas shell. Fixes #73337. Thanks @Gr4via.</li>
<li>Plugin SDK: restore the deprecated <code>plugin-sdk/zalouser</code> command-auth facade so published Lark/Zalo plugins that import it load on current hosts. Fixes #74702. Thanks @Goron01.</li>
<li>Plugins/runtime-deps: include bundled provider plugins when <code>models.providers</code>, auth profiles, agent defaults, or subagent model refs configure that provider, while keeping inactive default-enabled provider plugins out of doctor repair. Refs #74307. Thanks @Skeptomenos.</li>
<li>Plugins/runtime: resolve relative plugin <code>api.resolvePath</code> inputs against the plugin root instead of the host working directory, while keeping absolute and home paths user-resolved. Fixes #74718. Thanks @jimdawdy-hub.</li>
<li>Plugins/runtime-deps: refresh mirrored root chunks through a temporary file before replacing the active copy, so failed refreshes do not delete chunks that running plugin imports still need. Thanks @shakkernerd.</li>
<li>Plugins/runtime-deps: prefer <code>require</code> conditional exports when building staged dependency aliases, so CommonJS-only plugin runtime deps such as <code>ws</code> do not resolve to ESM wrappers under Jiti. Fixes #74547. Thanks @aderius.</li>
<li>Bonjour/Gateway: cap flapping advertiser restarts in a sliding window, so mDNS probing/name-conflict loops disable discovery instead of churning indefinitely on constrained hosts. Refs #74209 and #74242. Thanks @ndj888 and @Sanjays2402.</li>
<li>Plugins/runtime-deps: verify staged package entry files before reusing mirrored runtime roots, so browser-control repairs incomplete <code>ajv</code>/MCP SDK installs after update instead of failing after restart on a missing <code>ajv/dist/ajv.js</code>. Refs #74630. Thanks @spickeringlr.</li>
<li>Heartbeat: resolve <code>responsePrefix</code> template variables with the selected provider, model, and thinking context before delivering alerts or suppressing prefixed <code>HEARTBEAT_OK</code> replies. Fixes #43064; repairs #43065; supersedes #46858. Thanks @yweiii and @JunJD.</li>
<li>Memory/LanceDB: show full memory UUIDs in the <code>memory_forget</code> candidate list so agents can pass the displayed ID back to targeted deletion without hitting the full-UUID validator. (#66913) Thanks @amittell.</li>
<li>File-transfer plugin: require canonical read-path preflight authorization for <code>file.fetch</code>, fail closed when <code>dir.fetch</code> preflight entries are missing, absolute, or traversing, and recheck returned archive entries before handing archive bytes to callers. Carries forward #74134. Thanks @omarshahine.</li>
<li>Channels/Feishu: retry file-typed iOS video resource downloads as <code>media</code> after a Feishu/Lark HTTP 502 and preserve the original 502 when the fallback also fails. Fixes #49855; carries forward #50164 and #73986. Thanks @alex-xuweilong.</li>
<li>Providers/Amazon Bedrock: expose the full Claude Opus 4.7 thinking profile (<code>xhigh</code>, <code>adaptive</code>, and <code>max</code>) for Bedrock model refs, while keeping Opus/Sonnet 4.6 on adaptive-by-default, so <code>/think</code> menus and validation match the Anthropic transport behavior. Fixes #74701. Thanks @prasad-yashdeep, @sparkleHazard, @Sanjays2402, and @hclsys.</li>
<li>Plugins/tokenjuice: compile the bundled plugin against tokenjuice 0.7.0's published OpenClaw host types instead of a local compatibility shim, so package contract drift fails in OpenClaw validation before release. Thanks @vincentkoc.</li>
<li>OAuth/secrets: ignore root-level Google OAuth <code>client_secret_*.json</code> downloads so local client-secret files do not appear as commit candidates. (#74689) Thanks @jeongdulee.</li>
<li>Memory: mirror <code>sqlite-vec</code> into packaged bundled-plugin runtime deps for the default memory plugin, so builtin vector search does not lose its SQLite extension after upgrading to 2026.4.27. Fixes #74692. Thanks @mozi1924.</li>
<li>Gateway/startup: bound local discovery advertisement during startup, so a stuck discovery plugin can no longer keep the Gateway from reaching ready. Fixes #73865; refs #74630 and #74633. Thanks @lpendeavors, @moltar-bot, and @Saboor711.</li>
<li>Gateway/models: serve the last successful model catalog while stale reloads refresh in the background, so Gateway control-plane and OpenAI-compatible requests no longer block behind model-provider rediscovery after model config changes. Refs #74135, #74630, and #74633. Thanks @DerFlash, @moltar-bot, and @Saboor711.</li>
<li>CLI/status: resolve read-only channel setup runtime fallback from the packaged OpenClaw dist root, so <code>status --all</code>, <code>status --deep</code>, channel, and doctor paths do not crash when an external channel plugin needs setup metadata. Fixes #74693. Thanks @giangthb.</li>
<li>SDK/events: keep per-run SDK event streams from surfacing duplicate raw chat projection frames, while normalizing chat-only projection frames and preserving raw access through <code>rawEvents</code>. Refs #74704. Thanks @BunsDev.</li>
<li>SDK: report Gateway terminal <code>agent.wait</code> timeout snapshots with lifecycle metadata as <code>timed_out</code> while keeping bare wait deadlines non-terminal. Thanks @clawsweeper.</li>
<li>Google Meet: block managed Chrome intro/test speech until browser health proves the participant is in-call, and expose <code>speechReady</code> diagnostics so login, admission, permission, and audio-bridge blockers no longer look like successful speech. Refs #72478. Thanks @DougButdorf.</li>
<li>Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi.</li>
<li>Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc.</li>
<li>Slack/interactive replies: keep rendered buttons and selects within Slack Block Kit value and count limits, and align command argument select values with Slack's option limit, so overlong agent-authored choices no longer make Slack reject the whole block payload. Thanks @slackapi.</li>
<li>Slack/interactive replies: drop overlong Block Kit button URLs while preserving valid callback values, so malformed link buttons no longer make Slack reject the whole interactive reply. Thanks @slackapi.</li>
<li>Slack/commands: truncate native command argument-menu confirmation text to Slack's dialog limit, so long plugin arg names no longer make fallback buttons render invalid Block Kit payloads. Thanks @slackapi.</li>
<li>Slack/exec approvals: cap native approval metadata context to Slack's element and text limits, so large approval details no longer make Slack reject the approval card. Thanks @slackapi.</li>
<li>Slack/exec approvals: cap native approval update fallback text to Slack's message limit while preserving the rendered approval blocks, so long commands no longer make resolved or expired approval cards stay stale after <code>chat.update</code> rejects <code>msg_too_long</code>. Thanks @slackapi.</li>
<li>Slack/commands: cap native command argument-menu fallback rows to Slack's message block limit, so large plugin choice lists no longer make Slack reject the generated menu. Thanks @slackapi.</li>
<li>Slack/commands: drop fallback command argument buttons whose encoded values exceed Slack's button-value limit, so one oversized plugin choice no longer makes Slack reject the whole menu. Thanks @slackapi.</li>
<li>Slack/messages: merge message-tool presentation and interactive blocks on Slack sends, so buttons and selects are no longer dropped when a structured message body is also present. Thanks @slackapi.</li>
<li>Slack/messages: cap Block Kit fallback text to Slack's send limit while preserving the rendered blocks, so long context fallbacks no longer make rich Slack messages fail with <code>msg_too_long</code>. Thanks @slackapi.</li>
<li>Slack/messages: cap Block Kit fallback text on message edits while preserving the rendered blocks, so long context fallbacks no longer make Slack reject <code>chat.update</code> calls with <code>msg_too_long</code>. Thanks @slackapi.</li>
<li>Channels/WhatsApp: require Baileys outbound message ids before marking auto-replies delivered, so transcript text and ack reactions no longer make failed group replies look sent. Fixes #49225. Thanks @TinyTb.</li>
<li>CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash.</li>
<li>Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme.</li>
<li>Plugin SDK/testing: lazy-load TypeScript from the plugin test-contract runtime and add release checks for critical SDK contract entrypoint imports and bundle size, so published packages fail preflight before shipping ESM-incompatible or oversized contract helpers. Thanks @vincentkoc.</li>
<li>Channels/Microsoft Teams: treat configured <code>19:...@thread.tacv2</code> and legacy <code>19:...@thread.skype</code> team/channel IDs as already resolved during startup, avoiding false <code>channels unresolved</code> warnings while preserving Graph name lookup for display-name entries. Fixes #74683. Thanks @dseravalli.</li>
<li>CLI/browser: preserve parent flags while lazy-loading browser subcommands, so <code>openclaw browser --json open</code> and <code>openclaw browser --json tabs</code> keep machine-readable output after reparsing. Fixes #74574. Thanks @devintegeritsm.</li>
<li>Exec/elevated: preserve <code>turnSourceChannel</code> as <code>messageProvider</code> on approval-followup runs so <code>tools.elevated.allowFrom.<provider></code> checks no longer fail with <code>provider=null</code> after the user approves an async elevated command. Fixes #74646. Thanks @xhd2015.</li>
<li>Plugins/runtime-deps: add <code>openclaw plugins deps</code> inspection and repair with script-free package-manager defaults shared across plugin installers, so operators can repair missing bundled runtime deps without corrupting JSON output or blocking unrelated conflict-free deps. Thanks @vincentkoc.</li>
<li>Agents/output: strip internal <code>[tool calls omitted]</code> replay placeholders from user-facing replies while preserving visible reply whitespace. Fixes #74573. Thanks @blaspat.</li>
<li>Providers/Google Vertex: route authorized_user ADC credentials through OpenClaw's REST transport so Docker installs using gcloud application-default credentials no longer crash in the Google SDK before requests are sent. Fixes #74628. Thanks @frankhal2001-design.</li>
<li>ACP/resolver: fall through to thread-bound session resolution when an explicit <code>--session</code> token cannot be resolved while preserving the bad-token diagnostic when no thread binding exists, so Discord slash commands that auto-fill the current thread ID as the positional ACP target no longer return "Unable to resolve session target" errors. Fixes #66299. Thanks @hclsys, @kindomLee, and @martingarramon.</li>
<li>Agents/sessions: emit a terminal lifecycle backstop when embedded timeout/error turns return without <code>agent_end</code>, so Gateway sessions no longer stay stuck in <code>running</code> after failover surfaces a timeout. Fixes #74607. Thanks @millerc79.</li>
<li>Gateway/diagnostics: include stuck-session reason hints and recovery skip causes in warnings, so operators can tell whether a lane is waiting on active work, queued work, or stale bookkeeping. Thanks @vincentkoc.</li>
<li>Providers/DeepSeek: expose native DeepSeek V4 <code>xhigh</code> and <code>max</code> thinking levels through the provider <code>resolveThinkingProfile</code> hook so <code>/think xhigh|max</code> applies the intended effort instead of falling back to base levels. (#73008) Thanks @ai-hpc.</li>
<li>Agents/Codex: bound embedded-run cleanup, trajectory flushing, and command-lane task timeouts after runtime failures, so Discord and other chat sessions return to idle instead of staying stuck in processing. Thanks @vincentkoc.</li>
<li>Heartbeat/exec: consume successful metadata-only async exec completions silently so Telegram and other chat surfaces no longer ask users for missing command logs after <code>No session found</code>. Fixes #74595. Thanks @gkoch02.</li>
<li>Web fetch: add a documented <code>tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange</code> opt-in and thread it through cache keys and DNS/IP checks so trusted fake-IP proxy stacks using <code>fc00::/7</code> can work without broad private-network access. Fixes #74351. Thanks @jeffrey701.</li>
<li>OpenAI Codex: restore <code>/verbose full</code> persistence and app-server tool-output forwarding, and retry Gateway E2E temp-home cleanup so debug runs do not regress on stale validation or cleanup flakes. Thanks @vincentkoc.</li>
<li>Anthropic/Meridian: preserve text and thinking content seeded on <code>content_block_start</code> in anthropic-messages streams, so <code>[thinking, text]</code> replies no longer persist as empty turns or trigger empty-response fallbacks. Fixes #74410. Thanks @vyctorbrzezowski.</li>
<li>Channels/Matrix: complete the cross-signing handshake on <code>openclaw matrix verify confirm-sas</code> so the operator's other Matrix device clears its <code>Verifying…</code> loop instead of staying stuck after the agent confirms. (#74542) Thanks @nklock.</li>
<li>CLI/status: honor channel-specific model context-window overrides when reporting effective context, so channel-scoped sessions reflect the active window in <code>openclaw status</code>. Thanks @HemantSudarshan.</li>
<li>Sandbox/Docker: tolerate Docker daemon unavailability when sandbox mode is off, so doctor and preflight checks no longer fail on installs that do not run the Docker daemon. Fixes #73671. Thanks @kaseonedge.</li>
<li>Control UI/mobile: persist mobile chat settings through Lit-managed state and route mobile navigation through the same view-state path so chat panel toggles survive transitions on small viewports. Thanks @BunsDev.</li>
<li>Control UI/exports: align sidebar trigger affordances across the resizable divider, mobile layout, and exported-HTML transcript template so the sidebar toggle and exported transcript sidebar render with consistent hit areas and styling. Thanks @BunsDev.</li>
<li>Control UI/chat: disable the page refresh affordance while a chat run is active so accidental refreshes do not abort an in-flight reply. Thanks @Angfr95 and @BunsDev.</li>
<li>Memory/LanceDB: return real memory records from <code>openclaw ltm list</code> (with optional <code>--limit</code> and createdAt ordering) instead of an empty placeholder, so the CLI surface matches the documented LTM listing contract. (#67952) Thanks @zhangyue19921010.</li>
<li>Media: include redacted per-attempt resize failures and resolved model input capabilities in vision-pipeline errors so ARM64 image failures are diagnosable without closing the remaining routing investigation. Refs #74552. Thanks @1yihui.</li>
<li>Control UI/i18n: route zh-CN agent, debug, channel-refresh, and exec-approval copy through the locale source while preserving the English <code>Cron Jobs</code> agent tab label and the security-audit command styling. Carries forward #39692 repair context. Thanks @hepeng154833488 and @vincentkoc.</li>
<li>Auto-reply: honor explicit <code>silentReply.direct: "allow"</code> for clean empty or reasoning-only direct chat turns while keeping the default direct-chat empty-response guard conservative. Fixes #74409. Thanks @jesuskannolis.</li>
<li>OpenAI Codex: send a non-empty Responses input item when a Codex turn only has systemPrompt-backed instructions, avoiding ChatGPT backend 400s from <code>input: []</code>. Fixes #73820. Thanks @woodhouse-bot.</li>
<li>Ollama: normalize provider-prefixed tool-call names at the native stream boundary so Kimi/Ollama calls such as <code>functions.exec</code> dispatch as <code>exec</code> instead of missing configured tools. Fixes #74487. Thanks @afurm and @carreipeia.</li>
<li>Security/audit: resolve configured model aliases before model-tier and small-parameter checks, so alias-based GPT-5/Codex configs no longer report false weak-model warnings. Fixes #74455. Thanks @blaspat.</li>
<li>CLI/agent: isolate Gateway-timeout embedded fallback runs under explicit <code>gateway-fallback-*</code> sessions so accepted Gateway runs cannot race transcript locks or replace the routed conversation session. Fixes #62981. Thanks @HemantSudarshan.</li>
<li>CLI/QR/device-pair: reject malformed public setup URLs before issuing mobile pairing bootstrap tokens, while keeping valid bare host:port setup URLs supported. Thanks @Lucenx9.</li>
<li>Models/UI: hide unauthenticated providers from the default Web chat, <code>/models</code>, and model setup pickers while keeping explicit full-catalog browse paths through <code>view: "all"</code>, <code>/models <provider> all</code>, and <code>models list --all</code>. Fixes #74423. Thanks @guarismo and @SymbolStar.</li>
<li>Ollama: keep explicit local model runs on target-provider runtime hooks when PI discovery is skipped, so one-shot Ollama calls no longer cold-load unrelated provider runtimes before streaming. Fixes #74078. Thanks @sakalaboator.</li>
<li>Slack/prompts: rely on Slack <code>interactiveReplies</code> guidance instead of generic <code>inlineButtons</code> config hints so enabled Slack button directives are not contradicted. Fixes #46647. Thanks @jeremykoerber.</li>
<li>Slack/reactions: treat duplicate <code>already_reacted</code> responses as idempotent success so repeated agent reaction adds no longer surface as tool failures. Fixes #69005. Thanks @shipitsteven and @martingarramon.</li>
<li>Channels/Discord: cool down Cloudflare/Error 1015 HTML 429 REST failures during startup application lookup and gateway metadata fetches, add <code>channels.discord.applicationId</code> as an app-id lookup bypass, sanitize HTML bodies before logging, and honor Retry-After before falling back to a conservative cooldown. Fixes #38853. (#74489) Thanks @djgeorg3 and @Garyko0730.</li>
<li>Slack/tools: expose <code>fileId</code> in the shared message tool schema so <code>download-file</code> can receive Slack attachment IDs from inbound placeholders. Fixes #45574. Thanks @chadvegas.</li>
<li>Exec: reject invalid per-call <code>host</code> values instead of silently falling back to the default target, so hostname-like values fail before commands run. Fixes #74426. Thanks @scr00ge-00 and @vyctorbrzezowski.</li>
<li>Google/Gemini: send non-empty placeholder content when a Gemini run is triggered with empty or filtered user content, avoiding <code>contents is not specified</code> API errors. Thanks @CaoYuhaoCarl.</li>
<li>Heartbeat: preserve non-task <code>HEARTBEAT.md</code> context around <code>tasks:</code> blocks and apply <code>agents.defaults.heartbeat</code> to all agents unless per-agent heartbeat entries restrict scope. Thanks @Sekhar03.</li>
<li>Markdown: preserve paragraph breaks inside loose list items in shared outbound formatting while keeping tight list spacing stable. Thanks @Lucenx9.</li>
<li>Build/Gateway: route restart, shutdown, respawn, diagnostics, command-queue cleanup, and runtime cleanup through one stable gateway lifecycle runtime entry so rebuilt packages do not strand long-running gateways on stale hashed chunks. Carries forward #73964. Thanks @pashpashpash.</li>
<li>Memory/wiki: keep broad shared-source and generated related-link blocks from turning every page into a search hit, cap noisy backlinks, support all-term searches such as people-routing queries, and prefer readable page body snippets over generated metadata. Thanks @vincentkoc.</li>
<li>Cron/Gateway: abort and bounded-clean up timed-out isolated agent turns before recording the timeout, so stale cron sessions cannot leave Discord or other chat lanes stuck in <code>processing</code> after a timeout. Thanks @vincentkoc.</li>
<li>Agents/errors: suppress malformed streaming tool-call JSON fragments before they reach chat surfaces while preserving provider request-validation diagnostics. Fixes #59076; keeps #59080 as duplicate coverage. (#59118) Thanks @singleGanghood.</li>
<li>CLI/models: restore provider-filtered <code>models list --all --provider <id></code> rows for providers without manifest/static catalog coverage, including Anthropic and Amazon Bedrock, while keeping the compatibility fallback off expensive availability and resolver paths. Thanks @shakkernerd.</li>
<li>CLI/models: keep manifest auth-evidence credentials visible across <code>models status</code>, auth probes, and PI model discovery so workspace-scoped provider auth does not disagree between listing, probing, and execution. Thanks @shakkernerd.</li>
<li>CLI/models: move local credential evidence such as Google Vertex ADC into generic plugin manifest setup metadata so the model-list auth index stays declarative without provider-specific runtime branches. Thanks @shakkernerd.</li>
<li>CLI/models: compute the <code>models list</code> Auth column through one command-local provider auth index so row rendering no longer repeats auth profile, env, configured-provider, AWS, or synthetic-auth checks per model row. Thanks @shakkernerd.</li>
<li>CLI/models: move the OpenAI listable catalog into the plugin manifest so <code>models list --all --provider openai</code> uses the manifest fast path instead of loading provider runtime normalization hooks. Thanks @shakkernerd.</li>
<li>CLI/tools: keep the Gateway <code>tools.*</code> RPC namespace out of plugin command discovery and managed proxy startup, so stray commands like <code>openclaw tools effective</code> fail quickly instead of cold-loading plugin metadata. Refs #73477. Thanks @oromeis.</li>
<li>CLI/status: keep default text <code>openclaw status --usage</code> on metadata-only channel scans unless <code>--deep</code> or <code>--all</code> is set, and send stray <code>openclaw tools --help</code> through the precomputed root-help fast path so latency-triage commands avoid plugin/runtime cold loads before printing. Refs #73477 and #74220. Thanks @oromeis and @NianJiuZst.</li>
<li>Agents/diagnostics: trace embedded-run startup and preparation stage timings before model I/O, and warn only on severe slow stages, so Docker/VPS latency reports can identify whether plugin loading, auth/model resolution, tool inventory, bootstrap, MCP/LSP, resource loading, or stream setup is dominating pre-run latency without noisy normal logs. Refs #73428. Thanks @Dimaoggg, @quangtran88, and @Heyvhuang.</li>
<li>Agents/subagents: cache persisted subagent run registry reads by file signature while preserving fresh-parse isolation, so busy gateways stop reparsing unchanged <code>subagents/runs.json</code> on controller/list/status hot paths. Refs #72338. Thanks @argus-as.</li>
<li>Gateway/clients: wait for the event loop to become responsive before opening Gateway WebSocket RPC/probe/client connections while charging that readiness wait to caller timeouts, so Windows deferred module-evaluation stalls no longer turn healthy loopback gateways into false handshake timeouts across status, TUI, ACP, MCP, node-host, and plugin client paths. Refs #74279 and #48270. Thanks @wongcode and @joost-heijden.</li>
<li>Gateway/Windows: read listener command lines via PowerShell before falling back to <code>wmic</code>, so restart health can recognize OpenClaw listeners on modern Windows installs and avoid long anonymous-port waits. Refs #74280. Thanks @zym951223.</li>
<li>Plugins/runtime-deps: record process start-time in bundled dependency install locks and expire recycled-PID locks, so Docker gateway restarts recover from stale <code>.openclaw-runtime-deps.lock</code> directories without waiting through repeated five-minute timeouts. Fixes #74346. (#74361) Thanks @jhsmith409.</li>
<li>Plugins/runtime-deps: memoize packaged bundled runtime dist-mirror preparation after the first successful pass while keeping source-checkout mirrors refreshable, so constrained Docker/VPS installs avoid repeated root scans before chat turns. Refs #73428, #73421, #73532, and #73477. Thanks @Dimaoggg, @oromeis, @oadiazp, @jmfraga, @bstanbury, @antoniusfelix, and @jkobject.</li>
<li>Channels/Discord: treat bare numeric outbound targets that match the effective Discord DM allowlist as user DMs while preserving account-specific legacy <code>dm.allowFrom</code> precedence over inherited root <code>allowFrom</code>. (#74303) Thanks @Squirbie.</li>
<li>Channels/Discord/Slack: share one DM policy/allowlist resolver across runtime, setup, allowlist editing, and doctor repair, so legacy <code>dm.policy</code> / <code>dm.allowFrom</code> compatibility migrates to canonical <code>dmPolicy</code> / <code>allowFrom</code> without divergent access checks. Thanks @Squirbie.</li>
<li>Control UI: make the chat sidebar split divider focusable, keyboard-resizable, ARIA-described, and pointer-event based so sidebar resizing works without a mouse. Thanks @BunsDev.</li>
<li>Agents/usage: keep PI embedded-run telemetry attributed to the resolved model provider instead of the PI harness label, so OpenRouter and other provider-backed turns report the right provider in session usage and traces. Thanks @vincentkoc.</li>
<li>Agents/attribution: send OpenClaw attribution headers on native OpenAI and Codex traffic, including SDK transports, realtime voice and TTS, device-code auth, WHAM usage, and remote embeddings, so PI-origin defaults no longer leak into provider requests. Thanks @vincentkoc.</li>
<li>Agents/auth: keep OAuth auth profiles inherited from the main agent read-through instead of copying refresh tokens into secondary agents, and refresh Codex app-server tokens against the owning store so multi-agent swarms avoid reused refresh-token failures. Fixes #74055. Thanks @ClarityInvest.</li>
<li>Channels/Telegram: honor <code>ALL_PROXY</code> / <code>all_proxy</code> and service-level <code>OPENCLAW_PROXY_URL</code> when constructing the HTTP/1-only Telegram Bot API transport, so Windows and service installs that rely on those proxy settings no longer fall back to direct egress. Fixes #74014; refs #74086. Thanks @SymbolStar.</li>
<li>Channels/Telegram: keep raw host/network-unreachable Bot API connect failures non-fatal and route tagged polling uncaught exceptions through the Telegram restart path, so transient reachability failures no longer kill the Gateway or leave long polling stuck. Fixes #60515; refs #74540. Thanks @HemantSudarshan, @thacid22, and @ewimsatt.</li>
<li>Channels/Telegram: continue polling when <code>deleteWebhook</code> hits a transient network failure but <code>getWebhookInfo</code> confirms no webhook is configured, so startup does not retry cleanup forever after the webhook was already removed. Refs #74086; carries forward #47384. Thanks @clovericbot.</li>
<li>Channels/Telegram: retry native quote replies without <code>reply_parameters.quote</code> when Telegram returns <code>QUOTE_TEXT_INVALID</code>, so stale or truncated quote excerpts no longer drop the whole reply. Fixes #74581. Thanks @moeedahmed.</li>
<li>Channels/Telegram: apply strict safe-send retry to inbound final replies when grammY wraps a pre-connect failure, while leaving ambiguous plain network envelopes single-shot to avoid duplicate visible messages. Fixes #74203. Thanks @nanli2000cn.</li>
<li>Channels/Telegram: surface polling liveness warnings in channel status and doctor when a running long-poller has not completed <code>getUpdates</code> after startup grace or its transport activity is stale, so silent polling failures no longer look clean. Refs #74299. Thanks @lolaopenclaw.</li>
<li>Channels/Telegram: publish webhook runtime state and warn when <code>setWebhook</code> has not completed after startup grace, so webhook-mode accounts no longer look healthy while registration is still failing or retrying. Refs #74299. Thanks @lolaopenclaw and @martingarramon.</li>
<li>Channels/Telegram: bound native command menu <code>deleteMyCommands</code> and <code>setMyCommands</code> Bot API calls and allow the same timeout-triggered transport fallback retry as other startup control calls, so Windows/WSL network stalls cannot leave command sync hanging behind an otherwise running provider. Refs #74086. Thanks @SymbolStar.</li>
<li>ACP/commands: accept forwarded ACP timeout config controls in the OpenClaw bridge, treat unsupported discard-close controls as recoverable cleanup, and restore native <code>/verbose full</code> plus no-arg status behavior, so Discord command menus and nested ACP turns no longer fail on supported session controls. Thanks @vincentkoc.</li>
<li>Codex harness: interrupt and release native app-server turns that go quiet after an OpenClaw dynamic-tool response without sending <code>turn/completed</code>, so Discord and other chat lanes do not stay stuck in <code>processing</code>. Thanks @vincentkoc.</li>
<li>Codex harness: bound OpenClaw dynamic tool responses to 30 seconds and fail closed with an explicit tool result when the app-server bridge would otherwise strand the turn in <code>processing</code>. Thanks @vincentkoc.</li>
<li>TUI/status: clear stale <code>streaming</code> footer state when a final event arrives after the active run was already cleared and no tracked runs remain, while preserving concurrent-run ownership and inactive local <code>/btw</code> terminal handling. Fixes #64825; carries forward #64842, #64843, #64847, and #64862. Thanks @briandevans and @Yanhu007.</li>
<li>Channels/Discord: fail startup closed when Discord cannot resolve the bot's own identity and keep mention gating active when only configured mention patterns can detect mentions, so the provider no longer continues with a missing bot id. Fixes #42219; carries forward #46856 and #49218. Thanks @education-01 and @BenediktSchackenberg.</li>
<li>Channels/Discord: split long CJK replies at punctuation and code-point-safe fallback boundaries so Discord chunking stays readable without corrupting astral characters. Fixes #38597; repairs #71384. Thanks @p3nchan.</li>
<li>TUI: keep the streaming watchdog alive across active tool/lifecycle proof-of-life, pause it during disconnects, and reload history after stale reconnect runs so long-running chats stop flipping to false idle or hanging on stale streaming. Fixes #69081. Thanks @EenvoudJasper.</li>
<li>Browser/gateway: ignore Playwright dialog-close races from <code>Page.handleJavaScriptDialog</code> so browser automation no longer crashes the Gateway when a dialog disappears before Playwright accepts it. (#40067) Thanks @randyjtw.</li>
<li>Cron/Gateway: defer missed isolated agent-turn catch-up out of the channel startup window, so overdue cron work cannot starve Discord or Telegram while providers connect after a restart. Thanks @vincentkoc.</li>
<li>Heartbeat/cron: defer heartbeat turns while cron work is active or queued, add opt-in <code>heartbeat.skipWhenBusy</code> for subagent/nested lane pressure, and retry busy skips without advancing the schedule so local Ollama hosts do not run heartbeat and cron prompts concurrently. Fixes #50773. Thanks @scottgl9.</li>
<li>Agents/thinking: honor configured model <code>compat.supportedReasoningEfforts</code> entries that include <code>xhigh</code>, so custom OpenAI-compatible provider refs expose and validate <code>/think xhigh</code> consistently across command menus, Gateway sessions, agent CLI, and <code>llm-task</code>. Carries forward #48904. Thanks @Milchstrassse and @wufunc.</li>
<li>Vercel AI Gateway: expose provider-owned <code>/think xhigh</code> for trusted OpenAI/Codex upstream refs and Claude adaptive thinking for Anthropic upstream refs, while leaving untrusted namespaced refs on base levels. Carries forward #41561. Thanks @Zcg2021.</li>
<li>Plugins/runtime-deps: prune stale <code>openclaw-unknown-*</code> bundled runtime dependency roots during Gateway startup while keeping recent or locked roots, so old staging debris cannot keep growing across restarts. Thanks @vincentkoc.</li>
<li>Plugins/runtime-deps: include ten more root-package runtime dependencies (<code>@agentclientprotocol/sdk</code>, <code>@lydell/node-pty</code>, <code>croner</code>, <code>dotenv</code>, <code>jiti</code>, <code>json5</code>, <code>jszip</code>, <code>markdown-it</code>, <code>tar</code>, <code>web-push</code>) in <code>MIRRORED_CORE_RUNTIME_DEP_NAMES</code> so they are mirrored into the runtime-deps tree alongside <code>semver</code> and <code>tslog</code>, preventing <code>Cannot find package 'X'</code> failures from core dist code (for example <code>qmd-manager</code>, <code>cron/schedule</code>, <code>infra/archive</code>, <code>infra/push-web</code>, <code>infra/backup-create</code>, <code>process/supervisor/adapters/pty</code>) when no enabled extension owns the dependency. Adds a static drift guard test that scans <code>src/</code> for value imports of root-package deps and fails CI when one is missing from the mirror allowlist or extension-owned set. Refs #74199. Thanks @maxpuppet.</li>
<li>Ollama: compose caller abort signals with guarded-fetch timeouts for native <code>/api/chat</code> streams, so <code>/stop</code> and early cancellation still interrupt local Ollama requests that also carry provider timeout budgets. Refs #74133. Thanks @obviyus.</li>
<li>Doctor/TTS: migrate legacy <code>messages.tts.enabled</code>, agent TTS, channel TTS, and voice-call plugin TTS toggles to <code>auto</code> mode during <code>openclaw doctor --fix</code>, matching the documented TTS config contract. Thanks @vincentkoc.</li>
<li>CLI/logs: fall back to the configured Gateway file log when implicit loopback Gateway connections close or time out before or during <code>logs.tail</code>, so <code>openclaw logs</code> still works while diagnosing local-model Gateway disconnects. Refs #74078. Thanks @sakalaboator.</li>
<li>MCP/plugins: stringify non-array plugin tool results with chat-content coercion instead of default object stringification, so MCP callers receive useful JSON/text content from plugin tools. Thanks @vincentkoc.</li>
<li>Active Memory/QMD: make gateway-start QMD refresh opt-in via <code>memory.qmd.update.startup</code>, keep normal memory access lazy, preserve interactive file watching, and align watcher dependency/build ignores with QMD's scanner so cold gateway startup no longer imports or initializes QMD by default. Thanks @codexGW.</li>
<li>Channels/Discord: remove Discord-owned queued-run timeout replies through the shared channel lifecycle queue while preserving message ordering and compatibility timeout constants, so long Discord turns stay governed by session/tool/runtime lifecycle instead of channel fallback errors. Thanks @codexGW.</li>
<li>Agents/tools: clamp <code>process.poll</code> waits to 30 seconds, advertise that cap in the tool schema, and honor abort signals while waiting, so long command polls cannot pin agent responsiveness after cancellation. Thanks @vincentkoc.</li>
<li>Plugin SDK: add tracked Discord component-message helpers and a Telegram account-resolution compatibility facade, so existing plugins using those subpaths resolve while new plugins stay on generic channel SDK contracts. Thanks @vincentkoc.</li>
<li>Shared labels: preserve Unicode combining marks and NFC-equivalent accented text in group/channel slug normalization so non-Latin labels no longer lose meaningful characters. Fixes #58932; carries forward #58942 and #58995. Thanks @fengqing-git, @Starhappysh, and @koen666.</li>
<li>Channels/Telegram: include probed video width and height when sending regular Telegram videos, so portrait clips render with the correct orientation instead of being stretched by clients. (#18915) Thanks @storyarcade.</li>
<li>Docs/Hetzner: clarify that SSH tunnel access requires <code>AllowTcpForwarding local</code> before running <code>ssh -L</code>, so hardened VPS sshd configs do not block loopback Gateway access. Fixes #54557; carries forward #54564; refs #54954. Thanks @satishkc7, @blackstrype, and @Aftabbs.</li>
<li>Agents/config: preserve authored <code>agents.defaults.params</code> and per-model <code>agents.defaults.models[].params</code> during narrowed internal config writes, so OpenAI transport overrides such as <code>transport: "sse"</code> and <code>openaiWsWarmup: false</code> are not stripped from <code>openclaw.json</code>. Fixes #73607; refs #73428. Thanks @quangtran88.</li>
<li>Agents/model config: resolve per-model extra params through canonical model keys while preserving legacy double-prefixed fallback entries, so provider-prefixed model ids such as <code>openrouter/auto</code> keep their configured runtime params. (#44319) Thanks @HenryXiaoYang.</li>
<li>Gateway/shutdown: report structured shutdown warnings and HTTP close timeout warnings through <code>ShutdownResult</code> while preserving lifecycle hook hardening. Carries forward #41296. Thanks @edenfunf.</li>
<li>Control UI: keep Agents Overview and config-form select dropdowns on their configured value after options render while preserving inherited agent model placeholders. Fixes #40352; carries forward #52948. Thanks @xiaoquanidea.</li>
<li>Agents/exec: launch zsh, bash, and fish host exec shells with startup files suppressed while preserving existing PATH fallbacks, so daemon env is not overridden by shell startup files. Carries forward #40200; fixes #40179. Thanks @NewdlDewdl.</li>
<li>Plugins/QA: prebuild the private QA channel runtime before plugin gauntlet source runs so wrapper CPU/RSS measurements are not polluted by private QA dist rebuild work. Thanks @vincentkoc.</li>
<li>Plugins/QA: add a Kitchen Sink plugin gauntlet that installs the external package, checks command inventory, MCP tools, channel status, provider turns, gateway RSS, CPU, and fatal log anomalies. Thanks @vincentkoc.</li>
<li>Plugins/config: reuse the bundled plugin alias scan within a single config normalization pass, so Kitchen Sink-style plugin configs no longer peg Gateway CPU by repeatedly rescanning bundled metadata before agent turns. Thanks @vincentkoc.</li>
<li>Plugins/channels: reject malformed runtime channel registrations that omit required config helpers before they can poison channel status. Thanks @vincentkoc.</li>
<li>MCP/plugins: serialize raw plugin tool return values through the plugin-tools MCP bridge so Kitchen Sink-style tools no longer surface <code>undefined</code> content. Thanks @vincentkoc.</li>
<li>Gateway/reload: bound default restart deferral and SIGUSR1 restart drain to five minutes while preserving explicit <code>deferralTimeoutMs: 0</code> indefinite waits, so stale active work accounting cannot block config reloads forever. Thanks @vincentkoc.</li>
<li>Active Memory: register the prompt-build hook with the configured recall timeout plus setup grace instead of the 150s maximum budget, so default memory recall cannot delay turn startup for multiple minutes. Thanks @vincentkoc.</li>
<li>Gateway/readiness: include an <code>eventLoop</code> diagnostic block in local or authenticated <code>/readyz</code> responses with event-loop delay (p99 and max), event-loop utilization, CPU core ratio, and a <code>degraded</code> flag, so operators can see when slow startups or runaway turns stall the event loop. Thanks @vincentkoc.</li>
<li>Gateway/agents: schedule accepted agent runs after the accepted RPC frame has a chance to flush, so pre-turn prompt/context work is less likely to starve immediate <code>agent.wait</code> callers. Thanks @vincentkoc.</li>
<li>CLI/update: tolerate stale memory-runtime import failures during best-effort CLI process teardown, so <code>openclaw update</code> replacing hashed runtime chunks before the finalizer runs no longer surfaces as exit-time <code>Cannot find module</code> noise. Thanks @vincentkoc.</li>
<li>CLI/channels logs: reuse the rolling log-file resolver so <code>openclaw channels logs</code> falls back to the active dated log across date boundaries without reading unrelated custom log files. Fixes #42875; carries forward #42904 and #43043. Thanks @ethanclaw and @wdskuki.</li>
<li>CLI/update: skip tracked plugins disabled in config during post-update plugin sync before npm, ClawHub, or marketplace update checks, preserving their install records without failing the update. Fixes #73880. Thanks @islandpreneur007.</li>
<li>Control UI: fix Peak Error Hours showing incorrect hourly rates when the browser's timezone observes DST, by storing hourly message counts with UTC date keys and using DST-aware <code>Date.getHours()</code> for local conversion. Also extract <code>accumulateMessageCounts</code> helper to reduce duplicated daily/hourly aggregation logic. (#49396) Thanks @konanok.</li>
<li>iMessage: normalize known leading attributedBody corruption markers on sent-message echo text keys so delayed reflected echoes with U+FFFD/U+FFFE/U+FFFF/FEFF prefixes are dropped without collapsing interior text. Fixes #59973; carries forward #59980 and #62191. Thanks @neeravmakwana and @maguilar631697.</li>
<li>Security/audit: recognize dangerous node command IDs as valid <code>gateway.nodes.denyCommands</code> entries, so audit only warns on real typos or unsupported patterns. (#56923) Thanks @chziyue.</li>
<li>Cron: treat implicit text payloads with agent-turn overrides as agent turns, preserving model overrides for scheduled text prompts instead of pruning them as system events. Fixes #28905. (#64060) Thanks @liaoandi.</li>
<li>Telegram/exec approvals: stop treating general Telegram chat allowlists and <code>defaultTo</code> routes as native exec approvers; Telegram now uses explicit <code>execApprovals.approvers</code> or owner identity from <code>commands.ownerAllowFrom</code>, matching the first-pairing owner bootstrap path. Thanks @pashpashpash.</li>
<li>Plugins/providers: keep Gateway startup primary-model discovery on metadata-only provider entries and reuse active non-speech capability providers even with explicit plugin entries, avoiding unnecessary provider registry loads during startup and media capability checks. Fixes #73729, #73835, and #73793; carries forward #73853 and #73794. Thanks @sg1416-zg, @brokemac79, and @poolside-ventures.</li>
<li>Chat commands: route sensitive group <code>/diagnostics</code> and <code>/export-trajectory</code> approvals and results to a private owner route, preferring same-surface DMs before falling back to the first configured owner route, so Discord group invocations can land in Telegram when that is the primary owner interface. Thanks @pashpashpash.</li>
<li>Gateway/hooks: keep successful <code>deliver:false</code> agent hooks silent, log a hook audit record for suppressed success announcements, and suppress fallback summaries after attempted hook delivery while still surfacing failed hook runs. Repairs #55761; builds on #36332 and #49234. Thanks @EffortlessSteven, @cioclawcode, and @BrennerSpear.</li>
<li>Plugin SDK/Discord: restore a deprecated <code>openclaw/plugin-sdk/discord</code> compatibility facade and the legacy compat group-policy warning export for the published <code>@openclaw/discord@2026.3.13</code> package, covering its config, account, directory, status, and thread-binding imports while keeping new plugins on generic SDK subpaths. Fixes #73685; supersedes #73703. Thanks @rderickson9 and @SymbolStar.</li>
<li>Channels/Discord: suppress duplicate gateway monitors when multiple enabled accounts resolve to the same bot token, preferring config tokens over default env fallback and reporting skipped duplicates as disabled. Supersedes #73608. Thanks @kagura-agent.</li>
<li>CLI/health: build channel health summaries from inspected credential metadata plus runtime state, so <code>openclaw health --json</code> reports Discord <code>running</code>, <code>connected</code>, and <code>tokenSource</code> consistently with channel status. Fixes #44354. Thanks @ferenc-acs.</li>
<li>Control UI/Talk: decode Google Live binary WebSocket JSON frames and stop queued browser audio on interruption or shutdown, so browser Talk leaves <code>Connecting Talk...</code> and barge-in no longer plays stale audio. Fixes #73601 and #73460; supersedes #73466. Thanks @Spolen23 and @WadydX.</li>
<li>Channels/Discord: ignore stale route-shaped conversation bindings after a Discord channel is reconfigured to another agent, while preserving explicit focus and subagent bindings. Fixes #73626. Thanks @ramitrkar-hash.</li>
<li>Agents/bootstrap: pass pending BOOTSTRAP.md contents through the first-run user prompt while keeping them out of privileged system context, and show limited bootstrap guidance when workspace file access is unavailable. Fixes #73622. Thanks @mark1010.</li>
<li>ACP/tasks: classify parent-owned ACP sessions as background work regardless of persistent runtime mode, and close terminal stale ACP sessions when no active binding remains, so delegated ACP output reports through the parent task notifier instead of acting like a normal foreground chat session. Refs #73609. Thanks @joerod26.</li>
<li>Tasks: keep terminal mirrored TaskFlow timestamps pinned to task completion time and let maintenance repair stale mirrors, so ACP terminal delivery updates no longer leave inconsistent flow audits. Refs #73609. Thanks @joerod26.</li>
<li>Gateway/sessions: add conservative stuck-session recovery that releases only stale session lanes while active embedded runs, reply operations, and lane tasks remain serialized, so queued follow-ups can drain without aborting legitimate long-running turns. Refs #73581, #73655, #73652, #73705, #73647, #73602, #73592, and #73601. Thanks @WS-Q0758, @bryangauvin, @spenceryang1996-dot, @bmilne1981, @mattmcintyre, @Vksh07, and @Spolen23.</li>
<li>Plugins: cache unchanged plugin manifest loads by file signature, reducing repeated JSON/JSON5 parsing and manifest normalization in bursty startup and runtime registry paths. Refs #73532 and #73647; carries forward #73678. Thanks @TheDutchRuler.</li>
<li>Plugins/runtime-deps: cache unchanged bundled runtime mirror dist-file materialization decisions and close file-lock handles on owner-write failures, reducing repeated startup chunk scans and avoiding FileHandle-GC recovery stalls. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
<li>Plugins/runtime-deps: retry and defer transient cleanup failures for owned runtime staging directories so CLI startup no longer aborts after a successful bundled dependency swap. Refs #73903. Thanks @bobfreeman1989.</li>
<li>Plugins/runtime-deps: cache bundled runtime-deps JSON/package files by file signature, reducing repeated staged-runtime metadata reads during bundled channel startup. Refs #73647 and #73705. Thanks @mattmcintyre and @bmilne1981.</li>
<li>Plugins/runtime-deps: delegate bundled plugin dependency staging to complete npm/pnpm install plans with durable runtime state, removing retained-manifest and source-checkout cache reconciliation from Gateway startup. Refs #73532. Thanks @oadiazp, @bstanbury, and @jmfraga.</li>
<li>Plugins/runtime-deps: replace Gateway-start root chunk dependency inference with explicit mirrored-root dependency metadata, reducing staged runtime scans while preserving lazy per-plugin installs. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
<li>Plugins/runtime-deps: run pnpm staged installs outside the repository workspace and disable pnpm release-age gates for exact bundled runtime dependency materialization, so bundled plugin dependency repair writes packages into the generated stage without blocking fresh packaged dependencies. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
<li>CLI/TUI: keep <code>chat.history</code> off model-catalog discovery so initial Gateway-backed TUI history loads cannot block behind slow provider/plugin model scans on low-core hosts. Refs #73524. Thanks @harshcatsystems-collab.</li>
<li>Channels/WhatsApp: flag recently reconnected linked accounts in channel status even when the socket is currently healthy, so flapping WhatsApp Web sessions no longer look clean after a brief reconnect. Refs #73602. Thanks @Vksh07.</li>
<li>Channels/WhatsApp: log shared dispatcher delivery failures with reply kind, message id, chat id, and connection id, so typing-without-send reports can identify whether the WhatsApp send path rejected a generated reply. Refs #74269. Thanks @tomcosta-git.</li>
<li>Feishu: suppress distinct late <code>final</code> text deliveries after a streaming card has already closed, while keeping media attachments deliverable, so late-finals no longer reopen duplicate Feishu cards. Fixes #71977. (#72294) Thanks @MonkeyLeeT.</li>
<li>Gateway: expose <code>gateway.handshakeTimeoutMs</code> in config, schema, and docs while preserving <code>OPENCLAW_HANDSHAKE_TIMEOUT_MS</code> precedence, so loaded or low-powered hosts can tune local WebSocket pre-auth handshakes without patching dist files. Supersedes #51282; refs #73592 and #73652. Thanks @henry-the-frog.</li>
<li>Gateway/TUI/status: align configured and env-based WebSocket handshake budgets across local clients, probes, and fallback RPCs while preserving explicit status timeouts and paired-device auth fallback, so slow local gateways are not marked unreachable by a shorter client watchdog. Refs #73524, #73535, #73592, and #73602. Thanks @harshcatsystems-collab, @DJBlackhawk, and @Vksh07.</li>
<li>Gateway/startup: return retryable <code>UNAVAILABLE</code> during the sidecar startup window and keep CLI/TUI/status clients retrying inside their existing timeout budget, so early connects no longer surface as terminal handshake failures. Fixes #73652. Thanks @spenceryang1996-dot.</li>
<li>Gateway/proxy: bypass inherited proxy environment for local Gateway control-plane WebSockets to <code>localhost</code> as well as loopback IPs, so Windows/WSL proxy settings cannot intercept local CLI/TUI Gateway connections. Supersedes #73474; refs #73602. Thanks @DhtIsCoding.</li>
<li>Doctor/Gateway: use a lightweight <code>status</code> RPC without channel summary work for doctor Gateway liveness, so slow health snapshots do not falsely drive service restart repair. Fixes #64400; supersedes #64511. Thanks @CHE10X and @EronFan.</li>
<li>Agents/auth: scope external CLI credential discovery to configured providers during model auth status and startup prewarm, so opencode-only and other single-provider gateways do not block on unrelated Claude CLI Keychain probes. Fixes #73908. Thanks @Ailuras.</li>
<li>Agents/model selection: resolve slash-form aliases before provider/model parsing and keep alias-resolved primary models subject to transient provider cooldowns, so cron and persisted sessions do not retry cooled-down raw aliases. Fixes #73573 and #73657. Thanks @akai-shuuichi and @hashslingers.</li>
<li>Agents/Claude CLI: reuse already-cached macOS Keychain credentials for no-prompt Claude credential reads, so doctor/runtime checks do not miss fresh interactive Claude auth. Fixes #73682. Thanks @RyanSandoval.</li>
<li>Agents/Claude CLI doctor: scope workspace and project-dir checks to agents that actually use the Claude CLI runtime, so non-default Claude agents no longer make the default agent look Claude-backed. Fixes #73903. Thanks @bobfreeman1989.</li>
<li>Gateway/sessions: expose effective agent runtime metadata on session rows, <code>sessions.patch</code>, and local <code>openclaw sessions --json</code>, while keeping Claude CLI-backed rows on the canonical model provider so runtime backend and model identity are no longer conflated. Fixes #73090. Thanks @vishutdhar.</li>
<li>Gateway/auth status: scope external CLI credential overlays to configured providers, runtimes, or profiles and keep status reads off new Keychain prompts, so single-provider Gateway configs no longer probe unrelated Claude/Codex/MiniMax auth on startup. Fixes #73908. Thanks @Ailuras.</li>
<li>Agents/runtime status: expose effective agent runtime metadata in <code>agents.list</code>, Control UI agent panels, and <code>/agents</code>, and avoid rendering stale or cumulative CLI token totals as live context usage. Fixes #73660, #73578, and #45268. Thanks @spartman, @DashLabsDev, and @xyooz.</li>
<li>Agents/transcripts: strip empty assistant text blocks while preserving valid text, images, and signatures, so Anthropic-style providers no longer reject sanitized transcript turns. Fixes #73640. Thanks @jowhee327.</li>
<li>Gateway/sessions: preserve session keys on hidden lifecycle events so channel-routed runs still persist terminal session state and do not strand session status as running after Codex turn completion. Thanks @cathrynlavery.</li>
<li>Providers/Bedrock: omit deprecated <code>temperature</code> for Claude Opus 4.7 Bedrock model ids, named and application inference profiles, including dotted <code>opus-4.7</code> refs, and classify the nested validation response for failover. Fixes #73663. Thanks @bstanbury.</li>
<li>Gateway: raise the preauth/connect-challenge timeout to 15s so cold CLI starts on slower hosts have more time to process the WebSocket challenge before the Gateway closes the connection. Fixes #51469; refs #73592 and #62060. Thanks @GothicFox and @jackychen-png.</li>
<li>CLI/status: fall back to a bounded local <code>status</code> RPC when loopback detail probes time out or report unknown capability, so reachable local gateways are no longer marked unreachable by slow read diagnostics. Fixes #73535; refs #48360, #62762, #51357, and #42019. Thanks @RacecarGuy, @justinschille, @DJBlackhawk, @tianyaqpzm, and @0xrsydn.</li>
<li>CLI/gateway: reuse cached paired-device auth during <code>gateway probe</code> and report post-connect diagnostic failures as degraded reachability, so healthy local gateways are no longer marked unreachable after loopback auth or read timeouts. Fixes #48360. Thanks @RacecarGuy.</li>
<li>Channels/Discord: give Discord Gateway WebSocket handshakes a 30s timeout so stalled TLS/network transitions emit an error and Carbon can continue its reconnect loop instead of leaving the bot silent until restart. Refs #50046. Thanks @codexGW.</li>
<li>Mattermost/WebSocket: send protocol ping/pong keepalives and terminate stale sessions when pongs stop arriving, so silent TCP drops reconnect instead of leaving monitoring idle. Fixes #41837; carries forward #57621; refs #50138, #44160, and #51104. Thanks @JasonWang1124.</li>
<li>Channels/Telegram: suppress standalone failed edit/write warning payloads when a user-facing assistant error reply already covers the turn, while keeping unresolved mutating failures visible behind success-looking or suppressed-error replies. Fixes #39631; refs #73750; carries forward #39636 and #39717; leaves #39406 for configurable delivery policy. Thanks @Bartok9 and @Bortlesboat.</li>
<li>Control UI/agents: persist the Set Default action through <code>agents.list[].default</code> instead of writing the unsupported <code>agents.defaultId</code> field, so saved default-agent changes survive config validation. Fixes #65565; carries forward #72585. Thanks @luyao618.</li>
<li>NVIDIA/NIM: persist the <code>NVIDIA_API_KEY</code> provider marker and mark bundled NVIDIA Chat Completions models as string-content compatible, so NIM models load from <code>models.json</code> and OpenAI-compatible subagent calls send plain text content. Fixes #73013 and #50107; refs #73014. Thanks @bautrey, @iot2edge, @ifearghal, and @futhgar.</li>
<li>Channels/Discord: let text-only configs drop the <code>GuildVoiceStates</code> gateway intent and expose a bounded <code>/gateway/bot</code> metadata timeout with rate-limited fallback logs, reducing idle CPU and warning floods. Fixes #73709 and #73585. Thanks @sanchezm86 and @trac3r00.</li>
<li>Agents/sessions: mark same-turn <code>sessions_send</code> and A2A reply prompts with an inter-session <code>isUser=false</code> envelope before they reach the model, so foreign session output no longer lands as bare active user text. Fixes #73702; refs #73698, #73609, #73595, and #73622. Thanks @alvelda.</li>
<li>Channels/Telegram: fail closed when account-level public DM settings conflict with a restrictive top-level <code>allowFrom</code>, and require an effective wildcard before <code>dmPolicy="open"</code> behaves as public access. Fixes #73756; refs #73698. Thanks @Hilo-Hilo and @xace1825.</li>
<li>Channels/security: move open-DM allowlist semantics into the shared policy helpers and align Discord, Slack, Mattermost, Matrix, Feishu, LINE, IRC, Google Chat, Zalo, Zalo User, QQ Bot, and Synology Chat so <code>dmPolicy="open"</code> is public only with an effective wildcard and otherwise still respects sender allowlists. Refs #73756 and #73698. Thanks @Hilo-Hilo and @xace1825.</li>
<li>ACP/tasks: sweep orphaned parent-owned ACP sessions whose task records are gone, preserving bound persistent sessions but clearing unbound stale ACPX metadata so old child sessions cannot silently respawn into chat. Fixes #73609. Thanks @joerod26.</li>
<li>Outbound/security: strip known internal runtime scaffolding such as <code><system-reminder></code> and <code><previous_response></code> at the final channel delivery boundary and keep Discord output on targeted tag stripping, so degraded harness replies cannot leak those tags to users. Fixes #73595. Thanks @gabrielexito-stack and @martingarramon.</li>
<li>Security/Telegram: load Telegram security adapters in read-only audit/doctor, audit malformed Telegram DM <code>allowFrom</code> entries even when groups are disabled, and keep allowlist DM audits from counting stale pairing-store senders, so public/shared-DM risk checks stay accurate. Refs #73698. Thanks @xace1825.</li>
<li>Plugins: remove hidden manifest, provider-owner, bootstrap, and channel metadata caches so plugin installs, manifest edits, and bundled-root changes are visible on the next metadata read while keeping runtime/module loader caches for actual plugin code. Thanks @shakkernerd.</li>
<li>CLI/plugins: use plugin metadata snapshots for install slot selection and add opt-in plugin lifecycle timing traces, so plugin install avoids runtime-loading the plugin registry for metadata-only decisions. Thanks @shakkernerd.</li>
<li>fix(plugins): restrict bundled plugin dir resolution to trusted package roots. (#73275) Thanks @pgondhi987.</li>
<li>fix(security): prevent workspace PATH injection via service env and trash helpers. (#73264) Thanks @pgondhi987.</li>
<li>Active Memory: allow <code>allowedChatTypes</code> to include explicit portal/webchat sessions and classify <code>agent:...:explicit:...</code> session keys before opaque session ids can shadow the chat type. Fixes #65775. (#66285) Thanks @Lidang-Jiang.</li>
<li>Active Memory: allow the hidden recall sub-agent to use both <code>memory_recall</code> and the legacy <code>memory_search</code>/<code>memory_get</code> memory tool contract, so bundled <code>memory-lancedb</code> recall works without breaking the default <code>memory-core</code> path. Fixes #73502. (#73584) Thanks @Takhoffman.</li>
<li>fix(device-pairing): validate callerScopes against resolved token scopes on repair [AI]. (#72925) Thanks @pgondhi987.</li>
<li>Active Memory docs: document the <code>cacheTtlMs</code> 1000-120000 ms range and 15000 ms default so setup snippets do not lead users past the schema limit. Fixes #65708. (#65737) Thanks @WuKongAI-CMU.</li>
<li>fix(agents): canonicalize provider aliases in byProvider tool policy lookup [AI]. (#72917) Thanks @pgondhi987.</li>
<li>fix(security): block npm_execpath injection from workspace .env [AI-assisted]. (#73262) Thanks @pgondhi987.</li>
<li>Tools/web_fetch: decode response bodies from raw bytes using declared HTTP, XML, or HTML meta charsets before extraction, so Shift_JIS and other legacy-charset pages no longer return mojibake. Fixes #72916. Thanks @amknight.</li>
<li>Active Memory: skip payload-less <code>memory_search</code> transcript tool results when building debug telemetry, so newer empty entries no longer hide the latest useful debug payload. (#68773) Thanks @SimbaKingjoe.</li>
<li>Active Memory: keep recall setup time from consuming the configured model timeout while giving the hook runner an explicit bounded budget for the plugin, so slow embedded-run setup no longer causes immediate recall timeouts. Fixes #72606. (#72620) Thanks @hyspacex.</li>
<li>Channels/Discord: bound message read/search REST calls, route those actions through Gateway execution, and fall back to <code>CommandTargetSessionKey</code> for inbound hook session keys so Discord reads do not hang and hooks still fire when <code>SessionKey</code> is empty. Fixes #73431. (#73521) Thanks @amknight.</li>
<li>Plugins/media: auto-enable provider plugins referenced by <code>agents.defaults.imageGenerationModel</code>, <code>videoGenerationModel</code>, and <code>musicGenerationModel</code> primary/fallback refs, so configured Google and MiniMax media providers do not stay disabled behind a restrictive plugin allowlist. Thanks @vincentkoc.</li>
<li>Memory-core/dreaming: retry managed dreaming cron registration after startup when the cron service is not reachable yet, so the scheduled Memory Dreaming Promotion sweep recovers without waiting for heartbeat traffic. Fixes #72841. Thanks @amknight.</li>
<li>Acpx/runtime: validate the runtime session mode at the <code>AcpxRuntime.ensureSession</code> wrapper boundary so callers that pass anything other than <code>persistent</code> or <code>oneshot</code> get a clear <code>ACP_INVALID_RUNTIME_OPTION</code> error instead of silently round-tripping through the encoded handle as a default <code>persistent</code> mode and later throwing <code>SessionResumeRequiredError</code>. Investigation context: #73071. (#73548) Thanks @amknight.</li>
<li>CLI/infer: keep web-search fallback on missing provider API keys, preserve structured validation errors from the selected provider, and let per-request image describe prompts override configured media-entry prompts. (#63263) Thanks @Spolen23.</li>
<li>Chat commands: include configured model-catalog reasoning metadata when building <code>/think</code> argument menus so Ollama Cloud and other provider-owned reasoning models show supported levels instead of only <code>off</code>. Fixes #73515; supersedes #73568. Thanks @danielzinhu99 and @neeravmakwana.</li>
<li>Channels/Telegram: suppress generic tool-progress chatter when preview streaming is off, so non-streaming Telegram turns only deliver final replies while approvals, media, and errors still route normally. Refs #72363 and #72482. Thanks @neeravmakwana and @SweetSophia.</li>
<li>CLI/model probes: add repeatable image <code>--file</code> inputs to <code>infer model run</code> for local and gateway multimodal model smokes, so vision models such as Ollama Qwen VL and Gemini can be tested through the raw model-probe surface. Fixes #63700. Thanks @cedricjanssens.</li>
<li>CLI/model probes: request trusted operator scope for <code>infer model run --gateway --model <provider/model></code> so Gateway raw model smokes can use one-off provider/model overrides instead of being rejected before provider auth resolution. Fixes #73759. Thanks @chrislro.</li>
<li>CLI/image describe: pass <code>--prompt</code> and <code>--timeout-ms</code> through <code>infer image describe</code> and <code>describe-many</code>, so custom vision instructions and slow local model budgets reach media-understanding providers such as Ollama, OpenAI, Google, and OpenRouter. Refs #63700. Thanks @cedricjanssens.</li>
<li>Model selection: include the rejected provider/model ref and allowlist recovery hint when a stored session override is cleared, so local model selections such as Gemma GGUF variants do not fall back to the default with a generic message. Refs #71069. Thanks @CyberRaccoonTeam.</li>
<li>OpenAI-compatible providers: drop malformed event-only or blank-data SSE frames before the OpenAI SDK stream parser sees them, so proxies that split <code>event:</code> from <code>data:</code> no longer crash streaming runs with <code>Unexpected end of JSON input</code>. Fixes #52802. Thanks @LyHug.</li>
<li>Gateway/OpenAI-compatible streaming: strip <code><final></code> tags split across streamed model deltas before they reach SSE clients, so <code>/v1/chat/completions</code> no longer emits tag remnants or drops content when final-answer wrappers cross chunk boundaries. Fixes #63325. Thanks @tzwickl.</li>
<li>Ollama: resolve explicitly selected signed-in <code>:cloud</code> models through <code>/api/show</code> when <code>/api/tags</code> omits them, so working models such as <code>gemini-3-flash-preview:cloud</code> and <code>deepseek-v4-pro:cloud</code> do not fail dynamic model resolution before the native <code>/api/chat</code> transport runs. Fixes #73909. Thanks @chtse53.</li>
<li>Discord/exec approvals: keep the local <code>/approve</code> prompt when no native Discord approval runtime is active, and send a manual fallback notice when native approval delivery reaches no targets, so failed DM cards no longer leave approval turns silent or dependent on model-written shell commands. Fixes #73954; carries forward #74027. Thanks @guarismo and @brokemac79.</li>
<li>Local model prompt caching: keep stable Project Context above volatile channel/session prompt guidance and stop embedding current channel names in the message tool description, so Ollama, MLX, llama.cpp, and other prefix-cache backends avoid avoidable full prompt reprocessing across channel turns. Fixes #40256; supersedes #40296. Thanks @rhclaw and @sriram369.</li>
<li>Gateway/OpenAI-compatible API: guard provider policy lookup against runtime providers with non-array <code>models</code> values, so <code>/v1/chat/completions</code> no longer fails with <code>provider?.models?.some is not a function</code>. Fixes #66744; carries forward #66761. Thanks @MightyMoud, @MukundaKatta.</li>
<li>WhatsApp/Web: pass explicit Baileys socket timings into every WhatsApp Web socket and expose <code>web.whatsapp.*</code> keepalive, connect, and query timeout settings so unstable networks can avoid repeated 408 disconnect and opening-handshake timeout loops. Fixes #56365. (#73580) Thanks @velvet-shark.</li>
<li>WhatsApp/Web: recover recently active listeners when a post-408 reconnect keeps receiving transport frames but stops delivering app messages, while keeping group metadata fallback off Baileys sends. Fixes #63855 and #66920; refs #7433, #67986, #70856, #60007, and #72621. Thanks @legonhilltech-jpg, @octopuslabs-fl, @Kanorin-chan, and @stuswan.</li>
<li>Channels/Telegram: persist native command metadata on target sessions so topic, helper, and ACP-bound slash commands keep their session metadata attached to the routed conversation. (#57548) Thanks @GaosCode.</li>
<li>Channels/native commands: keep validated native slash command replies visible in group chats while preserving explicit owner allowlists for command authorization. (#73672) Thanks @obviyus.</li>
<li>Pairing/doctor: bootstrap <code>commands.ownerAllowFrom</code> from the first approved DM pairing when no command owner exists, and have doctor explain missing owners so privileged slash commands are not accidentally unusable after onboarding. Thanks @pashpashpash.</li>
<li>Telegram/exec: infer native exec approvers from <code>commands.ownerAllowFrom</code> and auto-enable the Telegram approval client when an owner is resolvable, so owner-only commands such as <code>/diagnostics</code> can be approved in Telegram without duplicate per-channel approver config. Thanks @pashpashpash.</li>
<li>Auto-reply/session: carry the tail of user/assistant turns into the freshly-rotated transcript on silent in-reply session resets (compaction failure, role-ordering conflict) so direct-chat continuity survives the rebind. Fixes #70853. (#70898) Thanks @neeravmakwana.</li>
<li>Skills: load grouped skill directories such as <code>skills/<group>/<skill>/SKILL.md</code> from configured skill roots while keeping grouped discovery capped for large directories. Fixes #56915. (#72534) Thanks @ottodeng, @MoerAI, and @i010542.</li>
<li>Config: skip malformed non-string <code>env.vars</code> entries before env-reference checks, so config loading no longer crashes on JSON values like numbers or booleans. (#42402) Thanks @MiltonHeYan.</li>
<li>Docker Compose: default missing config and workspace bind mounts to <code>${HOME:-/tmp}/.openclaw</code> so manual compose runs do not create invalid empty-source volume specs. (#64485) Thanks @jlapenna.</li>
<li>Agents/context engines: preserve the child agent's configured <code>agentDir</code> when subagent cleanup re-resolves a context engine, so <code>onSubagentEnded</code> hooks keep operating on the correct per-agent state. (#67243) Thanks @jarimustonen.</li>
<li>Channels/WhatsApp: restrict pairing verification replies to real inbound user content, preventing unsolicited prompts from receipts, typing indicators, presence updates, and other non-message Baileys upserts. Fixes #73797. (#73823) Thanks @hclsys.</li>
<li>Configure/Ollama: show the configured Ollama model allowlist after Cloud only or Cloud + Local setup and skip slow per-model cloud metadata fetches. (#73995) Thanks @obviyus.</li>
<li>Channels/WhatsApp: detect explicit group <code>@mentions</code> again when the bot's own E.164 is in <code>allowFrom</code>, so shared-number setups no longer skip group pings that directly mention the bot. Fixes #49317. (#73453) Thanks @juan-flores077.</li>
<li>WhatsApp/reliability: publish real transport-liveness into WhatsApp channel status and force earlier reconnects on silent transport stalls, so quiet healthy sessions stay connected while wedged sockets recover before the later remote 408 path. (#72656) Thanks @Sathvik-1007.</li>
<li>Core/channels: tighten selected runtime, media, and plugin edge-case handling while preserving existing behavior. Thanks @jesse-merhi.</li>
<li>Channels/WhatsApp: strip leaked plural tool-call XML wrappers on every WhatsApp-visible outbound path and keep channel error payloads out of WhatsApp chats. (#71830) Thanks @rubencu.</li>
<li>Agents/embedded-runner: inject the resolved OAuth bearer (and forward the run abort signal) on the boundary-aware embedded stream fallback so models that route through <code>openai-codex-responses</code> and other boundary-aware transports stop failing with <code>401 Unauthorized: Missing bearer or basic authentication in header</code>. Fixes #73559. (#73588) Thanks @openperf.</li>
<li>Telegram/gateway: bound outbound Bot API calls and cache bundled plugin alias lookup so slow Telegram sends or WSL2 filesystem scans no longer wedge gateway replies. (#74210) Thanks @obviyus.</li>
<li>Configure/GitHub Copilot: reuse existing Copilot auth during configure and show the provider's manifest model catalog in the model picker. (#74276) Thanks @obviyus.</li>
<li>Configure/models: keep the model picker scoped to the selected manifest provider and enable its bundled plugin before catalog lookup, so choosing GitHub Copilot no longer falls back to Ollama or skips the catalog. (#74322) Thanks @obviyus.</li>
<li>Auto-reply/subagents: reject <code>/focus</code> from leaf subagents and scope fallback target resolution to the requesting subagent's children, so subagents cannot bind conversations outside their control boundary. (#73613) Thanks @drobison00.</li>
<li>Gateway/startup: skip inherited workspace startup memory for sandboxed spawned sessions without real-workspace write access, so <code>/new</code> no longer preloads host workspace memory into isolated child runs. (#73611) Thanks @drobison00.</li>
<li>Agents/tool policy: validate caller group IDs against session or spawned context before applying group-scoped tool policies or persisting gateway group metadata, so forged group IDs cannot unlock more permissive tools. (#73720) Thanks @mmaps.</li>
<li>Commands: keep channel-prefixed owner allowlist entries scoped to matching providers so webchat command contexts cannot inherit external channel owners. Thanks @zsxsoft.</li>
<li>Auth/device pairing: bound bootstrap handoff token issuance, redemption, and approved pairing baselines to the documented per-role scope allowlist, so bootstrap approvals cannot persistently grant <code>operator.admin</code>, <code>operator.pairing</code>, or <code>node.exec</code> scopes. Thanks @eleqtrizit.</li>
<li>Providers/GitHub Copilot: support the GUI/RPC wizard device-code auth flow so onboarding from non-TTY clients (gateway RPC bridge, GUI wizards) completes instead of returning empty profiles. Dangerous-state handling now distinguishes <code>access_denied</code> and <code>expired_token</code> from transport errors. (#73290) Thanks @indierawk2k2.</li>
<li>Installer/Linux: warn before switching an unwritable npm global prefix to <code>~/.npm-global</code>, then tell users to run future global updates with <code>npm i -g openclaw@latest</code> without <code>sudo</code> so npm keeps using the redirected user prefix. Fixes #44365; carries forward #50479. Thanks @Sayeem3051.</li>
<li>Gateway/plugins: enable the native <code>require()</code> fast path on Windows for bundled plugin modules so plugin loading uses <code>require()</code> instead of Jiti's transform pipeline, reducing startup from ~39s to ~2s on typical 6-plugin setups. Fixes #68656. (#74173) Thanks @galiniliev.</li>
<li>macOS app: detect stale Gateway TLS certificate pins, automatically repair trusted Tailscale Serve rotations, and surface paired-but-disconnected Mac companion nodes so partial Gateway connections no longer look healthy. Thanks @guti.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.29/OpenClaw-2026.4.29.zip" length="50896802" type="application/octet-stream" sparkle:edSignature="YfQ25zMGgDv8XvHbdlL/s0SMJXyu763l5ppnfjiKOjSyxZY9sfoLaoXthcctFQDXA8isR1EEb/EEausu+XkFCA=="/>
</item>
</channel>
</rss>

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026051200
versionName = "2026.5.12"
versionCode = 2026051700
versionName = "2026.5.17"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -12,6 +12,7 @@ import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewaySession
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
import ai.openclaw.app.gateway.GatewayTlsProbeResult
import ai.openclaw.app.gateway.normalizeGatewayTlsFingerprint
import ai.openclaw.app.gateway.probeGatewayTlsFingerprint
import ai.openclaw.app.node.A2UIHandler
import ai.openclaw.app.node.CalendarHandler
@@ -248,6 +249,7 @@ class NodeRuntime(
val endpoint: GatewayEndpoint,
val fingerprintSha256: String,
val auth: GatewayConnectAuth,
val previousFingerprintSha256: String? = null,
)
private val _isConnected = MutableStateFlow(false)
@@ -260,6 +262,7 @@ class NodeRuntime(
private val _pendingGatewayTrust = MutableStateFlow<GatewayTrustPrompt?>(null)
val pendingGatewayTrust: StateFlow<GatewayTrustPrompt?> = _pendingGatewayTrust.asStateFlow()
private val connectAttemptSeq = AtomicLong(0)
private fun resolveNodeMainSessionKey(agentId: String? = gatewayDefaultAgentId): String {
val deviceId = identityStore.loadOrCreate().deviceId
@@ -1112,23 +1115,58 @@ class NodeRuntime(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
) {
val connectAttemptId = connectAttemptSeq.incrementAndGet()
_pendingGatewayTrust.value = null
val tls = connectionManager.resolveTlsParams(endpoint)
if (tls?.required == true && tls.expectedFingerprint.isNullOrBlank()) {
// First-time TLS: capture fingerprint, ask user to verify out-of-band, then store and connect.
if (tls?.required == true) {
val expectedFingerprint =
tls.expectedFingerprint
?.let(::normalizeGatewayTlsFingerprint)
?.takeIf { it.isNotBlank() }
_statusText.value = "Verify gateway TLS fingerprint…"
scope.launch {
val tlsProbe = tlsFingerprintProbe(endpoint.host, endpoint.port)
if (!isCurrentConnectAttempt(connectAttemptId)) return@launch
val fp =
tlsProbe.fingerprintSha256 ?: run {
_statusText.value = gatewayTlsProbeFailureMessage(tlsProbe.failure)
if (expectedFingerprint == null) {
_statusText.value = gatewayTlsProbeFailureMessage(tlsProbe.failure)
} else {
connectAfterTlsCheck(endpoint = endpoint, auth = auth, connectAttemptId = connectAttemptId)
}
return@launch
}
_pendingGatewayTrust.value =
GatewayTrustPrompt(endpoint = endpoint, fingerprintSha256 = fp, auth = auth)
val observedFingerprint =
normalizeGatewayTlsFingerprint(fp)
.takeIf { it.isNotBlank() }
?: fp
val previousFingerprint = expectedFingerprint?.takeUnless { it == observedFingerprint }
if (expectedFingerprint == null || previousFingerprint != null) {
_pendingGatewayTrust.value =
GatewayTrustPrompt(
endpoint = endpoint,
fingerprintSha256 = observedFingerprint,
auth = auth,
previousFingerprintSha256 = previousFingerprint,
)
return@launch
}
connectAfterTlsCheck(endpoint = endpoint, auth = auth, connectAttemptId = connectAttemptId)
}
return
}
connectAfterTlsCheck(endpoint = endpoint, auth = auth, connectAttemptId = connectAttemptId)
}
private fun isCurrentConnectAttempt(connectAttemptId: Long): Boolean = connectAttemptSeq.get() == connectAttemptId
private fun connectAfterTlsCheck(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
connectAttemptId: Long,
) {
if (!isCurrentConnectAttempt(connectAttemptId)) return
connectedEndpoint = endpoint
operatorStatusText = "Connecting…"
nodeStatusText = "Connecting…"
@@ -1221,6 +1259,7 @@ class NodeRuntime(
}
fun disconnect() {
connectAttemptSeq.incrementAndGet()
stopActiveVoiceSession()
connectedEndpoint = null
activeGatewayAuth = null

View File

@@ -53,7 +53,10 @@ fun buildGatewayTlsConfig(
onStore: ((String) -> Unit)? = null,
): GatewayTlsConfig? {
if (params == null) return null
val expected = params.expectedFingerprint?.let(::normalizeFingerprint)
val expected =
params.expectedFingerprint
?.let(::normalizeGatewayTlsFingerprint)
?.takeIf { it.isNotBlank() }
val defaultTrust = defaultTrustManager()
@SuppressLint("CustomX509TrustManager")
@@ -200,7 +203,7 @@ private fun sha256Hex(data: ByteArray): String {
return out.toString()
}
private fun normalizeFingerprint(raw: String): String {
fun normalizeGatewayTlsFingerprint(raw: String): String {
val stripped =
raw
.trim()

View File

@@ -100,8 +100,14 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
containerColor = mobileCardSurface,
title = { Text("Trust this gateway?", style = mobileHeadline, color = mobileText) },
text = {
val message =
if (prompt.previousFingerprintSha256.isNullOrBlank()) {
"First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}"
} else {
"The gateway TLS certificate changed. Only continue if you expected this.\n\nOld SHA-256 fingerprint:\n${prompt.previousFingerprintSha256}\n\nNew SHA-256 fingerprint:\n${prompt.fingerprintSha256}"
}
Text(
"First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}",
message,
style = mobileCallout,
color = mobileText,
)

View File

@@ -497,8 +497,14 @@ fun OnboardingFlow(
containerColor = onboardingSurface,
title = { Text("Trust this gateway?", style = onboardingHeadlineStyle, color = onboardingText) },
text = {
val message =
if (prompt.previousFingerprintSha256.isNullOrBlank()) {
"First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}"
} else {
"The gateway TLS certificate changed. Only continue if you expected this.\n\nOld SHA-256 fingerprint:\n${prompt.previousFingerprintSha256}\n\nNew SHA-256 fingerprint:\n${prompt.fingerprintSha256}"
}
Text(
"First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}",
message,
style = onboardingCalloutStyle,
color = onboardingText,
)

View File

@@ -30,13 +30,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withLink
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -499,19 +502,12 @@ private fun AnnotatedString.Builder.appendInlineNode(
}
}
is Link -> {
withStyle(
SpanStyle(
color = linkColor,
textDecoration = TextDecoration.Underline,
),
) {
appendInlineNode(
current.firstChild,
inlineCodeBg = inlineCodeBg,
inlineCodeColor = inlineCodeColor,
linkColor = linkColor,
)
}
appendLinkNode(
link = current,
inlineCodeBg = inlineCodeBg,
inlineCodeColor = inlineCodeColor,
linkColor = linkColor,
)
}
is MarkdownImage -> {
val alt = buildPlainText(current.firstChild)
@@ -527,13 +523,69 @@ private fun AnnotatedString.Builder.appendInlineNode(
}
}
else -> {
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor)
appendInlineNode(
current.firstChild,
inlineCodeBg = inlineCodeBg,
inlineCodeColor = inlineCodeColor,
linkColor = linkColor,
)
}
}
current = current.next
}
}
private fun AnnotatedString.Builder.appendLinkNode(
link: Link,
inlineCodeBg: Color,
inlineCodeColor: Color,
linkColor: Color,
) {
val destination = link.destination?.trim().orEmpty()
val linkStyle =
SpanStyle(
color = linkColor,
textDecoration = TextDecoration.Underline,
)
if (destination.isEmpty()) {
withStyle(linkStyle) {
appendInlineNode(
link.firstChild,
inlineCodeBg = inlineCodeBg,
inlineCodeColor = inlineCodeColor,
linkColor = linkColor,
)
}
return
}
withLink(LinkAnnotation.Url(url = destination, styles = TextLinkStyles(style = linkStyle))) {
appendInlineNode(
link.firstChild,
inlineCodeBg = inlineCodeBg,
inlineCodeColor = inlineCodeColor,
linkColor = linkColor,
)
}
}
internal fun buildChatInlineMarkdown(
text: String,
linkColor: Color = Color.Blue,
): AnnotatedString {
val document = markdownParser.parse(text) as Document
val paragraph = document.firstChild as? Paragraph ?: return AnnotatedString("")
return buildInlineMarkdown(
paragraph.firstChild,
InlineStyles(
inlineCodeBg = Color.Transparent,
inlineCodeColor = Color.Unspecified,
linkColor = linkColor,
baseCallout = TextStyle.Default,
),
)
}
private fun buildPlainText(start: Node?): String {
val sb = StringBuilder()
var node = start

View File

@@ -10,6 +10,7 @@ import ai.openclaw.app.node.InvokeDispatcher
import ai.openclaw.app.protocol.OpenClawTalkCommand
import ai.openclaw.app.voice.TalkModeManager
import android.Manifest
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
@@ -153,7 +154,7 @@ class GatewayBootstrapAuthTest {
NodeRuntime(
app,
prefs,
tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "fp-1") },
tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "fp:1") },
)
val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
val explicitAuth =
@@ -169,11 +170,93 @@ class GatewayBootstrapAuthTest {
runtime.acceptGatewayTrustPrompt()
assertEquals("fp-1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "nodeSession"))
assertEquals("f1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
assertEquals("setup-bootstrap-token", waitForDesiredBootstrapToken(runtime, "nodeSession"))
assertNull(desiredBootstrapToken(runtime, "operatorSession"))
}
@Test
fun connect_promptsBeforeReplacingChangedTlsFingerprint() =
runBlocking {
val app = RuntimeEnvironment.getApplication()
val securePrefs =
app.getSharedPreferences(
"openclaw.node.secure.test.${UUID.randomUUID()}",
android.content.Context.MODE_PRIVATE,
)
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
prefs.saveGatewayTlsFingerprint(endpoint.stableId, "sha256:aa:aa:aa:aa")
val runtime =
NodeRuntime(
app,
prefs,
tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "sha256:bb:bb:bb:bb") },
)
runtime.connect(
endpoint,
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
)
val prompt = waitForGatewayTrustPrompt(runtime)
assertEquals("aaaaaaaa", prompt.previousFingerprintSha256)
assertEquals("bbbbbbbb", prompt.fingerprintSha256)
assertEquals("sha256:aa:aa:aa:aa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
runtime.declineGatewayTrustPrompt()
assertEquals("sha256:aa:aa:aa:aa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
runtime.connect(
endpoint,
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
)
waitForGatewayTrustPrompt(runtime)
runtime.acceptGatewayTrustPrompt()
assertEquals("bbbbbbbb", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
}
@Test
fun connect_ignoresStaleTlsProbeAfterDisconnect() =
runBlocking {
val app = RuntimeEnvironment.getApplication()
val securePrefs =
app.getSharedPreferences(
"openclaw.node.secure.test.${UUID.randomUUID()}",
android.content.Context.MODE_PRIVATE,
)
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
prefs.saveGatewayTlsFingerprint(endpoint.stableId, "aaaaaaaa")
val probeStarted = CompletableDeferred<Unit>()
val probeResult = CompletableDeferred<GatewayTlsProbeResult>()
val runtime =
NodeRuntime(
app,
prefs,
tlsFingerprintProbe = { _, _ ->
probeStarted.complete(Unit)
probeResult.await()
},
)
runtime.connect(
endpoint,
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
)
probeStarted.await()
runtime.disconnect()
probeResult.complete(GatewayTlsProbeResult(fingerprintSha256 = "aaaaaaaa"))
Thread.sleep(100)
assertNull(runtime.pendingGatewayTrust.value)
assertNull(desiredBootstrapToken(runtime, "nodeSession"))
assertEquals("aaaaaaaa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
}
@Test
fun connect_showsSecureEndpointGuidanceWhenTlsProbeFails() {
val app = RuntimeEnvironment.getApplication()
@@ -269,6 +352,21 @@ class GatewayBootstrapAuthTest {
return readField(desired, "bootstrapToken")
}
private fun waitForDesiredBootstrapToken(
runtime: NodeRuntime,
sessionFieldName: String,
): String {
var lastObserved: String? = null
repeat(50) {
desiredBootstrapToken(runtime, sessionFieldName)?.let { token ->
lastObserved = token
return token
}
Thread.sleep(10)
}
error("Expected desired bootstrap token for $sessionFieldName; last observed=$lastObserved")
}
private fun <T> readField(
target: Any,
name: String,

View File

@@ -0,0 +1,42 @@
package ai.openclaw.app.ui.chat
import androidx.compose.ui.text.LinkAnnotation
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class ChatMarkdownTest {
@Test
fun bareUrlsCarryClickableUrlAnnotations() {
val url = "https://www.amazon.it/GAZEBO-CANOPY-ACCIAIO-BIANCO-IMPERMEABILE/dp/B01G5R9FCK"
val annotated = buildChatInlineMarkdown("Open $url")
assertEquals("Open $url", annotated.text)
val links = annotated.getLinkAnnotations(0, annotated.length)
assertEquals(1, links.size)
assertEquals(5, links.single().start)
assertEquals(5 + url.length, links.single().end)
assertEquals(url, (links.single().item as LinkAnnotation.Url).url)
}
@Test
fun markdownLinksUseLabelTextAndDestinationUrl() {
val annotated = buildChatInlineMarkdown("Open [docs](https://docs.openclaw.ai/help/testing) now")
assertEquals("Open docs now", annotated.text)
val links = annotated.getLinkAnnotations(0, annotated.length)
assertEquals(1, links.size)
assertEquals(5, links.single().start)
assertEquals(9, links.single().end)
assertEquals("https://docs.openclaw.ai/help/testing", (links.single().item as LinkAnnotation.Url).url)
}
@Test
fun plainTextDoesNotAddLinkAnnotations() {
val annotated = buildChatInlineMarkdown("No link here")
assertEquals("No link here", annotated.text)
assertTrue(annotated.getLinkAnnotations(0, annotated.length).isEmpty())
}
}

View File

@@ -1,5 +1,9 @@
# OpenClaw iOS Changelog
## 2026.5.17 - 2026-05-17
Maintenance update for the current OpenClaw release.
## 2026.5.12 - 2026-05-12
Maintenance update for the current OpenClaw beta release.

View File

@@ -2,8 +2,8 @@
// Source of truth: apps/ios/version.json
// Generated by scripts/ios-sync-versioning.ts.
OPENCLAW_IOS_VERSION = 2026.5.12
OPENCLAW_MARKETING_VERSION = 2026.5.12
OPENCLAW_IOS_VERSION = 2026.5.17
OPENCLAW_MARKETING_VERSION = 2026.5.17
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -4,15 +4,19 @@ import OpenClawKit
final class CalendarService: CalendarServicing {
func events(params: OpenClawCalendarEventsParams) async throws -> OpenClawCalendarEventsPayload {
let store = EKEventStore()
let status = EKEventStore.authorizationStatus(for: .event)
let authorized = EventKitAuthorization.allowsRead(status: status)
let authorized: Bool = if status == .notDetermined || status == .writeOnly {
await Self.requestFullEventAccess()
} else {
EventKitAuthorization.allowsRead(status: status)
}
guard authorized else {
throw NSError(domain: "Calendar", code: 1, userInfo: [
NSLocalizedDescriptionKey: "CALENDAR_PERMISSION_REQUIRED: grant Calendar permission",
])
}
let store = EKEventStore()
let (start, end) = Self.resolveRange(
startISO: params.startISO,
endISO: params.endISO)
@@ -37,15 +41,19 @@ final class CalendarService: CalendarServicing {
}
func add(params: OpenClawCalendarAddParams) async throws -> OpenClawCalendarAddPayload {
let store = EKEventStore()
let status = EKEventStore.authorizationStatus(for: .event)
let authorized = EventKitAuthorization.allowsWrite(status: status)
let authorized: Bool = if status == .notDetermined {
await Self.requestWriteOnlyEventAccess()
} else {
EventKitAuthorization.allowsWrite(status: status)
}
guard authorized else {
throw NSError(domain: "Calendar", code: 2, userInfo: [
NSLocalizedDescriptionKey: "CALENDAR_PERMISSION_REQUIRED: grant Calendar permission",
])
}
let store = EKEventStore()
let title = params.title.trimmingCharacters(in: .whitespacesAndNewlines)
guard !title.isEmpty else {
throw NSError(domain: "Calendar", code: 3, userInfo: [
@@ -95,6 +103,24 @@ final class CalendarService: CalendarServicing {
return OpenClawCalendarAddPayload(event: payload)
}
private static func requestFullEventAccess() async -> Bool {
await PermissionRequestBridge.awaitRequest { completion in
let store = EKEventStore()
store.requestFullAccessToEvents { granted, _ in
completion(granted)
}
}
}
private static func requestWriteOnlyEventAccess() async -> Bool {
await PermissionRequestBridge.awaitRequest { completion in
let store = EKEventStore()
store.requestWriteOnlyAccessToEvents { granted, _ in
completion(granted)
}
}
}
private static func resolveCalendar(
store: EKEventStore,
calendarId: String?,

View File

@@ -97,14 +97,17 @@ final class ContactsService: ContactsServicing {
return OpenClawContactsAddPayload(contact: Self.payload(from: persisted))
}
private static func ensureAuthorization(store: CNContactStore, status: CNAuthorizationStatus) async -> Bool {
private static func ensureAuthorization(status: CNAuthorizationStatus) async -> Bool {
switch status {
case .authorized, .limited:
return true
case .notDetermined:
// Dont prompt during node.invoke; the caller should instruct the user to grant permission.
// Prompts block the invoke and lead to timeouts in headless flows.
return false
return await PermissionRequestBridge.awaitRequest { completion in
let store = CNContactStore()
store.requestAccess(for: .contacts) { granted, _ in
completion(granted)
}
}
case .restricted, .denied:
return false
@unknown default:
@@ -113,15 +116,14 @@ final class ContactsService: ContactsServicing {
}
private static func authorizedStore() async throws -> CNContactStore {
let store = CNContactStore()
let status = CNContactStore.authorizationStatus(for: .contacts)
let authorized = await Self.ensureAuthorization(store: store, status: status)
let authorized = await Self.ensureAuthorization(status: status)
guard authorized else {
throw NSError(domain: "Contacts", code: 1, userInfo: [
NSLocalizedDescriptionKey: "CONTACTS_PERMISSION_REQUIRED: grant Contacts permission",
])
}
return store
return CNContactStore()
}
private static func normalizeStrings(_ values: [String]?, lowercased: Bool = false) -> [String] {

View File

@@ -52,6 +52,14 @@
</array>
<key>NSCameraUsageDescription</key>
<string>OpenClaw can capture photos or short video clips when requested via the gateway.</string>
<key>NSCalendarsUsageDescription</key>
<string>OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.</string>
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>OpenClaw uses your calendars to add events when you enable calendar access.</string>
<key>NSContactsUsageDescription</key>
<string>OpenClaw uses your contacts so you can search and reference people while using the assistant.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>OpenClaw discovers and connects to your OpenClaw gateway on the local network.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
@@ -64,6 +72,8 @@
<string>OpenClaw may use motion data to support device-aware interactions and automations.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>OpenClaw needs photo library access when you choose existing photos to share with your assistant.</string>
<key>NSRemindersFullAccessUsageDescription</key>
<string>OpenClaw uses your reminders to list, add, and complete tasks when you enable reminders access.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>OpenClaw uses on-device speech recognition for voice wake.</string>
<key>NSSupportsLiveActivities</key>

View File

@@ -1,4 +1,5 @@
import Foundation
import OpenClawKit
import SwiftUI
struct GatewayOnboardingView: View {

View File

@@ -0,0 +1,64 @@
import Foundation
enum PermissionRequestBridge {
final class Box: @unchecked Sendable {
private let lock = NSLock()
private var continuation: CheckedContinuation<Bool, Never>?
private var hasResumed = false
func install(_ continuation: CheckedContinuation<Bool, Never>) -> Bool {
self.lock.lock()
if self.hasResumed {
self.lock.unlock()
continuation.resume(returning: false)
return false
}
self.continuation = continuation
self.lock.unlock()
return true
}
func resume(_ value: Bool) {
self.lock.lock()
guard !self.hasResumed else {
self.lock.unlock()
return
}
self.hasResumed = true
let continuation = self.continuation
self.continuation = nil
self.lock.unlock()
continuation?.resume(returning: value)
}
func canStartRequest() -> Bool {
self.lock.lock()
let canStart = !self.hasResumed
self.lock.unlock()
return canStart
}
}
static func awaitRequest(
_ start: @escaping @Sendable (@escaping @Sendable (Bool) -> Void) -> Void) async -> Bool
{
let box = Box()
return await withTaskCancellationHandler {
await withCheckedContinuation(isolation: nil) { continuation in
guard !Task.isCancelled else {
continuation.resume(returning: false)
return
}
guard box.install(continuation) else { return }
Task { @MainActor in
guard box.canStartRequest() else { return }
start { granted in
box.resume(granted)
}
}
}
} onCancel: {
box.resume(false)
}
}
}

View File

@@ -4,15 +4,19 @@ import OpenClawKit
final class RemindersService: RemindersServicing {
func list(params: OpenClawRemindersListParams) async throws -> OpenClawRemindersListPayload {
let store = EKEventStore()
let status = EKEventStore.authorizationStatus(for: .reminder)
let authorized = EventKitAuthorization.allowsRead(status: status)
let authorized: Bool = if status == .notDetermined || status == .writeOnly {
await Self.requestFullReminderAccess()
} else {
EventKitAuthorization.allowsRead(status: status)
}
guard authorized else {
throw NSError(domain: "Reminders", code: 1, userInfo: [
NSLocalizedDescriptionKey: "REMINDERS_PERMISSION_REQUIRED: grant Reminders permission",
])
}
let store = EKEventStore()
let limit = max(1, min(params.limit ?? 50, 500))
let statusFilter = params.status ?? .incomplete
@@ -48,15 +52,19 @@ final class RemindersService: RemindersServicing {
}
func add(params: OpenClawRemindersAddParams) async throws -> OpenClawRemindersAddPayload {
let store = EKEventStore()
let status = EKEventStore.authorizationStatus(for: .reminder)
let authorized = EventKitAuthorization.allowsWrite(status: status)
let authorized: Bool = if status == .notDetermined {
await Self.requestFullReminderAccess()
} else {
EventKitAuthorization.allowsWrite(status: status)
}
guard authorized else {
throw NSError(domain: "Reminders", code: 2, userInfo: [
NSLocalizedDescriptionKey: "REMINDERS_PERMISSION_REQUIRED: grant Reminders permission",
])
}
let store = EKEventStore()
let title = params.title.trimmingCharacters(in: .whitespacesAndNewlines)
guard !title.isEmpty else {
throw NSError(domain: "Reminders", code: 3, userInfo: [
@@ -100,6 +108,15 @@ final class RemindersService: RemindersServicing {
return OpenClawRemindersAddPayload(reminder: payload)
}
private static func requestFullReminderAccess() async -> Bool {
await PermissionRequestBridge.awaitRequest { completion in
let store = EKEventStore()
store.requestFullAccessToReminders { granted, _ in
completion(granted)
}
}
}
private static func resolveList(
store: EKEventStore,
listId: String?,

View File

@@ -0,0 +1,298 @@
import Contacts
import EventKit
import SwiftUI
import UIKit
struct PrivacyAccessSectionView: View {
@State private var contactsStatus: CNAuthorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
@State private var calendarStatus: EKAuthorizationStatus = EKEventStore.authorizationStatus(for: .event)
@State private var remindersStatus: EKAuthorizationStatus = EKEventStore.authorizationStatus(for: .reminder)
@Environment(\.scenePhase) private var scenePhase
var body: some View {
DisclosureGroup("Privacy & Access") {
self.permissionRow(
title: "Contacts",
icon: "person.crop.circle",
status: self.statusText(for: self.contactsStatus),
detail: "Search and add contacts from the assistant.",
actionTitle: self.actionTitle(for: self.contactsStatus),
action: self.handleContactsAction)
self.permissionRow(
title: "Calendar (Add Events)",
icon: "calendar.badge.plus",
status: self.calendarWriteStatusText,
detail: "Add events with least privilege.",
actionTitle: self.calendarWriteActionTitle,
action: self.handleCalendarWriteAction)
self.permissionRow(
title: "Calendar (View Events)",
icon: "calendar",
status: self.calendarReadStatusText,
detail: "List and read calendar events.",
actionTitle: self.calendarReadActionTitle,
action: self.handleCalendarReadAction)
self.permissionRow(
title: "Reminders",
icon: "checklist",
status: self.remindersStatusText,
detail: "List, add, and complete reminders.",
actionTitle: self.remindersActionTitle,
action: self.handleRemindersAction)
}
.onAppear { self.refreshAll() }
.onChange(of: self.scenePhase) { _, phase in
if phase == .active {
self.refreshAll()
}
}
}
private func permissionRow(
title: String,
icon: String,
status: String,
detail: String,
actionTitle: String?,
action: (() -> Void)?) -> some View
{
VStack(alignment: .leading, spacing: 6) {
HStack {
Label(title, systemImage: icon)
Spacer()
Text(status)
.font(.footnote.weight(.medium))
.foregroundStyle(self.statusColor(for: status))
}
Text(detail)
.font(.footnote)
.foregroundStyle(.secondary)
if let actionTitle, let action {
Button(actionTitle, action: action)
.font(.footnote)
.buttonStyle(.bordered)
}
}
.padding(.vertical, 2)
}
private func statusColor(for status: String) -> Color {
switch status {
case "Allowed":
.green
case "Not Set":
.orange
case "Add-Only":
.yellow
default:
.red
}
}
private func statusText(for cnStatus: CNAuthorizationStatus) -> String {
switch cnStatus {
case .authorized, .limited:
"Allowed"
case .notDetermined:
"Not Set"
case .denied, .restricted:
"Not Allowed"
@unknown default:
"Unknown"
}
}
private func actionTitle(for cnStatus: CNAuthorizationStatus) -> String? {
switch cnStatus {
case .notDetermined:
"Request Access"
case .denied, .restricted:
"Open Settings"
default:
nil
}
}
private func handleContactsAction() {
switch self.contactsStatus {
case .notDetermined:
Task {
_ = await PermissionRequestBridge.awaitRequest { completion in
let store = CNContactStore()
store.requestAccess(for: .contacts) { granted, _ in
completion(granted)
}
}
await MainActor.run { self.refreshAll() }
}
case .denied, .restricted:
self.openSettings()
default:
break
}
}
private var calendarWriteStatusText: String {
switch self.calendarStatus {
case .authorized, .fullAccess, .writeOnly:
"Allowed"
case .notDetermined:
"Not Set"
case .denied, .restricted:
"Not Allowed"
@unknown default:
"Unknown"
}
}
private var calendarWriteActionTitle: String? {
switch self.calendarStatus {
case .notDetermined:
"Request Access"
case .denied, .restricted:
"Open Settings"
default:
nil
}
}
private func handleCalendarWriteAction() {
switch self.calendarStatus {
case .notDetermined:
Task {
_ = await self.requestCalendarWriteOnly()
await MainActor.run { self.refreshAll() }
}
case .denied, .restricted:
self.openSettings()
default:
break
}
}
private var calendarReadStatusText: String {
switch self.calendarStatus {
case .authorized, .fullAccess:
"Allowed"
case .writeOnly:
"Add-Only"
case .notDetermined:
"Not Set"
case .denied, .restricted:
"Not Allowed"
@unknown default:
"Unknown"
}
}
private var calendarReadActionTitle: String? {
switch self.calendarStatus {
case .notDetermined:
"Request Full Access"
case .writeOnly:
"Upgrade to Full Access"
case .denied, .restricted:
"Open Settings"
default:
nil
}
}
private func handleCalendarReadAction() {
switch self.calendarStatus {
case .notDetermined, .writeOnly:
Task {
_ = await self.requestCalendarFull()
await MainActor.run { self.refreshAll() }
}
case .denied, .restricted:
self.openSettings()
default:
break
}
}
private var remindersStatusText: String {
switch self.remindersStatus {
case .authorized, .fullAccess:
"Allowed"
case .writeOnly:
"Add-Only"
case .notDetermined:
"Not Set"
case .denied, .restricted:
"Not Allowed"
@unknown default:
"Unknown"
}
}
private var remindersActionTitle: String? {
switch self.remindersStatus {
case .notDetermined:
"Request Access"
case .writeOnly:
"Upgrade to Full Access"
case .denied, .restricted:
"Open Settings"
default:
nil
}
}
private func handleRemindersAction() {
switch self.remindersStatus {
case .notDetermined, .writeOnly:
Task {
_ = await self.requestRemindersFull()
await MainActor.run { self.refreshAll() }
}
case .denied, .restricted:
self.openSettings()
default:
break
}
}
private func refreshAll() {
self.contactsStatus = CNContactStore.authorizationStatus(for: .contacts)
self.calendarStatus = EKEventStore.authorizationStatus(for: .event)
self.remindersStatus = EKEventStore.authorizationStatus(for: .reminder)
}
private func requestCalendarWriteOnly() async -> Bool {
await PermissionRequestBridge.awaitRequest { completion in
let store = EKEventStore()
store.requestWriteOnlyAccessToEvents { granted, _ in
completion(granted)
}
}
}
private func requestCalendarFull() async -> Bool {
await PermissionRequestBridge.awaitRequest { completion in
let store = EKEventStore()
store.requestFullAccessToEvents { granted, _ in
completion(granted)
}
}
}
private func requestRemindersFull() async -> Bool {
await PermissionRequestBridge.awaitRequest { completion in
let store = EKEventStore()
store.requestFullAccessToReminders { granted, _ in
completion(granted)
}
}
}
private func openSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}
}

View File

@@ -405,6 +405,8 @@ struct SettingsTab: View {
}
}
AnyView(PrivacyAccessSectionView())
DisclosureGroup("Device Info") {
TextField("Name", text: self.$displayName)
Text(self.instanceId)
@@ -419,16 +421,7 @@ struct SettingsTab: View {
}
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
self.dismiss()
} label: {
Image(systemName: "xmark")
}
.accessibilityLabel("Close")
}
}
.modifier(SettingsCloseToolbar())
.sheet(isPresented: self.$showGatewayProblemDetails) {
if let gatewayProblem = self.appModel.lastGatewayProblem {
GatewayProblemDetailsSheet(
@@ -488,90 +481,91 @@ struct SettingsTab: View {
Text(self.scannerError ?? "")
}
.onAppear {
self.lastLocationModeRaw = self.locationEnabledModeRaw
self.syncManualPortText()
let trimmedInstanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedInstanceId.isEmpty {
self.gatewayToken = GatewaySettingsStore.loadGatewayToken(instanceId: trimmedInstanceId) ?? ""
self.gatewayPassword = GatewaySettingsStore.loadGatewayPassword(instanceId: trimmedInstanceId) ?? ""
self.lastLocationModeRaw = self.locationEnabledModeRaw
self.syncManualPortText()
let trimmedInstanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedInstanceId.isEmpty {
self.gatewayToken = GatewaySettingsStore.loadGatewayToken(instanceId: trimmedInstanceId) ?? ""
self.gatewayPassword = GatewaySettingsStore
.loadGatewayPassword(instanceId: trimmedInstanceId) ?? ""
}
self.defaultShareInstruction = ShareToAgentSettings.loadDefaultInstruction()
self.appModel.refreshLastShareEventFromRelay()
// Keep setup front-and-center when disconnected; keep things compact once connected.
self.gatewayExpanded = !self.isGatewayConnected
self.selectedAgentPickerId = self.appModel.selectedAgentId ?? ""
if self.isGatewayConnected {
self.appModel.reloadTalkConfig()
}
}
self.defaultShareInstruction = ShareToAgentSettings.loadDefaultInstruction()
self.appModel.refreshLastShareEventFromRelay()
// Keep setup front-and-center when disconnected; keep things compact once connected.
self.gatewayExpanded = !self.isGatewayConnected
self.selectedAgentPickerId = self.appModel.selectedAgentId ?? ""
if self.isGatewayConnected {
self.appModel.reloadTalkConfig()
.onChange(of: self.selectedAgentPickerId) { _, newValue in
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
self.appModel.setSelectedAgentId(trimmed.isEmpty ? nil : trimmed)
}
}
.onChange(of: self.selectedAgentPickerId) { _, newValue in
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
self.appModel.setSelectedAgentId(trimmed.isEmpty ? nil : trimmed)
}
.onChange(of: self.appModel.selectedAgentId ?? "") { _, newValue in
if newValue != self.selectedAgentPickerId {
self.selectedAgentPickerId = newValue
.onChange(of: self.appModel.selectedAgentId ?? "") { _, newValue in
if newValue != self.selectedAgentPickerId {
self.selectedAgentPickerId = newValue
}
}
}
.onChange(of: self.preferredGatewayStableID) { _, newValue in
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
GatewaySettingsStore.savePreferredGatewayStableID(trimmed)
}
.onChange(of: self.gatewayToken) { _, newValue in
guard !self.suppressCredentialPersist else { return }
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
let instanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
guard !instanceId.isEmpty else { return }
GatewaySettingsStore.saveGatewayToken(trimmed, instanceId: instanceId)
}
.onChange(of: self.gatewayPassword) { _, newValue in
guard !self.suppressCredentialPersist else { return }
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
let instanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
guard !instanceId.isEmpty else { return }
GatewaySettingsStore.saveGatewayPassword(trimmed, instanceId: instanceId)
}
.onChange(of: self.defaultShareInstruction) { _, newValue in
ShareToAgentSettings.saveDefaultInstruction(newValue)
}
.onChange(of: self.manualGatewayPort) { _, _ in
self.syncManualPortText()
}
.onChange(of: self.appModel.gatewayServerName) { _, newValue in
if newValue != nil {
self.setupCode = ""
self.setupStatusText = nil
return
.onChange(of: self.preferredGatewayStableID) { _, newValue in
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
GatewaySettingsStore.savePreferredGatewayStableID(trimmed)
}
if self.manualGatewayEnabled {
self.setupStatusText = self.appModel.gatewayStatusText
.onChange(of: self.gatewayToken) { _, newValue in
guard !self.suppressCredentialPersist else { return }
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
let instanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
guard !instanceId.isEmpty else { return }
GatewaySettingsStore.saveGatewayToken(trimmed, instanceId: instanceId)
}
}
.onChange(of: self.appModel.gatewayStatusText) { _, newValue in
guard self.manualGatewayEnabled || self.connectingGatewayID == "manual" else { return }
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
self.setupStatusText = trimmed
}
.onChange(of: self.locationEnabledModeRaw) { _, newValue in
let previous = self.lastLocationModeRaw
self.lastLocationModeRaw = newValue
guard let mode = OpenClawLocationMode(rawValue: newValue) else { return }
Task {
let granted = await self.appModel.requestLocationPermissions(mode: mode)
if !granted {
await MainActor.run {
self.locationEnabledModeRaw = previous
self.lastLocationModeRaw = previous
}
.onChange(of: self.gatewayPassword) { _, newValue in
guard !self.suppressCredentialPersist else { return }
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
let instanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
guard !instanceId.isEmpty else { return }
GatewaySettingsStore.saveGatewayPassword(trimmed, instanceId: instanceId)
}
.onChange(of: self.defaultShareInstruction) { _, newValue in
ShareToAgentSettings.saveDefaultInstruction(newValue)
}
.onChange(of: self.manualGatewayPort) { _, _ in
self.syncManualPortText()
}
.onChange(of: self.appModel.gatewayServerName) { _, newValue in
if newValue != nil {
self.setupCode = ""
self.setupStatusText = nil
return
}
await MainActor.run {
self.gatewayController.refreshActiveGatewayRegistrationFromSettings()
if self.manualGatewayEnabled {
self.setupStatusText = self.appModel.gatewayStatusText
}
}
.onChange(of: self.appModel.gatewayStatusText) { _, newValue in
guard self.manualGatewayEnabled || self.connectingGatewayID == "manual" else { return }
let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
self.setupStatusText = trimmed
}
.onChange(of: self.locationEnabledModeRaw) { _, newValue in
let previous = self.lastLocationModeRaw
self.lastLocationModeRaw = newValue
guard let mode = OpenClawLocationMode(rawValue: newValue) else { return }
Task {
let granted = await self.appModel.requestLocationPermissions(mode: mode)
if !granted {
await MainActor.run {
self.locationEnabledModeRaw = previous
self.lastLocationModeRaw = previous
}
return
}
await MainActor.run {
self.gatewayController.refreshActiveGatewayRegistrationFromSettings()
}
}
}
}
}
.gatewayTrustPromptAlert()
}
@@ -1138,4 +1132,21 @@ struct SettingsTab: View {
}
}
private struct SettingsCloseToolbar: ViewModifier {
@Environment(\.dismiss) private var dismiss
func body(content: Content) -> some View {
content.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
self.dismiss()
} label: {
Image(systemName: "xmark")
}
.accessibilityLabel("Close")
}
}
}
}
// swiftlint:enable type_body_length

View File

@@ -40,6 +40,7 @@ Sources/Onboarding/OnboardingStateStore.swift
Sources/Onboarding/OnboardingWizardView.swift
Sources/Onboarding/QRScannerView.swift
Sources/OpenClawApp.swift
Sources/Permissions/PermissionRequestBridge.swift
Sources/Push/ExecApprovalNotificationBridge.swift
Sources/Push/BackgroundAliveBeacon.swift
Sources/Push/PushBuildConfig.swift
@@ -60,6 +61,7 @@ Sources/Services/WatchConnectivityTransport.swift
Sources/Services/WatchMessagingPayloadCodec.swift
Sources/Services/WatchMessagingService.swift
Sources/SessionKey.swift
Sources/Settings/PrivacyAccessSectionView.swift
Sources/Settings/SettingsNetworkingHelpers.swift
Sources/Settings/SettingsTab.swift
Sources/Settings/VoiceWakeWordsSettingsView.swift

View File

@@ -0,0 +1,26 @@
import Testing
@testable import OpenClaw
@Suite(.serialized) struct PermissionRequestBridgeTests {
@Test func `box resumes immediately when cancelled before install`() async {
let box = PermissionRequestBridge.Box()
box.resume(false)
let granted: Bool = await withCheckedContinuation { continuation in
_ = box.install(continuation)
}
#expect(granted == false)
#expect(box.canStartRequest() == false)
}
@Test func `box resumes installed continuation once`() async {
let box = PermissionRequestBridge.Box()
let granted: Bool = await withCheckedContinuation { continuation in
_ = box.install(continuation)
box.resume(true)
box.resume(false)
}
#expect(granted == true)
}
}

View File

@@ -1 +1 @@
Maintenance update for the current OpenClaw beta release.
Maintenance update for the current OpenClaw release.

View File

@@ -136,11 +136,16 @@ targets:
NSBonjourServices:
- _openclaw-gw._tcp
NSCameraUsageDescription: OpenClaw can capture photos or short video clips when requested via the gateway.
NSCalendarsUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.
NSCalendarsFullAccessUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.
NSCalendarsWriteOnlyAccessUsageDescription: OpenClaw uses your calendars to add events when you enable calendar access.
NSContactsUsageDescription: OpenClaw uses your contacts so you can search and reference people while using the assistant.
NSLocationWhenInUseUsageDescription: OpenClaw uses your location when you allow location sharing.
NSLocationAlwaysAndWhenInUseUsageDescription: OpenClaw can share your location in the background when you enable Always.
NSMicrophoneUsageDescription: OpenClaw needs microphone access for voice wake.
NSMotionUsageDescription: OpenClaw may use motion data to support device-aware interactions and automations.
NSPhotoLibraryUsageDescription: OpenClaw needs photo library access when you choose existing photos to share with your assistant.
NSRemindersFullAccessUsageDescription: OpenClaw uses your reminders to list, add, and complete tasks when you enable reminders access.
NSSpeechRecognitionUsageDescription: OpenClaw uses on-device speech recognition for voice wake.
NSSupportsLiveActivities: true
ITSAppUsesNonExemptEncryption: false

View File

@@ -1,3 +1,3 @@
{
"version": "2026.5.12"
"version": "2026.5.17"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -81,6 +81,7 @@ let package = Package(
dependencies: [
"OpenClawIPC",
"OpenClaw",
"OpenClawMacCLI",
"OpenClawDiscovery",
.product(name: "OpenClawProtocol", package: "OpenClawKit"),
.product(name: "SwabbleKit", package: "swabble"),

View File

@@ -318,7 +318,12 @@ final class AppState {
self.iconAnimationsEnabled = true
UserDefaults.standard.set(true, forKey: iconAnimationsEnabledKey)
}
self.showDockIcon = UserDefaults.standard.bool(forKey: showDockIconKey)
if let storedShowDockIcon = UserDefaults.standard.object(forKey: showDockIconKey) as? Bool {
self.showDockIcon = storedShowDockIcon
} else {
self.showDockIcon = true
UserDefaults.standard.set(true, forKey: showDockIconKey)
}
self.voiceWakeMicID = UserDefaults.standard.string(forKey: voiceWakeMicKey) ?? ""
self.voiceWakeMicName = UserDefaults.standard.string(forKey: voiceWakeMicNameKey) ?? ""
self.voiceWakeLocaleID = UserDefaults.standard.string(forKey: voiceWakeLocaleKey) ?? Locale.current.identifier
@@ -358,19 +363,29 @@ final class AppState {
}
let configRoot = OpenClawConfigFile.loadDict()
let configRemoteUrl = GatewayRemoteConfig.resolveUrlString(root: configRoot)
let configRemoteToken = GatewayRemoteConfig.resolveTokenValue(root: configRoot)
let configRemoteTransport = GatewayRemoteConfig.resolveTransport(root: configRoot)
let configRemoteResolution = GatewayRemoteConfig.resolveTransportResolution(root: configRoot)
let configRemoteTransport = configRemoteResolution.transport
let configRemoteUrl = configRemoteResolution.directURL?.absoluteString
?? GatewayRemoteConfig.resolveUrlString(root: configRoot)
let resolvedConnectionMode = ConnectionModeResolver.resolve(root: configRoot).mode
self.remoteTransport = configRemoteTransport
self.connectionMode = resolvedConnectionMode
let configRemote = (configRoot["gateway"] as? [String: Any])?["remote"] as? [String: Any]
let configRemoteTarget = (configRemote?["sshTarget"] as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let storedRemoteTarget = UserDefaults.standard.string(forKey: remoteTargetKey) ?? ""
if resolvedConnectionMode == .remote,
configRemoteTransport != .direct,
storedRemoteTarget.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
let host = AppState.remoteHost(from: configRemoteUrl),
!LoopbackHost.isLoopbackHost(host)
!configRemoteTarget.isEmpty,
storedRemoteTarget.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
self.remoteTarget = configRemoteTarget
} else if resolvedConnectionMode == .remote,
configRemoteTransport != .direct,
storedRemoteTarget.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
let host = AppState.remoteHost(from: configRemoteUrl),
!LoopbackHost.isLoopbackHost(host)
{
self.remoteTarget = "\(NSUserName())@\(host)"
} else {
@@ -380,9 +395,11 @@ final class AppState {
self.remoteToken = configRemoteToken.textFieldValue
self.remoteTokenDirty = false
self.remoteTokenUnsupported = configRemoteToken.isUnsupportedNonString
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey) ?? ""
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey) ?? ""
self.remoteCliPath = UserDefaults.standard.string(forKey: remoteCliPathKey) ?? ""
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey)?.nonEmpty
?? configRemote?["sshIdentity"] as? String
?? ""
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey)?.nonEmpty ?? ""
self.remoteCliPath = UserDefaults.standard.string(forKey: remoteCliPathKey)?.nonEmpty ?? ""
self.canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
let execDefaults = ExecApprovalsStore.resolveDefaults()
self.execApprovalMode = ExecApprovalQuickMode.from(security: execDefaults.security, ask: execDefaults.ask)
@@ -517,7 +534,10 @@ final class AppState {
}
case .ssh:
changed = Self.updateGatewayString(&remote, key: "transport", value: nil) || changed
changed = Self.updateGatewayString(
&remote,
key: "transport",
value: RemoteTransport.ssh.rawValue) || changed
let sanitizedTarget = Self.sanitizeSSHTarget(draft.remoteTarget)
let expectedRemoteHost = CommandResolver.parseSSHTarget(sanitizedTarget)?.host ?? draft.remoteHost
@@ -561,7 +581,8 @@ final class AppState {
let hasRemoteUrl = !(remoteUrl?
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty ?? true)
let remoteTransport = GatewayRemoteConfig.resolveTransport(root: root)
let remoteResolution = GatewayRemoteConfig.resolveTransportResolution(root: root)
let remoteTransport = remoteResolution.transport
let desiredMode: ConnectionMode? = switch modeRaw {
case "local":
@@ -585,7 +606,7 @@ final class AppState {
if remoteTransport != self.remoteTransport {
self.remoteTransport = remoteTransport
}
let remoteUrlText = remoteUrl ?? ""
let remoteUrlText = remoteResolution.directURL?.absoluteString ?? remoteUrl ?? ""
if remoteUrlText != self.remoteUrl {
self.remoteUrl = remoteUrlText
}

View File

@@ -26,7 +26,7 @@ final class CLIInstallPrompter {
case .alertFirstButtonReturn:
Task { await self.installCLI() }
case .alertThirdButtonReturn:
self.openSettings(tab: .general)
self.openSettings(tab: .connection)
default:
break
}

View File

@@ -1,9 +1,27 @@
import SwiftUI
enum ConfigSchemaFormMode {
case full
case channelQuick
}
struct ConfigSchemaForm: View {
@Bindable var store: ChannelsStore
let schema: ConfigSchemaNode
let path: ConfigPath
let mode: ConfigSchemaFormMode
init(
store: ChannelsStore,
schema: ConfigSchemaNode,
path: ConfigPath,
mode: ConfigSchemaFormMode = .full)
{
self.store = store
self.schema = schema
self.path = path
self.mode = mode
}
var body: some View {
self.renderNode(self.schema, path: self.path)
@@ -12,7 +30,7 @@ struct ConfigSchemaForm: View {
private func renderNode(_ schema: ConfigSchemaNode, path: ConfigPath) -> AnyView {
let storedValue = self.store.configValue(at: path)
let value = storedValue ?? schema.explicitDefault
let label = hintForPath(path, hints: store.configUiHints)?.label ?? schema.title
let label = self.fieldLabel(for: schema, path: path)
let help = hintForPath(path, hints: store.configUiHints)?.help ?? schema.description
let variants = schema.anyOf.isEmpty ? schema.oneOf : schema.anyOf
@@ -62,18 +80,16 @@ struct ConfigSchemaForm: View {
.foregroundStyle(.secondary)
}
let properties = schema.properties
let sortedKeys = properties.keys.sorted { lhs, rhs in
let orderA = hintForPath(path + [.key(lhs)], hints: store.configUiHints)?.order ?? 0
let orderB = hintForPath(path + [.key(rhs)], hints: store.configUiHints)?.order ?? 0
if orderA != orderB { return orderA < orderB }
return lhs < rhs
}
let sortedKeys = self.visibleObjectKeys(properties: properties, path: path)
ForEach(sortedKeys, id: \ .self) { key in
if let child = properties[key] {
self.renderNode(child, path: path + [.key(key)])
}
}
if schema.allowsAdditionalProperties {
if sortedKeys.isEmpty, self.mode == .channelQuick, self.isChannelRoot(path) {
self.renderChannelQuickEmptyState()
}
if self.shouldRenderAdditionalProperties(schema, path: path, value: value) {
self.renderAdditionalProperties(schema, path: path, value: value)
}
})
@@ -100,6 +116,116 @@ struct ConfigSchemaForm: View {
}
}
private func fieldLabel(for schema: ConfigSchemaNode, path: ConfigPath) -> String? {
hintForPath(path, hints: self.store.configUiHints)?.label
?? schema.title
?? labelForConfigPath(path)
}
private func visibleObjectKeys(
properties: [String: ConfigSchemaNode],
path: ConfigPath) -> [String]
{
let sortedKeys = properties.keys.sorted { lhs, rhs in
let orderA = hintForPath(path + [.key(lhs)], hints: store.configUiHints)?.order ?? 0
let orderB = hintForPath(path + [.key(rhs)], hints: store.configUiHints)?.order ?? 0
if orderA != orderB { return orderA < orderB }
return lhs < rhs
}
guard self.mode == .channelQuick, self.isChannelRoot(path) else {
return sortedKeys
}
return sortedKeys.filter { key in
guard let child = properties[key] else { return false }
return self.shouldRenderChannelQuickField(key: key, schema: child, path: path + [.key(key)])
}
}
private func shouldRenderChannelQuickField(
key: String,
schema: ConfigSchemaNode,
path: ConfigPath) -> Bool
{
if hintForPath(path, hints: self.store.configUiHints)?.advanced == true {
return false
}
if Self.channelQuickKeys.contains(key) {
return self.isSimpleField(schema)
}
return self.store.configValue(at: path) != nil && self.isSimpleField(schema)
}
private func isSimpleField(_ schema: ConfigSchemaNode) -> Bool {
let variants = schema.anyOf.isEmpty ? schema.oneOf : schema.anyOf
let nonNullVariants = variants.filter { !$0.isNullSchema }
if !nonNullVariants.isEmpty {
return nonNullVariants.allSatisfy(self.isSimpleField)
}
if let enumValues = schema.enumValues {
return !enumValues.isEmpty
}
switch schema.schemaType {
case "boolean", "integer", "number", "string":
return true
default:
return false
}
}
private func shouldRenderAdditionalProperties(
_ schema: ConfigSchemaNode,
path: ConfigPath,
value: Any?) -> Bool
{
guard schema.allowsAdditionalProperties else { return false }
if self.mode != .channelQuick { return true }
guard let dict = value as? [String: Any] else { return false }
let reserved = Set(schema.properties.keys)
return dict.keys.contains { !reserved.contains($0) }
}
private func isChannelRoot(_ path: ConfigPath) -> Bool {
guard path.count == 2 else { return false }
guard case .key("channels") = path[0] else { return false }
guard case .key = path[1] else { return false }
return true
}
private func renderChannelQuickEmptyState() -> some View {
VStack(alignment: .leading, spacing: 4) {
Text("No quick settings for this channel.")
.font(.callout.weight(.semibold))
Text("Use Config for account, guild, action, and policy details.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
private static let channelQuickKeys: Set<String> = [
"apiHash",
"apiId",
"appToken",
"baseUrl",
"botToken",
"configWrites",
"deviceName",
"dmPolicy",
"enabled",
"groupPolicy",
"historyLimit",
"mode",
"nativeCommands",
"nativeSkillCommands",
"phoneNumber",
"signingSecret",
"token",
"url",
"username",
"webhookUrl",
]
@ViewBuilder
private func renderStringField(
_ schema: ConfigSchemaNode,
@@ -353,7 +479,11 @@ struct ChannelConfigForm: View {
if self.store.configSchemaLoading {
ProgressView().controlSize(.small)
} else if let schema = store.channelConfigSchema(for: channelId) {
ConfigSchemaForm(store: self.store, schema: schema, path: [.key("channels"), .key(self.channelId)])
ConfigSchemaForm(
store: self.store,
schema: schema,
path: [.key("channels"), .key(self.channelId)],
mode: .channelQuick)
} else {
Text("Schema unavailable for this channel.")
.font(.caption)

View File

@@ -6,7 +6,7 @@ extension ChannelsSettings {
_ id: String,
as type: T.Type) -> T?
{
self.store.snapshot?.decodeChannel(id, as: type)
self.store.decodedChannel(id, as: type)
}
private func configuredChannelTint(configured: Bool, running: Bool, hasError: Bool, probeOk: Bool?) -> Color {
@@ -358,12 +358,16 @@ extension ChannelsSettings {
}
func ensureSelection() {
self.ensureSelection(in: self.orderedChannels)
}
func ensureSelection(in orderedChannels: [ChannelItem]) {
guard let selected = self.selectedChannel else {
self.selectedChannel = self.orderedChannels.first
self.selectedChannel = orderedChannels.first
return
}
if !self.orderedChannels.contains(selected) {
self.selectedChannel = self.orderedChannels.first
if !orderedChannels.contains(selected) {
self.selectedChannel = orderedChannels.first
}
}

View File

@@ -2,34 +2,49 @@ import SwiftUI
extension ChannelsSettings {
var body: some View {
HStack(spacing: 0) {
self.sidebar
let channels = self.orderedChannels
return HStack(spacing: 0) {
self.sidebar(channels: channels)
self.detail
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.onAppear {
self.store.start()
self.ensureSelection()
self.updateActiveWork(active: self.isActive)
self.ensureSelection(in: channels)
}
.onChange(of: self.orderedChannels) { _, _ in
self.ensureSelection()
.onChange(of: self.isActive) { _, active in
self.updateActiveWork(active: active)
}
.onChange(of: channels) { _, newValue in
self.ensureSelection(in: newValue)
}
.onDisappear { self.store.stop() }
}
private var sidebar: some View {
SettingsSidebarScroll {
private func updateActiveWork(active: Bool) {
if active {
self.store.start()
} else {
self.store.stop()
}
}
private func sidebar(channels: [ChannelItem]) -> some View {
let enabled = channels.filter { self.channelEnabled($0) }
let available = channels.filter { !self.channelEnabled($0) }
return SettingsSidebarScroll {
LazyVStack(alignment: .leading, spacing: 8) {
if !self.enabledChannels.isEmpty {
if !enabled.isEmpty {
self.sidebarSectionHeader("Configured")
ForEach(self.enabledChannels) { channel in
ForEach(enabled) { channel in
self.sidebarRow(channel)
}
}
if !self.availableChannels.isEmpty {
if !available.isEmpty {
self.sidebarSectionHeader("Available")
ForEach(self.availableChannels) { channel in
ForEach(available) { channel in
self.sidebarRow(channel)
}
}

View File

@@ -11,9 +11,11 @@ struct ChannelsSettings: View {
}
@Bindable var store: ChannelsStore
let isActive: Bool
@State var selectedChannel: ChannelItem?
init(store: ChannelsStore = .shared) {
init(store: ChannelsStore = .shared, isActive: Bool = true) {
self.store = store
self.isActive = isActive
}
}

View File

@@ -2,42 +2,171 @@ import Foundation
import OpenClawProtocol
extension ChannelsStore {
func loadConfigSchema() async {
guard !self.configSchemaLoading else { return }
func loadConfigSchema(force: Bool = false) async {
let sourceKey = self.currentConfigCacheSourceKey()
self.resetConfigSchemaCacheIfSourceChanged(sourceKey)
if !force, self.configSchema != nil {
return
}
guard !self.queueConfigSchemaReloadIfLoading(sourceKey: sourceKey, force: force) else { return }
self.configSchemaLoading = true
defer { self.configSchemaLoading = false }
self.configSchemaLoadingSourceKey = sourceKey
defer {
self.configSchemaLoading = false
self.configSchemaLoadingSourceKey = nil
}
do {
let res: ConfigSchemaResponse = try await GatewayConnection.shared.requestDecoded(
method: .configSchema,
params: nil,
timeoutMs: 8000)
let schemaValue = res.schema.foundationValue
self.configSchema = ConfigSchemaNode(raw: schemaValue)
let hintValues = res.uihints.mapValues { $0.foundationValue }
self.configUiHints = decodeUiHints(hintValues)
} catch {
self.configStatus = error.localizedDescription
var requestSourceKey = sourceKey
while true {
self.configSchemaLoadingSourceKey = requestSourceKey
do {
let res: ConfigSchemaResponse = try await GatewayConnection.shared.requestDecoded(
method: .configSchema,
params: nil,
timeoutMs: 8000)
self.applyConfigSchemaResponse(res, sourceKey: requestSourceKey)
} catch {
self.configStatus = error.localizedDescription
}
guard self.configSchemaReloadPending else { break }
self.configSchemaReloadPending = false
requestSourceKey = self.currentConfigCacheSourceKey()
self.resetConfigSchemaCacheIfSourceChanged(requestSourceKey)
}
}
func loadConfig() async {
do {
let snap: ConfigSnapshot = try await GatewayConnection.shared.requestDecoded(
method: .configGet,
params: nil,
timeoutMs: 10000)
self.configStatus = snap.valid == false
? "Config invalid; fix it in ~/.openclaw/openclaw.json."
: nil
self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:]
self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot
self.configDirty = false
self.configLoaded = true
@discardableResult
func loadConfigSchemaLookup(path: String, force: Bool = false) async -> ConfigSchemaLookupNode? {
let sourceKey = self.currentConfigCacheSourceKey()
self.resetConfigSchemaCacheIfSourceChanged(sourceKey)
let normalizedPath = Self.normalizeConfigLookupPath(path)
if !force, let cached = self.configLookupNode(path: normalizedPath) {
return cached
}
if self.configLookupLoadingPaths.contains(normalizedPath) {
return self.configLookupNode(path: normalizedPath)
}
self.applyUIConfig(snap)
self.configLookupLoadingPaths.insert(normalizedPath)
defer { self.configLookupLoadingPaths.remove(normalizedPath) }
do {
let res: ConfigSchemaLookupResult = try await GatewayConnection.shared.requestDecoded(
method: .configSchemaLookup,
params: ["path": AnyCodable(normalizedPath)],
timeoutMs: 5000)
guard let node = self.makeConfigLookupNode(res) else {
self.configStatus = "Config schema lookup returned an unsupported payload."
return nil
}
self.applyConfigLookupNode(node, sourceKey: sourceKey)
return node
} catch {
self.configStatus = error.localizedDescription
return nil
}
}
func loadConfig(force: Bool = true) async {
let sourceKey = self.currentConfigCacheSourceKey()
self.resetConfigCacheIfSourceChanged(sourceKey)
if !force, self.configLoaded {
return
}
guard !self.queueConfigReloadIfLoading(sourceKey: sourceKey, force: force) else { return }
self.configLoading = true
self.configLoadingSourceKey = sourceKey
defer {
self.configLoading = false
self.configLoadingSourceKey = nil
}
var requestForce = force
var requestSourceKey = sourceKey
while true {
self.configLoadingSourceKey = requestSourceKey
do {
let snap: ConfigSnapshot = try await GatewayConnection.shared.requestDecoded(
method: .configGet,
params: nil,
timeoutMs: 10000)
self.applyConfigSnapshot(snap, sourceKey: requestSourceKey, force: requestForce)
} catch {
self.configStatus = error.localizedDescription
}
guard self.configForceReloadPending else { break }
self.configForceReloadPending = false
requestForce = true
requestSourceKey = self.currentConfigCacheSourceKey()
self.resetConfigCacheIfSourceChanged(requestSourceKey)
}
}
func applyConfigSnapshot(_ snap: ConfigSnapshot, sourceKey: String, force: Bool) {
guard self.configSourceKey == sourceKey else { return }
guard force || !self.configDirty else { return }
self.configStatus = snap.valid == false
? "Config invalid; fix it in ~/.openclaw/openclaw.json."
: nil
self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:]
self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot
self.configDirty = false
self.configLoaded = true
self.configSourceKey = sourceKey
self.applyUIConfig(snap)
}
func applyConfigSchemaResponse(_ res: ConfigSchemaResponse, sourceKey: String) {
guard self.configSchemaSourceKey == sourceKey else { return }
let schemaValue = res.schema.foundationValue
self.configSchema = ConfigSchemaNode(raw: schemaValue)
let hintValues = res.uihints.mapValues { $0.foundationValue }
self.configUiHints = decodeUiHints(hintValues)
self.configSchemaSourceKey = sourceKey
}
func configLookupNode(path: String) -> ConfigSchemaLookupNode? {
let normalizedPath = Self.normalizeConfigLookupPath(path)
if normalizedPath == "." {
return self.configLookupRoot
}
return self.configLookupCache[normalizedPath]
}
func makeConfigLookupNode(_ res: ConfigSchemaLookupResult) -> ConfigSchemaLookupNode? {
let schemaValue = res.schema.foundationValue
guard let schema = ConfigSchemaNode(raw: schemaValue) else { return nil }
let hint = res.hint.map { ConfigUiHint(raw: $0.mapValues(\.foundationValue)) }
let children = res.children.compactMap(ConfigSchemaLookupChild.init(raw:))
return ConfigSchemaLookupNode(
path: Self.normalizeConfigLookupPath(res.path),
schema: schema,
hint: hint,
hintPath: res.hintpath,
children: children)
}
func applyConfigLookupNode(_ node: ConfigSchemaLookupNode, sourceKey: String) {
guard self.configSchemaSourceKey == sourceKey else { return }
if node.path == "." {
self.configLookupRoot = node
} else {
self.configLookupCache[node.path] = node
}
if let hint = node.hint {
self.configUiHints[node.path] = hint
}
for child in node.children {
if let hint = child.hint {
self.configUiHints[child.path] = hint
}
}
}
@@ -85,7 +214,83 @@ extension ChannelsStore {
}
func reloadConfigDraft() async {
await self.loadConfig()
await self.loadConfig(force: true)
}
func resetConfigSchemaCacheIfSourceChanged(_ sourceKey: String) {
guard let cachedSourceKey = self.configSchemaSourceKey else {
self.configSchemaSourceKey = sourceKey
return
}
guard cachedSourceKey != sourceKey else { return }
self.configSchema = nil
self.configLookupRoot = nil
self.configLookupCache.removeAll(keepingCapacity: true)
self.configLookupLoadingPaths.removeAll(keepingCapacity: true)
self.configUiHints = [:]
self.configSchemaSourceKey = sourceKey
}
func resetConfigCacheIfSourceChanged(_ sourceKey: String) {
guard let cachedSourceKey = self.configSourceKey else {
self.configSourceKey = sourceKey
return
}
guard cachedSourceKey != sourceKey else { return }
self.configRoot = [:]
self.configDraft = [:]
self.configDirty = false
self.configLoaded = false
self.configSourceKey = sourceKey
}
func queueConfigReloadIfLoading(sourceKey: String, force: Bool) -> Bool {
guard self.configLoading else { return false }
if force || self.configLoadingSourceKey != sourceKey {
self.configForceReloadPending = true
}
return true
}
func queueConfigSchemaReloadIfLoading(sourceKey: String, force: Bool) -> Bool {
guard self.configSchemaLoading else { return false }
if force || self.configSchemaLoadingSourceKey != sourceKey {
self.configSchemaReloadPending = true
}
return true
}
private func currentConfigCacheSourceKey() -> String {
let root = OpenClawConfigFile.loadDict()
let settings = CommandResolver.connectionSettings(configRoot: root)
let env = ProcessInfo.processInfo.environment
return [
"mode:\(settings.mode.rawValue)",
"target:\(settings.target)",
"identity:\(settings.identity)",
"project:\(settings.projectRoot)",
"cli:\(settings.cliPath)",
"port:\(GatewayEnvironment.gatewayPort())",
"gateway:\(Self.configFingerprint(root["gateway"]))",
"token:\(Self.configFingerprint(env["OPENCLAW_GATEWAY_TOKEN"]))",
"password:\(Self.configFingerprint(env["OPENCLAW_GATEWAY_PASSWORD"]))",
].joined(separator: "|")
}
static func normalizeConfigLookupPath(_ path: String) -> String {
let trimmed = path.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? "." : trimmed
}
private static func configFingerprint(_ value: Any?) -> String {
guard let value else { return "nil" }
if JSONSerialization.isValidJSONObject(value),
let data = try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys])
{
return "\(data.count):\(data.hashValue)"
}
let text = String(describing: value)
return "\(text.count):\(text.hashValue)"
}
}

View File

@@ -22,12 +22,15 @@ func whatsappLoginWaitRequestTimeoutMs(
extension ChannelsStore {
func start() {
guard !self.isPreview else { return }
self.startCount += 1
guard self.startCount == 1 else { return }
guard self.pollTask == nil else { return }
self.pollTask = Task.detached { [weak self] in
guard let self else { return }
await self.refresh(probe: true)
await self.loadConfigSchema()
await self.loadConfig()
await self.refresh(probe: false)
async let schemaLoad: Void = self.loadConfigSchema()
async let configLoad: Void = self.loadConfig(force: false)
_ = await (schemaLoad, configLoad)
while !Task.isCancelled {
try? await Task.sleep(nanoseconds: UInt64(self.interval * 1_000_000_000))
await self.refresh(probe: false)
@@ -36,6 +39,10 @@ extension ChannelsStore {
}
func stop() {
guard !self.isPreview else { return }
guard self.startCount > 0 else { return }
self.startCount -= 1
guard self.startCount == 0 else { return }
self.pollTask?.cancel()
self.pollTask = nil
}
@@ -46,14 +53,15 @@ extension ChannelsStore {
defer { self.isRefreshing = false }
do {
let statusTimeoutMs = probe ? 8000 : 2500
let params: [String: AnyCodable] = [
"probe": AnyCodable(probe),
"timeoutMs": AnyCodable(8000),
"timeoutMs": AnyCodable(statusTimeoutMs),
]
let snap: ChannelsStatusSnapshot = try await GatewayConnection.shared.requestDecoded(
method: .channelsStatus,
params: params,
timeoutMs: 12000)
timeoutMs: probe ? 12000 : 5000)
self.snapshot = snap
self.lastSuccess = Date()
self.lastError = nil

View File

@@ -219,12 +219,64 @@ struct ConfigSnapshot: Codable {
let issues: [Issue]?
}
struct ConfigSchemaLookupChild: Identifiable {
let key: String
let path: String
let typeLabel: String?
let required: Bool
let hasChildren: Bool
let hint: ConfigUiHint?
let hintPath: String?
var id: String {
self.path
}
init?(raw: [String: AnyCodable]) {
guard let key = raw["key"]?.stringValue,
let path = raw["path"]?.stringValue
else {
return nil
}
self.key = key
self.path = path
if let type = raw["type"]?.stringValue {
self.typeLabel = type
} else if let types = raw["type"]?.arrayValue {
self.typeLabel = types.compactMap(\.stringValue).joined(separator: " / ")
} else {
self.typeLabel = nil
}
self.required = raw["required"]?.boolValue ?? false
self.hasChildren = raw["hasChildren"]?.boolValue ?? false
if let hint = raw["hint"]?.dictionaryValue {
self.hint = ConfigUiHint(raw: hint.mapValues(\.foundationValue))
} else {
self.hint = nil
}
self.hintPath = raw["hintPath"]?.stringValue
}
}
struct ConfigSchemaLookupNode {
let path: String
let schema: ConfigSchemaNode
let hint: ConfigUiHint?
let hintPath: String?
let children: [ConfigSchemaLookupChild]
}
@MainActor
@Observable
final class ChannelsStore {
static let shared = ChannelsStore()
var snapshot: ChannelsStatusSnapshot?
var snapshot: ChannelsStatusSnapshot? {
didSet {
self.decodedChannelCache.removeAll(keepingCapacity: true)
}
}
var lastError: String?
var lastSuccess: Date?
var isRefreshing = false
@@ -239,15 +291,27 @@ final class ChannelsStore {
var isSavingConfig = false
var configSchemaLoading = false
var configSchema: ConfigSchemaNode?
var configLookupRoot: ConfigSchemaLookupNode?
var configLookupCache: [String: ConfigSchemaLookupNode] = [:]
var configLookupLoadingPaths: Set<String> = []
var configUiHints: [String: ConfigUiHint] = [:]
var configSchemaSourceKey: String?
var configSchemaLoadingSourceKey: String?
var configSchemaReloadPending = false
var configLoading = false
var configLoadingSourceKey: String?
var configForceReloadPending = false
var configDraft: [String: Any] = [:]
var configDirty = false
let interval: TimeInterval = 45
let isPreview: Bool
var startCount = 0
var pollTask: Task<Void, Never>?
var configRoot: [String: Any] = [:]
var configLoaded = false
var configSourceKey: String?
@ObservationIgnored private var decodedChannelCache: [String: Any] = [:]
func channelMetaEntry(_ id: String) -> ChannelsStatusSnapshot.ChannelUiMetaEntry? {
self.snapshot?.channelMeta?.first(where: { $0.id == id })
@@ -290,6 +354,18 @@ final class ChannelsStore {
return self.snapshot?.channelOrder ?? []
}
func decodedChannel<T: Decodable>(_ id: String, as type: T.Type) -> T? {
let key = "\(id)#\(ObjectIdentifier(type))"
if let cached = self.decodedChannelCache[key] as? T {
return cached
}
guard let decoded = self.snapshot?.decodeChannel(id, as: type) else {
return nil
}
self.decodedChannelCache[key] = decoded
return decoded
}
func applyWhatsAppLoginWaitResult(_ result: WhatsAppLoginWaitResult) {
self.whatsappLoginMessage = result.message
self.whatsappLoginConnected = result.connected

View File

@@ -426,10 +426,15 @@ enum CommandResolver {
{
let root = configRoot ?? OpenClawConfigFile.loadDict()
let mode = ConnectionModeResolver.resolve(root: root, defaults: defaults).mode
let target = defaults.string(forKey: remoteTargetKey) ?? ""
let identity = defaults.string(forKey: remoteIdentityKey) ?? ""
let projectRoot = defaults.string(forKey: remoteProjectRootKey) ?? ""
let cliPath = defaults.string(forKey: remoteCliPathKey) ?? ""
let remote = (root["gateway"] as? [String: Any])?["remote"] as? [String: Any]
let target = defaults.string(forKey: remoteTargetKey)?.nonEmpty
?? remote?["sshTarget"] as? String
?? ""
let identity = defaults.string(forKey: remoteIdentityKey)?.nonEmpty
?? remote?["sshIdentity"] as? String
?? ""
let projectRoot = defaults.string(forKey: remoteProjectRootKey)?.nonEmpty ?? ""
let cliPath = defaults.string(forKey: remoteCliPathKey)?.nonEmpty ?? ""
return RemoteSettings(
mode: mode,
target: self.sanitizedTarget(target),

View File

@@ -208,6 +208,25 @@ func isSensitivePath(_ path: ConfigPath) -> Bool {
|| key.hasSuffix("key")
}
func labelForConfigPath(_ path: ConfigPath) -> String? {
for segment in path.reversed() {
if case let .key(key) = segment {
return humanizeConfigKey(key)
}
}
return nil
}
func humanizeConfigKey(_ key: String) -> String {
key.replacingOccurrences(of: "_", with: " ")
.replacingOccurrences(of: "-", with: " ")
.replacingOccurrences(
of: "([a-z0-9])([A-Z])",
with: "$1 $2",
options: .regularExpression)
.capitalized
}
func pathKey(_ path: ConfigPath) -> String {
path.compactMap { segment -> String? in
switch segment {

View File

@@ -6,8 +6,8 @@ struct ConfigSettings: View {
private let isNixMode = ProcessInfo.processInfo.isNixMode
@Bindable var store: ChannelsStore
@State private var hasLoaded = false
@State private var activeSectionKey: String?
@State private var activeSubsection: SubsectionSelection?
@State private var activePath: String?
@State private var failedLookupPaths: Set<String> = []
init(store: ChannelsStore = .shared) {
self.store = store
@@ -23,30 +23,32 @@ struct ConfigSettings: View {
guard !self.hasLoaded else { return }
guard !self.isPreview else { return }
self.hasLoaded = true
await self.store.loadConfigSchema()
await self.store.loadConfig()
Task { await self.store.loadConfig(force: false) }
_ = await self.store.loadConfigSchemaLookup(path: ".")
self.ensureSelection()
}
.task(id: self.activePath) {
guard let activePath = self.activePath else { return }
await self.loadPath(activePath)
}
.onAppear { self.ensureSelection() }
.onChange(of: self.store.configSchemaLoading) { _, loading in
if !loading { self.ensureSelection() }
.onChange(of: self.store.configLookupRoot?.path) { _, _ in
self.failedLookupPaths.removeAll()
self.ensureSelection()
}
}
}
extension ConfigSettings {
private enum SubsectionSelection: Hashable {
case all
case key(String)
}
private struct ConfigSection: Identifiable {
let key: String
let label: String
let help: String?
let node: ConfigSchemaNode
let path: String
let hasChildren: Bool
var id: String {
self.key
self.path
}
}
@@ -54,21 +56,22 @@ extension ConfigSettings {
let key: String
let label: String
let help: String?
let node: ConfigSchemaNode
let path: ConfigPath
let path: String
let hasChildren: Bool
var id: String {
self.key
self.path
}
}
private var sections: [ConfigSection] {
guard let schema = self.store.configSchema else { return [] }
return self.resolveSections(schema)
guard let root = self.store.configLookupRoot else { return [] }
return self.resolveSections(root.children)
}
private var activeSection: ConfigSection? {
self.sections.first { $0.key == self.activeSectionKey }
guard let activePath = self.activePath else { return nil }
return self.sections.first { activePath == $0.path || activePath.hasPrefix("\($0.path).") }
}
private var sidebar: some View {
@@ -91,16 +94,16 @@ extension ConfigSettings {
private var detail: some View {
VStack(alignment: .leading, spacing: 16) {
if self.store.configSchemaLoading {
if self.store.configLookupRoot == nil,
!self.hasLoaded || self.store.configLookupLoadingPaths.contains(".")
{
ProgressView().controlSize(.small)
} else if let section = self.activeSection {
self.sectionDetail(section)
} else if self.store.configSchema != nil {
} else if self.store.configLookupRoot != nil {
self.emptyDetail
} else {
Text("Schema unavailable.")
.font(.caption)
.foregroundStyle(.secondary)
self.schemaUnavailableDetail
}
}
.frame(minWidth: 460, maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
@@ -117,6 +120,18 @@ extension ConfigSettings {
.padding(.vertical, 18)
}
private var schemaUnavailableDetail: some View {
VStack(alignment: .leading, spacing: 8) {
self.header
Text(self.store.configStatus ?? "Schema unavailable.")
.font(.callout)
.foregroundStyle(.secondary)
self.actionRow
}
.padding(.horizontal, 24)
.padding(.vertical, 18)
}
private func sectionDetail(_ section: ConfigSection) -> some View {
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 16) {
@@ -176,13 +191,14 @@ extension ConfigSettings {
Button(self.store.isSavingConfig ? "Saving…" : "Save") {
Task { await self.store.saveConfigDraft() }
}
.disabled(self.isNixMode || self.store.isSavingConfig || !self.store.configDirty)
.disabled(self.isNixMode || self.store.isSavingConfig || !self.store.configLoaded || !self.store
.configDirty)
}
.buttonStyle(.bordered)
}
private func sidebarSection(_ section: ConfigSection) -> some View {
let isExpanded = self.activeSectionKey == section.key
let isExpanded = self.activePath == section.path || self.activePath?.hasPrefix("\(section.path).") == true
let subsections = isExpanded ? self.resolveSubsections(for: section) : []
return VStack(alignment: .leading, spacing: 2) {
@@ -200,7 +216,7 @@ extension ConfigSettings {
.padding(.vertical, 5)
.padding(.horizontal, 8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(isExpanded && subsections.isEmpty
.background(self.activePath == section.path
? Color.accentColor.opacity(0.18)
: Color.clear)
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
@@ -211,9 +227,8 @@ extension ConfigSettings {
if isExpanded, !subsections.isEmpty {
VStack(alignment: .leading, spacing: 1) {
self.sidebarSubRow(title: "All", key: nil, sectionKey: section.key)
ForEach(subsections) { sub in
self.sidebarSubRow(title: sub.label, key: sub.key, sectionKey: section.key)
self.sidebarSubRow(sub)
}
}
.padding(.leading, 20)
@@ -223,21 +238,12 @@ extension ConfigSettings {
.animation(.easeInOut(duration: 0.18), value: isExpanded)
}
private func sidebarSubRow(title: String, key: String?, sectionKey: String) -> some View {
let isSelected: Bool = {
guard self.activeSectionKey == sectionKey else { return false }
if let key { return self.activeSubsection == .key(key) }
return self.activeSubsection == .all
}()
private func sidebarSubRow(_ subsection: ConfigSubsection) -> some View {
let isSelected = self.activePath == subsection.path
return Button {
if let key {
self.activeSubsection = .key(key)
} else {
self.activeSubsection = .all
}
self.selectPath(subsection.path)
} label: {
Text(title)
Text(subsection.label)
.font(.callout)
.lineLimit(1)
.padding(.vertical, 4)
@@ -252,123 +258,190 @@ extension ConfigSettings {
}
private func sectionForm(_ section: ConfigSection) -> some View {
let subsection = self.activeSubsection
let defaultPath: ConfigPath = [.key(section.key)]
let subsections = self.resolveSubsections(for: section)
let resolved: (ConfigSchemaNode, ConfigPath) = {
if case let .key(key) = subsection,
let match = subsections.first(where: { $0.key == key })
{
return (match.node, match.path)
let path = self.activePath ?? section.path
if self.store.configLookupLoadingPaths.contains(path) {
return AnyView(ProgressView().controlSize(.small))
}
guard let node = self.store.configLookupNode(path: path) else {
if self.failedLookupPaths.contains(path) {
return AnyView(self.lookupUnavailable(path: path))
}
return (self.resolvedSchemaNode(section.node), defaultPath)
}()
return ConfigSchemaForm(store: self.store, schema: resolved.0, path: resolved.1)
.disabled(self.isNixMode)
return AnyView(ProgressView().controlSize(.small))
}
if !node.children.isEmpty, !Self.shouldRenderFormEditor(for: node.schema) {
return AnyView(self.lookupChildrenList(node))
}
guard self.store.configLoaded else {
return AnyView(
HStack(spacing: 8) {
ProgressView().controlSize(.small)
Text("Loading current values…")
.font(.caption)
.foregroundStyle(.secondary)
})
}
guard let configPath = Self.configPath(from: node.path) else {
return AnyView(
Text("Wildcard config entries are edited from their concrete key.")
.font(.caption)
.foregroundStyle(.secondary))
}
return AnyView(
ConfigSchemaForm(store: self.store, schema: node.schema, path: configPath)
.disabled(self.isNixMode))
}
private func ensureSelection() {
guard let schema = self.store.configSchema else { return }
let sections = self.resolveSections(schema)
let sections = self.sections
guard !sections.isEmpty else { return }
let active = sections.first { $0.key == self.activeSectionKey } ?? sections[0]
if self.activeSectionKey != active.key {
self.activeSectionKey = active.key
}
self.ensureSubsection(for: active)
}
private func ensureSubsection(for section: ConfigSection) {
let subsections = self.resolveSubsections(for: section)
guard !subsections.isEmpty else {
self.activeSubsection = nil
if let activePath = self.activePath,
sections.contains(where: { activePath == $0.path || activePath.hasPrefix("\($0.path).") })
{
return
}
switch self.activeSubsection {
case .all:
return
case let .key(key):
if subsections.contains(where: { $0.key == key }) { return }
case .none:
break
}
if let first = subsections.first {
self.activeSubsection = .key(first.key)
}
self.selectSection(sections[0])
}
private func selectSection(_ section: ConfigSection) {
guard self.activeSectionKey != section.key else { return }
self.activeSectionKey = section.key
let subsections = self.resolveSubsections(for: section)
if let first = subsections.first {
self.activeSubsection = .key(first.key)
} else {
self.activeSubsection = nil
self.activePath = section.path
}
private func selectPath(_ path: String) {
self.activePath = path
}
private func lookupUnavailable(path: String) -> some View {
VStack(alignment: .leading, spacing: 8) {
Text(self.store.configStatus ?? "Schema unavailable.")
.font(.callout)
.foregroundStyle(.secondary)
Button("Retry") {
self.failedLookupPaths.remove(path)
Task { await self.loadPath(path) }
}
.buttonStyle(.bordered)
}
}
private func resolveSections(_ root: ConfigSchemaNode) -> [ConfigSection] {
let node = self.resolvedSchemaNode(root)
let hints = self.store.configUiHints
let keys = node.properties.keys.sorted { lhs, rhs in
let orderA = hintForPath([.key(lhs)], hints: hints)?.order ?? 0
let orderB = hintForPath([.key(rhs)], hints: hints)?.order ?? 0
if orderA != orderB { return orderA < orderB }
return lhs < rhs
private func lookupChildrenList(_ node: ConfigSchemaLookupNode) -> some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(node.children) { child in
Button {
self.selectPath(child.path)
} label: {
HStack(spacing: 8) {
VStack(alignment: .leading, spacing: 2) {
Text(self.label(for: child))
.font(.callout.weight(.semibold))
if let help = child.hint?.help {
Text(help)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
} else if let type = child.typeLabel {
Text(type)
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
if child.required {
Text("Required")
.font(.caption)
.foregroundStyle(.secondary)
}
Image(systemName: child.hasChildren ? "chevron.right" : "slider.horizontal.3")
.font(.caption.weight(.semibold))
.foregroundStyle(.tertiary)
}
.padding(.vertical, 8)
.padding(.horizontal, 10)
.background(Color.primary.opacity(0.04))
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
}
return keys.compactMap { key in
guard let child = node.properties[key] else { return nil }
let path: ConfigPath = [.key(key)]
let hint = hintForPath(path, hints: hints)
let label = hint?.label
?? child.title
?? self.humanize(key)
let help = hint?.help ?? child.description
return ConfigSection(key: key, label: label, help: help, node: child)
}
private func resolveSections(_ children: [ConfigSchemaLookupChild]) -> [ConfigSection] {
children
.sorted(by: self.sortLookupChildren)
.map { child in
ConfigSection(
key: child.key,
label: self.label(for: child),
help: child.hint?.help,
path: child.path,
hasChildren: child.hasChildren)
}
}
private func resolveSubsections(for section: ConfigSection) -> [ConfigSubsection] {
let node = self.resolvedSchemaNode(section.node)
guard node.schemaType == "object" else { return [] }
let hints = self.store.configUiHints
let keys = node.properties.keys.sorted { lhs, rhs in
let orderA = hintForPath([.key(section.key), .key(lhs)], hints: hints)?.order ?? 0
let orderB = hintForPath([.key(section.key), .key(rhs)], hints: hints)?.order ?? 0
if orderA != orderB { return orderA < orderB }
return lhs < rhs
guard let node = self.store.configLookupNode(path: section.path) else {
return []
}
return node.children
.sorted(by: self.sortLookupChildren)
.map { child in
ConfigSubsection(
key: child.key,
label: self.label(for: child),
help: child.hint?.help,
path: child.path,
hasChildren: child.hasChildren)
}
}
return keys.compactMap { key in
guard let child = node.properties[key] else { return nil }
let path: ConfigPath = [.key(section.key), .key(key)]
let hint = hintForPath(path, hints: hints)
let label = hint?.label
?? child.title
?? self.humanize(key)
let help = hint?.help ?? child.description
return ConfigSubsection(
key: key,
label: label,
help: help,
node: child,
path: path)
private func loadPath(_ path: String) async {
guard self.store.configLookupNode(path: path) == nil else {
self.failedLookupPaths.remove(path)
return
}
guard !self.store.configLookupLoadingPaths.contains(path) else { return }
if await self.store.loadConfigSchemaLookup(path: path) == nil {
self.failedLookupPaths.insert(path)
} else {
self.failedLookupPaths.remove(path)
}
}
private func resolvedSchemaNode(_ node: ConfigSchemaNode) -> ConfigSchemaNode {
let variants = node.anyOf.isEmpty ? node.oneOf : node.anyOf
if !variants.isEmpty {
let nonNull = variants.filter { !$0.isNullSchema }
if nonNull.count == 1, let only = nonNull.first { return only }
private func label(for child: ConfigSchemaLookupChild) -> String {
child.hint?.label
?? self.humanize(child.key)
}
private func sortLookupChildren(_ lhs: ConfigSchemaLookupChild, _ rhs: ConfigSchemaLookupChild) -> Bool {
let orderA = lhs.hint?.order ?? 0
let orderB = rhs.hint?.order ?? 0
if orderA != orderB { return orderA < orderB }
return lhs.key < rhs.key
}
private static func configPath(from lookupPath: String) -> ConfigPath? {
guard lookupPath != "." else { return [] }
let normalized = lookupPath
.replacingOccurrences(of: "[", with: ".")
.replacingOccurrences(of: "]", with: "")
let parts = normalized
.split(separator: ".")
.map(String.init)
.filter { !$0.isEmpty }
guard !parts.contains("*") else { return nil }
return parts.map { part in
if let index = Int(part) {
return .index(index)
}
return .key(part)
}
return node
}
private static func shouldRenderFormEditor(for schema: ConfigSchemaNode) -> Bool {
if schema.schemaType == "array" { return true }
return schema.additionalProperties != nil
}
private func humanize(_ key: String) -> String {

View File

@@ -9,31 +9,51 @@ struct ContextRootMenuLabelView: View {
MenuItemHighlightColors.palette(self.isHighlighted)
}
private var usesStackedLayout: Bool {
self.subtitle.count > 28 || self.subtitle.contains("\n")
}
var body: some View {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text("Context")
.font(.callout.weight(.semibold))
.foregroundStyle(self.palette.primary)
.lineLimit(1)
.layoutPriority(1)
HStack(alignment: self.usesStackedLayout ? .top : .firstTextBaseline, spacing: 8) {
VStack(alignment: .leading, spacing: 2) {
Text("Context")
.font(.callout.weight(.semibold))
.foregroundStyle(self.palette.primary)
.lineLimit(1)
if self.usesStackedLayout {
self.subtitleText
.lineLimit(5)
.fixedSize(horizontal: false, vertical: true)
}
}
.layoutPriority(1)
Spacer(minLength: 8)
Text(self.subtitle)
.font(.caption.monospacedDigit())
.foregroundStyle(self.palette.secondary)
.lineLimit(1)
.truncationMode(.tail)
.layoutPriority(2)
if !self.usesStackedLayout {
self.subtitleText
.lineLimit(1)
.layoutPriority(2)
}
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.foregroundStyle(self.palette.secondary)
.padding(.leading, 2)
.padding(.top, self.usesStackedLayout ? 2 : 0)
}
.padding(.vertical, 8)
.padding(.vertical, self.usesStackedLayout ? 7 : 8)
.padding(.leading, 22)
.padding(.trailing, 14)
.frame(width: max(1, self.width), alignment: .leading)
}
private var subtitleText: some View {
Text(self.subtitle)
.font(.caption.monospacedDigit())
.foregroundStyle(self.palette.secondary)
.multilineTextAlignment(.leading)
.truncationMode(.tail)
}
}

View File

@@ -240,6 +240,12 @@ final class ControlChannel {
case .timedOut:
return "Gateway request timed out; check gateway on localhost:\(port)."
case .notConnectedToInternet:
if Self.isLikelyLocalNetworkPermissionBlock() {
return """
macOS is blocking OpenClaw Local Network access.
Allow OpenClaw in System Settings → Privacy & Security → Local Network, then relaunch the app.
"""
}
return "No network connectivity; cannot reach gateway."
default:
break
@@ -257,6 +263,22 @@ final class ControlChannel {
return "Gateway error: \(trimmed)"
}
private static func isLikelyLocalNetworkPermissionBlock() -> Bool {
let root = OpenClawConfigFile.loadDict()
let resolution = GatewayRemoteConfig.resolveTransportResolution(root: root)
guard ConnectionModeResolver.resolve(root: root).mode == .remote,
resolution.transport == .direct,
let url = resolution.directURL,
url.scheme?.lowercased() == "ws",
let host = url.host,
GatewayRemoteConfig.isTrustedPlaintextRemoteHost(host),
!LoopbackHost.isLoopbackHost(host)
else {
return false
}
return true
}
private func scheduleRecovery(reason: String) {
let now = Date()
if let last = self.lastRecoveryAt, now.timeIntervalSince(last) < 10 { return }

View File

@@ -20,12 +20,11 @@ enum CritterIconRenderer {
private struct Geometry {
let bodyRect: CGRect
let bodyCorner: CGFloat
let leftArmRect: CGRect
let rightArmRect: CGRect
let leftEarRect: CGRect
let rightEarRect: CGRect
let earCorner: CGFloat
let earW: CGFloat
let earH: CGFloat
let antennaLineWidth: CGFloat
let legW: CGFloat
let legH: CGFloat
let legSpacing: CGFloat
@@ -33,7 +32,7 @@ enum CritterIconRenderer {
let legYBase: CGFloat
let legLift: CGFloat
let legHeightScale: CGFloat
let eyeW: CGFloat
let eyeSize: CGSize
let eyeY: CGFloat
let eyeOffset: CGFloat
@@ -43,46 +42,60 @@ enum CritterIconRenderer {
let snapX = canvas.snapX
let snapY = canvas.snapY
let bodyW = snapX(w * 0.78)
let bodyH = snapY(h * 0.58)
let bodyW = snapX(w * 0.68)
let bodyH = snapY(h * 0.68)
let bodyX = snapX((w - bodyW) / 2)
let bodyY = snapY(h * 0.36)
let bodyCorner = snapX(w * 0.09)
let bodyY = snapY(h * 0.24)
let earW = snapX(w * 0.22)
let earH = snapY(bodyH * 0.54 * earScale * (1 - 0.08 * abs(earWiggle)))
let earCorner = snapX(earW * 0.24)
let armSize = snapX(w * 0.2)
let armY = snapY(bodyY + bodyH * 0.36)
let leftArmRect = CGRect(
x: snapX(bodyX - armSize * 0.62),
y: armY,
width: armSize,
height: armSize)
let rightArmRect = CGRect(
x: snapX(bodyX + bodyW - armSize * 0.38),
y: armY,
width: armSize,
height: armSize)
let antennaW = snapX(w * 0.22)
let antennaH = snapY(min(bodyH * 0.24 * earScale, h * 0.19))
let antennaLineWidth = max(snapX(w * 0.095), canvas.stepX * 2) * min(1.2, 0.94 + earScale * 0.06)
let antennaLift = snapY(earWiggle * 0.35)
let leftEarRect = CGRect(
x: snapX(bodyX - earW * 0.55 + earWiggle),
y: snapY(bodyY + bodyH * 0.08 + earWiggle * 0.4),
width: earW,
height: earH)
x: snapX(bodyX + bodyW * 0.18 - antennaW * 0.35 - earWiggle * 0.28),
y: snapY(bodyY + bodyH * 0.86 + antennaLift),
width: antennaW,
height: antennaH)
let rightEarRect = CGRect(
x: snapX(bodyX + bodyW - earW * 0.45 - earWiggle),
y: snapY(bodyY + bodyH * 0.08 - earWiggle * 0.4),
width: earW,
height: earH)
x: snapX(bodyX + bodyW * 0.82 - antennaW * 0.65 + earWiggle * 0.28),
y: snapY(bodyY + bodyH * 0.86 - antennaLift),
width: antennaW,
height: antennaH)
let legW = snapX(w * 0.11)
let legH = snapY(h * 0.26)
let legSpacing = snapX(w * 0.085)
let legsWidth = snapX(4 * legW + 3 * legSpacing)
let legW = snapX(w * 0.15)
let legH = snapY(h * 0.25)
let legSpacing = snapX(w * 0.16)
let legsWidth = snapX(2 * legW + legSpacing)
let legStartX = snapX((w - legsWidth) / 2)
let legLift = snapY(legH * 0.35 * legWiggle)
let legYBase = snapY(bodyY - legH + h * 0.05)
let legYBase = snapY(bodyY - legH * 0.58)
let legHeightScale = 1 - 0.12 * legWiggle
let eyeW = snapX(bodyW * 0.2)
let eyeY = snapY(bodyY + bodyH * 0.56)
let eyeOffset = snapX(bodyW * 0.24)
let eyeSize = CGSize(
width: snapX(bodyW * 0.15),
height: snapY(bodyH * 0.2))
let eyeY = snapY(bodyY + bodyH * 0.58)
let eyeOffset = snapX(bodyW * 0.22)
self.bodyRect = CGRect(x: bodyX, y: bodyY, width: bodyW, height: bodyH)
self.bodyCorner = bodyCorner
self.leftArmRect = leftArmRect
self.rightArmRect = rightArmRect
self.leftEarRect = leftEarRect
self.rightEarRect = rightEarRect
self.earCorner = earCorner
self.earW = earW
self.earH = earH
self.antennaLineWidth = antennaLineWidth
self.legW = legW
self.legH = legH
self.legSpacing = legSpacing
@@ -90,7 +103,7 @@ enum CritterIconRenderer {
self.legYBase = legYBase
self.legLift = legLift
self.legHeightScale = legHeightScale
self.eyeW = eyeW
self.eyeSize = eyeSize
self.eyeY = eyeY
self.eyeOffset = eyeOffset
}
@@ -98,8 +111,6 @@ enum CritterIconRenderer {
private struct FaceOptions {
let blink: CGFloat
let earHoles: Bool
let earScale: CGFloat
let eyesClosedLines: Bool
}
@@ -125,16 +136,18 @@ enum CritterIconRenderer {
}
NSGraphicsContext.current = context
context.imageInterpolation = .none
context.cgContext.setShouldAntialias(false)
context.cgContext.setShouldAntialias(true)
let canvas = self.makeCanvas(for: rep, context: context)
let geometry = Geometry(canvas: canvas, legWiggle: legWiggle, earWiggle: earWiggle, earScale: earScale)
let geometry = Geometry(
canvas: canvas,
legWiggle: legWiggle,
earWiggle: earWiggle,
earScale: earHoles ? max(earScale, 1.2) : earScale)
self.drawBody(in: canvas, geometry: geometry)
let face = FaceOptions(
blink: blink,
earHoles: earHoles,
earScale: earScale,
eyesClosedLines: eyesClosedLines)
self.drawFace(in: canvas, geometry: geometry, options: face)
@@ -186,25 +199,41 @@ enum CritterIconRenderer {
}
private static func drawBody(in canvas: Canvas, geometry: Geometry) {
canvas.context.setStrokeColor(NSColor.labelColor.cgColor)
canvas.context.setLineWidth(geometry.antennaLineWidth)
canvas.context.setLineCap(.round)
canvas.context.setLineJoin(.round)
let leftStart = CGPoint(
x: canvas.snapX(geometry.bodyRect.minX + geometry.bodyRect.width * 0.34),
y: canvas.snapY(geometry.bodyRect.maxY - geometry.antennaLineWidth * 0.22))
let leftEnd = CGPoint(
x: canvas.snapX(geometry.leftEarRect.minX),
y: canvas.snapY(geometry.leftEarRect.maxY))
let leftControl = CGPoint(
x: canvas.snapX(geometry.leftEarRect.midX),
y: canvas.snapY(geometry.leftEarRect.minY))
let rightStart = CGPoint(
x: canvas.snapX(geometry.bodyRect.maxX - geometry.bodyRect.width * 0.34),
y: canvas.snapY(geometry.bodyRect.maxY - geometry.antennaLineWidth * 0.22))
let rightEnd = CGPoint(
x: canvas.snapX(geometry.rightEarRect.maxX),
y: canvas.snapY(geometry.rightEarRect.maxY))
let rightControl = CGPoint(
x: canvas.snapX(geometry.rightEarRect.midX),
y: canvas.snapY(geometry.rightEarRect.minY))
let antennae = CGMutablePath()
antennae.move(to: leftStart)
antennae.addQuadCurve(to: leftEnd, control: leftControl)
antennae.move(to: rightStart)
antennae.addQuadCurve(to: rightEnd, control: rightControl)
canvas.context.addPath(antennae)
canvas.context.strokePath()
canvas.context.setFillColor(NSColor.labelColor.cgColor)
canvas.context.addPath(CGPath(
roundedRect: geometry.bodyRect,
cornerWidth: geometry.bodyCorner,
cornerHeight: geometry.bodyCorner,
transform: nil))
canvas.context.addPath(CGPath(
roundedRect: geometry.leftEarRect,
cornerWidth: geometry.earCorner,
cornerHeight: geometry.earCorner,
transform: nil))
canvas.context.addPath(CGPath(
roundedRect: geometry.rightEarRect,
cornerWidth: geometry.earCorner,
cornerHeight: geometry.earCorner,
transform: nil))
for i in 0..<4 {
for i in 0..<2 {
let x = geometry.legStartX + CGFloat(i) * (geometry.legW + geometry.legSpacing)
let lift = i % 2 == 0 ? geometry.legLift : -geometry.legLift
let rect = CGRect(
@@ -218,6 +247,10 @@ enum CritterIconRenderer {
cornerHeight: geometry.legW * 0.34,
transform: nil))
}
canvas.context.addEllipse(in: geometry.leftArmRect)
canvas.context.addEllipse(in: geometry.rightArmRect)
canvas.context.addEllipse(in: geometry.bodyRect)
canvas.context.fillPath()
}
@@ -236,35 +269,8 @@ enum CritterIconRenderer {
x: canvas.snapX(canvas.w / 2 + geometry.eyeOffset),
y: canvas.snapY(geometry.eyeY))
if options.earHoles || options.earScale > 1.05 {
let holeW = canvas.snapX(geometry.earW * 0.6)
let holeH = canvas.snapY(geometry.earH * 0.46)
let holeCorner = canvas.snapX(holeW * 0.34)
let leftHoleRect = CGRect(
x: canvas.snapX(geometry.leftEarRect.midX - holeW / 2),
y: canvas.snapY(geometry.leftEarRect.midY - holeH / 2 + geometry.earH * 0.04),
width: holeW,
height: holeH)
let rightHoleRect = CGRect(
x: canvas.snapX(geometry.rightEarRect.midX - holeW / 2),
y: canvas.snapY(geometry.rightEarRect.midY - holeH / 2 + geometry.earH * 0.04),
width: holeW,
height: holeH)
canvas.context.addPath(CGPath(
roundedRect: leftHoleRect,
cornerWidth: holeCorner,
cornerHeight: holeCorner,
transform: nil))
canvas.context.addPath(CGPath(
roundedRect: rightHoleRect,
cornerWidth: holeCorner,
cornerHeight: holeCorner,
transform: nil))
}
if options.eyesClosedLines {
let lineW = canvas.snapX(geometry.eyeW * 0.95)
let lineW = canvas.snapX(geometry.eyeSize.width * 1.15)
let lineH = canvas.snapY(max(canvas.stepY * 2, geometry.bodyRect.height * 0.06))
let corner = canvas.snapX(lineH * 0.6)
let leftRect = CGRect(
@@ -289,34 +295,20 @@ enum CritterIconRenderer {
transform: nil))
} else {
let eyeOpen = max(0.05, 1 - options.blink)
let eyeH = canvas.snapY(geometry.bodyRect.height * 0.26 * eyeOpen)
let eyeH = canvas.snapY(geometry.eyeSize.height * eyeOpen)
let leftRect = CGRect(
x: canvas.snapX(leftCenter.x - geometry.eyeSize.width / 2),
y: canvas.snapY(leftCenter.y - eyeH / 2),
width: geometry.eyeSize.width,
height: eyeH)
let rightRect = CGRect(
x: canvas.snapX(rightCenter.x - geometry.eyeSize.width / 2),
y: canvas.snapY(rightCenter.y - eyeH / 2),
width: geometry.eyeSize.width,
height: eyeH)
let left = CGMutablePath()
left.move(to: CGPoint(
x: canvas.snapX(leftCenter.x - geometry.eyeW / 2),
y: canvas.snapY(leftCenter.y - eyeH)))
left.addLine(to: CGPoint(
x: canvas.snapX(leftCenter.x + geometry.eyeW / 2),
y: canvas.snapY(leftCenter.y)))
left.addLine(to: CGPoint(
x: canvas.snapX(leftCenter.x - geometry.eyeW / 2),
y: canvas.snapY(leftCenter.y + eyeH)))
left.closeSubpath()
let right = CGMutablePath()
right.move(to: CGPoint(
x: canvas.snapX(rightCenter.x + geometry.eyeW / 2),
y: canvas.snapY(rightCenter.y - eyeH)))
right.addLine(to: CGPoint(
x: canvas.snapX(rightCenter.x - geometry.eyeW / 2),
y: canvas.snapY(rightCenter.y)))
right.addLine(to: CGPoint(
x: canvas.snapX(rightCenter.x + geometry.eyeW / 2),
y: canvas.snapY(rightCenter.y + eyeH)))
right.closeSubpath()
canvas.context.addPath(left)
canvas.context.addPath(right)
canvas.context.addEllipse(in: leftRect)
canvas.context.addEllipse(in: rightRect)
}
canvas.context.fillPath()

View File

@@ -2,15 +2,20 @@ import SwiftUI
extension CronSettings {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
VStack(alignment: .leading, spacing: 16) {
self.header
self.schedulerBanner
self.content
Spacer(minLength: 0)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding(.leading, 18)
.padding(.trailing, SettingsLayout.scrollbarGutter)
.onAppear {
self.store.start()
self.channelsStore.start()
self.updateActiveWork(active: self.isActive)
}
.onChange(of: self.isActive) { _, active in
self.updateActiveWork(active: active)
}
.onDisappear {
self.store.stop()
@@ -48,10 +53,16 @@ extension CronSettings {
Text(job.displayName)
}
}
.onChange(of: self.store.selectedJobId) { _, newValue in
guard let newValue else { return }
Task { await self.store.refreshRuns(jobId: newValue) }
}
}
private func updateActiveWork(active: Bool) {
if active {
self.store.start()
self.channelsStore.start()
} else {
self.store.stop()
self.channelsStore.stop()
}
}
var schedulerBanner: some View {
@@ -89,16 +100,18 @@ extension CronSettings {
}
var header: some View {
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 4) {
Text("Cron")
.font(.headline)
Text("Manage Gateway cron jobs (main session vs isolated runs) and inspect run history.")
.font(.footnote)
HStack(alignment: .top, spacing: 16) {
VStack(alignment: .leading, spacing: 5) {
Text("Cron Jobs")
.font(.title3.weight(.semibold))
Text("Manage Gateway cron jobs and inspect run history.")
.font(.callout)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
Spacer(minLength: 16)
HStack(spacing: 8) {
Button {
Task { await self.store.refreshJobs() }
@@ -133,14 +146,34 @@ extension CronSettings {
.foregroundStyle(.secondary)
}
List(selection: self.$store.selectedJobId) {
ForEach(self.store.jobs) { job in
self.jobRow(job)
.tag(job.id)
ScrollView(.vertical) {
LazyVStack(alignment: .leading, spacing: 4) {
ForEach(self.store.jobs) { job in
Button {
self.selectJob(job.id)
} label: {
self.jobRow(job)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 8)
.background(
self.store.selectedJobId == job.id
? Color.accentColor.opacity(0.18) : .clear)
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
}
.buttonStyle(.plain)
.contextMenu { self.jobContextMenu(job) }
}
if self.store.jobs.isEmpty {
Text("No cron jobs yet.")
.font(.footnote)
.foregroundStyle(.secondary)
.padding(.horizontal, 8)
.padding(.vertical, 6)
}
}
.padding(.vertical, 4)
}
.listStyle(.inset)
}
.frame(width: 250)
@@ -151,6 +184,11 @@ extension CronSettings {
}
}
private func selectJob(_ id: String) {
self.store.selectedJobId = id
Task { await self.store.refreshRuns(jobId: id) }
}
@ViewBuilder
var detail: some View {
if let selected = self.selectedJob {

View File

@@ -104,7 +104,6 @@ extension CronSettings {
store.runEntries = [run]
let view = CronSettings(store: store, channelsStore: ChannelsStore(isPreview: true))
_ = view.body
_ = view.jobRow(job)
_ = view.jobContextMenu(job)
_ = view.detailHeader(job)

View File

@@ -4,14 +4,16 @@ import SwiftUI
struct CronSettings: View {
@Bindable var store: CronJobsStore
@Bindable var channelsStore: ChannelsStore
let isActive: Bool
@State var showEditor = false
@State var editingJob: CronJob?
@State var editorError: String?
@State var isSaving = false
@State var confirmDelete: CronJob?
init(store: CronJobsStore = .shared, channelsStore: ChannelsStore = .shared) {
init(store: CronJobsStore = .shared, channelsStore: ChannelsStore = .shared, isActive: Bool = true) {
self.store = store
self.channelsStore = channelsStore
self.isActive = isActive
}
}

View File

@@ -0,0 +1,135 @@
import AppKit
import Foundation
import OpenClawKit
import OSLog
private let dashboardManagerLogger = Logger(subsystem: "ai.openclaw", category: "DashboardManager")
@MainActor
final class DashboardManager {
static let shared = DashboardManager()
private var controller: DashboardWindowController?
private static let failureURL = URL(string: "about:blank")!
private init() {}
@discardableResult
func showConfiguredWindowIfPossible() -> Bool {
let mode = AppStateStore.shared.connectionMode
guard let config = self.immediateDashboardConfig(mode: mode),
let url = try? GatewayEndpointStore.dashboardURL(
for: config,
mode: mode,
authToken: config.token)
else {
return false
}
let auth = DashboardWindowAuth(
gatewayUrl: Self.websocketURLString(for: url),
token: config.token,
password: config.password?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty)
guard auth.hasCredential else {
return false
}
if let controller {
controller.show(url: url, auth: auth)
} else {
let controller = DashboardWindowController(url: url, auth: auth)
self.controller = controller
controller.show(url: url, auth: auth)
}
Task { _ = try? await ControlChannel.shared.health(timeout: 3) }
return true
}
func show() async throws {
let mode = AppStateStore.shared.connectionMode
dashboardManagerLogger.info("dashboard show requested mode=\(String(describing: mode), privacy: .public)")
let config = try await self.dashboardConfig(mode: mode)
dashboardManagerLogger.info("dashboard config url=\(config.url.absoluteString, privacy: .public)")
let token = await GatewayConnection.shared.controlUiAutoAuthToken(config: config)
let url = try GatewayEndpointStore.dashboardURL(for: config, mode: mode, authToken: token)
let auth = DashboardWindowAuth(
gatewayUrl: Self.websocketURLString(for: url),
token: token,
password: config.password?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty)
if let controller {
dashboardManagerLogger.info("dashboard reuse window url=\(url.absoluteString, privacy: .public)")
controller.show(url: url, auth: auth)
return
}
dashboardManagerLogger.info("dashboard create window url=\(url.absoluteString, privacy: .public)")
let controller = DashboardWindowController(url: url, auth: auth)
self.controller = controller
controller.show(url: url, auth: auth)
// Refresh the cached hello payload without blocking window creation.
Task { _ = try? await ControlChannel.shared.health(timeout: 3) }
}
func showFailure(_ error: Error) {
let message = (error as NSError).localizedDescription
dashboardManagerLogger.error("dashboard setup failed error=\(message, privacy: .public)")
let controller = self.controller ?? DashboardWindowController(
url: Self.failureURL,
auth: DashboardWindowAuth(gatewayUrl: nil, token: nil, password: nil))
self.controller = controller
controller.showFailure(
title: "Dashboard unavailable",
message: message,
detail: "Check Settings → Connection or use Debug → Reset Remote Tunnel, then try again.")
}
func close() {
self.controller?.closeDashboard()
}
private static func websocketURLString(for dashboardURL: URL) -> String {
guard var components = URLComponents(url: dashboardURL, resolvingAgainstBaseURL: false) else {
return dashboardURL.absoluteString
}
switch components.scheme?.lowercased() {
case "https":
components.scheme = "wss"
default:
components.scheme = "ws"
}
components.queryItems = nil
components.fragment = nil
return components.url?.absoluteString ?? dashboardURL.absoluteString
}
private func dashboardConfig(mode: AppState.ConnectionMode) async throws -> GatewayConnection.Config {
if let config = self.immediateDashboardConfig(mode: mode) {
return config
}
return try await Task.detached(priority: .userInitiated) {
await GatewayEndpointStore.shared.refresh()
return try await GatewayEndpointStore.shared.requireConfig()
}.value
}
private func immediateDashboardConfig(mode: AppState.ConnectionMode) -> GatewayConnection.Config? {
let root = OpenClawConfigFile.loadDict()
let resolution = GatewayRemoteConfig.resolveTransportResolution(root: root)
if mode == .remote,
resolution.transport == .direct,
let url = resolution.directURL
{
return (
url,
GatewayRemoteConfig.resolveTokenString(root: root),
GatewayRemoteConfig.resolvePasswordString(root: root))
}
if mode == .local {
return GatewayEndpointStore.localConfig()
}
return nil
}
}

View File

@@ -0,0 +1,21 @@
import AppKit
import Foundation
import OSLog
let dashboardWindowLogger = Logger(subsystem: "ai.openclaw", category: "DashboardWindow")
enum DashboardWindowLayout {
static let windowSize = NSSize(width: 1240, height: 860)
static let windowMinSize = NSSize(width: 900, height: 620)
}
struct DashboardWindowAuth: Equatable {
var gatewayUrl: String?
var token: String?
var password: String?
var hasCredential: Bool {
self.token?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ||
self.password?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
}
}

Some files were not shown because too many files have changed in this diff Show More