Compare commits

..

2145 Commits

Author SHA1 Message Date
Alex Knight
66354a4258 fix: preserve codex preflight compaction route 2026-05-25 12:21:54 +10:00
Vincent Koc
4798264a29 fix(e2e): harden bundled lifecycle probe on Windows 2026-05-25 04:14:00 +02:00
Vincent Koc
60c0f249ad test(e2e): sample kitchen sink rpc peak rss 2026-05-25 03:50:01 +02:00
Vincent Koc
ea3bb9282c fix(scripts): remove stale deadcode allowlist entries 2026-05-25 03:40:29 +02:00
Galin Iliev
b5c1199217 fix(telegram): route polling diagnostics away from errors
Route normal [telegram][diag] polling diagnostics through runtime.log while keeping non-diag Telegram warnings/errors and offset persistence failures on runtime.error.

Verification:
- node scripts/run-vitest.mjs extensions/telegram/src/monitor.test.ts (34 passed)
- git diff --check
- CI run 26378692736 passed on 979c6f31a4

Fixes #82957
2026-05-24 18:39:52 -07:00
Vincent Koc
793e300cc5 fix(plugins): support linked source checkouts on Windows 2026-05-25 03:36:06 +02:00
Galin Iliev
42bdc949f2 fix(gateway): back off session tool mirrors under pressure (#84846)
Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
2026-05-24 18:34:37 -07:00
Gio Della-Libera
06bf302864 fix(config): skip shell env fallback on Windows (#85739) 2026-05-24 18:27:08 -07:00
Galin Iliev
14590445a6 fix(gateway): avoid duplicate session message broadcasts 2026-05-24 18:26:42 -07:00
Omar Shahine
f37fbc9ef4 fix: repair anchorless iMessage watch payloads
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes #84470.
Refs #84503.

Thanks @zhangguiping-xydt and @zqchris.
2026-05-24 18:13:03 -07:00
Gio Della-Libera
749692ec37 fix(cli): route node status hints to stdout (#85780) 2026-05-24 18:11:38 -07:00
Gio Della-Libera
3a72a30074 fix(oc-path): support deep config edits (#86060) 2026-05-24 18:10:02 -07:00
Vincent Koc
f3f4f29dba fix(config): quiet benign metadata anomaly output 2026-05-25 03:06:28 +02:00
Vincent Koc
732cf54240 fix(test): fail multi-node update regressions 2026-05-25 02:44:02 +02:00
Damian Finol
f09b4ebe31 fix(google-vertex): support production ADC modes (#83971)
Fix Google Vertex production ADC mode support by routing explicit google-vertex models to the Vertex transport and relying on google-auth-library for request-time ADC resolution.

Verification:
- pnpm install --frozen-lockfile
- pnpm test extensions/google/transport-stream.test.ts extensions/google/index.test.ts src/config/zod-schema.models.test.ts src/agents/pi-embedded-runner/model.inline-provider.test.ts -- --reporter=verbose
- pnpm check:changed
- GitHub PR checks green on c4b7cad4df
- Live ADC smoke reached Google Vertex auth/transport and failed only because the configured redacted project has the Vertex AI API disabled

Co-authored-by: Damian Finol <damian@felixpago.com>
2026-05-25 01:37:52 +01:00
Vincent Koc
fa3ff4d503 test(e2e): expose corrupt plugin deps smoke 2026-05-25 02:27:53 +02:00
Peter Steinberger
d9af23fb5a fix(codex): log app-server approval promotion trigger 2026-05-25 01:26:37 +01:00
Vincent Koc
ec168fa2bd test(e2e): harden multi-node update smoke 2026-05-25 01:59:32 +02:00
Andy Ye
8dc6b4d330 Clean up browser MCP subprocess tree (#85832)
* fix: clean up browser MCP subprocess tree

* fix: clean up windows browser mcp tree before close

* fix(browser): repair chrome mcp cleanup rebase

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 00:57:34 +01:00
Sebastien Tardif
907bc0371c fix(agents): log warnings instead of swallowing subagent errors (#82943)
* fix: log subagent swallowed errors in hook emission and restore paths

Wire createSubsystemLogger into the two silent catch blocks that
discard errors during subagent lifecycle:

1. emitSubagentEndedHookOnce (subagent-registry-completion.ts):
   catch { return false } -> catch (err) { log.warn(...); return false }

2. restoreSubagentRunsOnce (subagent-registry.ts):
   catch { /* ignore */ } -> catch (err) { log.warn(...) }

Both paths now log the error message before continuing, providing
a diagnostic trail when hook emission or disk restore fails silently.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* test(agents): keep provider test mocks current

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 00:57:29 +01:00
Sebastien Tardif
f0061ddc54 fix(compaction): preserve partial summary on mid-chain chunk failure (#82952)
* fix(compaction): preserve partial summary on mid-chain chunk failure

When summarizing multiple chunks, if a chunk fails after at least one
chunk has already succeeded, return the partial summary instead of
propagating the error and losing all summarization progress.

Abort and timeout errors still propagate immediately. First-chunk
failures still rethrow so the existing fallback path runs.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix(compaction): use content array for assistant messages to match updated AgentMessage type

* fix(compaction): use as-unknown-as-AgentMessage cast for assistant test fixtures

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 00:57:25 +01:00
Sebastien Tardif
5d174a5bec fix(config): do not suppress recovery retry after failed backup restore (#85787)
maybeRecoverSuspiciousConfigRead unconditionally recorded
lastObservedSuspiciousSignature in health state even when
restoredFromBackup was false (copyFile failed). The guard at
resolveConfigReadRecoveryContext then prevented the same
signature from ever being retried, permanently accepting the
suspicious config on every subsequent launch.

Only record the dedup signature when the backup restore
actually succeeded.
2026-05-25 00:57:21 +01:00
Peter Steinberger
c422e7240f chore: release 2026.5.25 2026-05-25 00:46:47 +01:00
Vincent Koc
f68ed721b1 fix(installer): support alpine cli installs 2026-05-25 01:28:13 +02:00
Vincent Koc
2a73725b5d test(agents): keep runtime-plan provider mock current 2026-05-25 01:16:28 +02:00
Vincent Koc
4d4ce9e2f3 fix(scripts): launch env package scripts on Windows 2026-05-25 01:15:49 +02:00
Vincent Koc
3c8d101f5a fix(agents): cache fallback provider resolution 2026-05-25 00:55:30 +02:00
Vincent Koc
8ae997749d fix(test): make import timing scripts Windows-safe 2026-05-25 00:35:04 +02:00
Dallin Romney
8209426867 fix(telegram): transient Telegram pairing prompts (#85555)
* fix: avoid false telegram pairing prompts

* docs: add telegram pairing changelog

* refactor(telegram): share pairing-store gating and align isGroup check

Extract loadTelegramPairingStoreIfNeeded so the text-fragment flush path
and resolveTelegramGroupAllowFromContext share one implementation, and
align the isGroup derivation in the flush path with the
'group || supergroup' form used elsewhere in bot-handlers.runtime.ts.

Note on transient-vs-known errors: readChannelAllowFromStore already
translates missing-file (ENOENT) and JSON parse failures to an empty
allowlist internally, so the only errors that escape into the new
silent-drop path are unexpected I/O failures (EMFILE/EACCES/EIO/...) —
unpaired senders still get a pairing challenge as expected.

* fix(telegram): skip pairing-store read when commands.allowFrom already authorizes the sender

Native command auth resolves group/dm allow context (which may read the
pairing store) before checking commands.allowFrom. On DMs with
dmPolicy: "pairing", a transient pairing-store I/O failure was therefore
dropping commands from senders explicitly authorized by
commands.allowFrom.telegram.

Add a skipPairingStoreRead hint on resolveTelegramGroupAllowFromContext /
loadTelegramPairingStoreIfNeeded, precompute the command authorization
once at chat scope before the context call, and pass the hint when that
pre-check already authorizes the sender. The post-context command auth
check still owns the topic-scoped decision.

Regression covers a DM /status from a sender allowed by
commands.allowFrom.telegram with dmPolicy: "pairing" and a rejecting
readChannelAllowFromStore mock.

* fix(telegram): satisfy test-types on harness readChannelAllowFromStore

CI check-test-types failed because the harness now stores a loose
AnyAsyncMock for readChannelAllowFromStore but TelegramNativeCommandDeps
requires the precise typeof readChannelAllowFromStore signature. Cast at
the telegramDeps assignment so harness callers can keep passing any
vi.fn(...) (including ones that reject) without type pollution at the
call site.

* feat(telegram): reply with a retry hint when pairing-store read fails transiently

Wrap unexpected pairing-store I/O errors (EACCES, EMFILE, ...) in a
typed TelegramPairingStoreReadError and surface them through
handleInboundMessageLike with a friendly "please try again" reply that
matches the media-failure precedent at bot-handlers.runtime.ts:1893.
Beats silent drop: paired senders see why their message wasn't
processed, and unpaired senders who happen to send a DM during a
transient store outage retry naturally and get the correct pairing
prompt once the store recovers.

Verified live against @paxicoto_bot with chmod 000 on
~/.openclaw/credentials/telegram-default-allowFrom.json after touching
mtime to bypass the stat-pinned cache.
2026-05-24 15:12:30 -07:00
Vincent Koc
b681d5d5a6 fix(test): make max Vitest scripts Windows-safe 2026-05-24 23:54:29 +02:00
Gio Della-Libera
9e8cc7e077 fix(doctor): migrate Feishu account bot names (#86081) 2026-05-24 14:40:16 -07:00
Vincent Koc
500c95b1ba fix(scripts): prefilter conflict marker scans 2026-05-24 23:25:05 +02:00
clawsweeper[bot]
242e8767e7 docs: add ClawSweeper review policy to AGENTS (#86197)
* docs: add ClawSweeper review policy to AGENTS

Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>

* docs: add ClawSweeper review policy to AGENTS

* docs: move ClawSweeper review policy into AGENTS.md

Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>

---------

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-24 16:22:16 -05:00
Andy Ye
4742db6c31 fix(installer): avoid before with npm release-age configs (#85491)
Summary:
- The PR updates the Unix installers to avoid emitting npm `--before` when raw npm config contains `min-releas ...  records a changelog fix, and widens an internal model-catalog test helper type to accept sync auth checks.
- PR surface: Source +1, Tests +421, Docs +1, Other +150. Total +573 across 7 files.
- Reproducibility: yes. The linked report at https://github.com/openclaw/openclaw/issues/84743 gives an isolat ...  exclusivity, and current main still has the source path that can generate the conflicting `--before` flag.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(installer): avoid before with npm release-age configs
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8549…

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

Prepared head SHA: fb0762f468
Review: https://github.com/openclaw/openclaw/pull/85491#issuecomment-4522229812

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-24 21:18:58 +00:00
Vincent Koc
3e275a53dc fix(e2e): retry Windows kitchen sink probes 2026-05-24 23:10:33 +02:00
Vincent Koc
367d584ee3 fix(installer): install node with apk on alpine 2026-05-24 23:03:12 +02:00
Vincent Koc
acfed375ee fix(installer): detect musl linux shells 2026-05-24 22:47:05 +02:00
Dallin Romney
8ccb11cbfc perf(plugins,gateway): thread metadata snapshot + discovery through hot paths + plugin owner fixes (#84649)
* perf(plugins): thread metadata snapshot and discovery through hot paths

With the snapshot memo now actually hitting, route the snapshot's
manifestRegistry and discovery through the helper chains that already
had fast paths for them. Eliminates redundant per-call rebuilds at
two big amplifiers.

- Provider resolve paths (resolvePluginProviders /
  isPluginProvidersLoadInFlight / resolveOwningPluginIdsForProvider /
  resolveExternalAuthProfilesWithPlugins) self-service a snapshot once
  at the public entry, then thread it as a separate required arg
  through resolvePluginProviderLoadBase,
  resolveExplicitProviderOwnerPluginIds, and the setup/runtime load
  state helpers. Inner reads change from
  'params.pluginMetadataSnapshot?.x' to 'snapshot.x', no more
  enrichedParams clone. loadPluginManifestRegistryForInstalledIndex
  fires drop ~685 -> ~10 per cold start.

- Bundled-channel / auto-enable chain accepts an optional
  PluginDiscoveryResult. discoverOpenClawPlugins is fired once during
  snapshot building (resolveInstalledPluginIndexRegistry already
  produced it internally; now bubbled up through
  loadInstalledPluginIndexWithDiscovery, PluginRegistrySnapshotResult,
  and onto PluginMetadataSnapshot.discovery). load-context reads
  metadataSnapshot.discovery and passes it through
  applyPluginAutoEnable, so the bundled-channel cascade
  (collectConfiguredChannelIds, listBundledChannelIdsWith*,
  listPotentialConfiguredChannelPresenceSignals) short-circuits
  instead of each leaf re-firing discovery. Persisted-cache path is
  unchanged: no discovery on the snapshot, downstream chain handles
  its own fallback (pre-PR behavior on that path).

* test(plugins): isolate snapshot memo across tests that mock manifest registry

The snapshot memo is now process-scoped and effective (~98% hit rate).
Three test files were depending on cache misses (because the broken
cache returned them) — each test would set up its own
loadPluginManifestRegistry mock and expect a fresh derive. With the
cache fixed, an earlier test's mocked registry now leaks into later
tests in the same file.

- io.write-config.test.ts: afterEach now clears the snapshot memo so
  the 'demo' plugin mocked in the first test does not survive into
  'keeps shipped plugin install config records when index migration
  fails', which expects an empty registry to surface the 'plugin not
  found: demo' warning.

- gateway/model-pricing-cache.ts: resetGatewayModelPricingCacheForTest
  also clears the memo. Tests in model-pricing-cache.test.ts assert
  loadPluginManifestRegistryForInstalledIndex was called; the memo
  hit otherwise skips the call.

- providers.test.ts: vi.doMock loadPluginMetadataSnapshot to wrap the
  existing loadPluginManifestRegistryMock fixture. The plumbing
  commit added an auto-fetch fall-through in
  resolveOwningPluginIdsForProvider; without the mock, providers
  tests hit real disk reads and return empty registries (which is
  what surfaced as 9 unrelated-looking failures in the prior CI
  run).

* fix(plugins): preserve setup.cliBackends owner matching in provider scan

resolveOwningPluginIdsForProvider now also checks plugin.setup?.cliBackends.
The pre-PR no-registry fallback used resolvePluginContributionOwners which
includes both top-level cliBackends and setup.cliBackends; the PR's manifest
scan replacement was missing the setup case.

* fix(plugins): inherit active registry workspaceDir before loading metadata snapshot

isPluginProvidersLoadInFlight and resolvePluginProviders now resolve
env and workspaceDir once at the entry point (falling back to
getActivePluginRegistryWorkspaceDir) and pass them into both
loadPluginMetadataSnapshot and resolvePluginProviderLoadBase. Pre-fix
the snapshot used params.workspaceDir raw while the load base inherited
the active workspace, so workspace-scoped provider plugins could be
absent from the snapshot manifest registry even though owner resolution
expected them.

Regression test asserts the snapshot mock receives the active
workspaceDir when the caller omits it.

* perf(gateway): thread discovery into applyPluginAutoEnable call sites

Every gateway applyPluginAutoEnable call now passes the snapshot's
PluginDiscoveryResult so the bundled-channel cascade (collectConfiguredChannelIds
→ listBundledChannelIdsWith* → listPotentialConfiguredChannelPresenceSignals)
short-circuits instead of each leaf re-firing discovery.

Startup-time sites pull discovery from the snapshot/lookup-table they already
hold:
- server-plugin-bootstrap.ts (pluginLookUpTable)
- server-startup-plugins.ts (pluginMetadataSnapshot)
- server-startup-config.ts (pluginMetadataSnapshot)
- server-plugins.ts (pluginLookUpTable, both call sites)

Per-RPC sites (server.impl getRuntimeConfig callback, server-methods/channels
status + start handlers, server-methods/send) source discovery via
getCurrentPluginMetadataSnapshot using the runtime config to validate
compatibility. Falls through to the original slow path when the snapshot is
absent or incompatible.
2026-05-24 13:44:03 -07:00
Vincent Koc
8bf4f7d4a8 fix(ui): split control ui runtime chunks 2026-05-24 22:20:33 +02:00
tanshanshan
fe34141a3d refactor(config): extract GoogleChat schema into zod-schema.providers-googlechat.ts (#82100)
Merged via squash.

Prepared head SHA: 7555272656
Co-authored-by: tanshanshan <22539261+tanshanshan@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-24 23:03:35 +03:00
Vincent Koc
6cc8244333 fix(update): suppress internal handoff version warnings 2026-05-24 21:42:42 +02:00
Vincent Koc
0acc3e3216 test(e2e): select installable bundled plugins 2026-05-24 21:36:08 +02:00
Vincent Koc
43252c8099 fix(scripts): harden Windows native opus install 2026-05-24 21:28:09 +02:00
Brian Potter
efd88dc00d fix(agents): match runtime policy entries when session provider is empty (#85970)
Merged via squash.

Prepared head SHA: 1f081b3a8c
Co-authored-by: potterdigital <197414865+potterdigital@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-24 22:01:21 +03:00
Vincent Koc
0a98559440 fix(scripts): harden Windows generated formatting 2026-05-24 20:50:07 +02:00
David
07f500aa56 fix(mcp): bound tools/list during catalog discovery (#85063)
Summary:
- The branch adds a 1500 ms internal timeout to bundled MCP `tools/list` catalog discovery, adds slow and hung stdio MCP regression tests, and records the fix in `CHANGELOG.md`.
- PR surface: Source +2, Tests +216, Docs +1. Total +219 across 3 files.
- Reproducibility: yes. The current-main source path is high confidence: bundled MCP connects successfully, then calls `client.listTools` without request options, and the upstream SDK defaults that request to 60000 ms.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(mcp): use internal tools list timeout
- PR branch already contained follow-up commit before automerge: fix(mcp): bound tools/list during catalog discovery
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8506…

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

Prepared head SHA: bbbfb9f059
Review: https://github.com/openclaw/openclaw/pull/85063#issuecomment-4511554739

Co-authored-by: nxmxbbd <32288+nxmxbbd@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-24 18:37:28 +00:00
Vincent Koc
dfa1a51225 fix(test): focus plugin binding Docker smoke 2026-05-24 20:28:43 +02:00
Vincent Koc
a4fab83b55 test(e2e): fail release memory indexing errors 2026-05-24 20:12:43 +02:00
Vincent Koc
af07769871 test(daemon): fail launchd integration bootstrap errors 2026-05-24 19:57:59 +02:00
Omar Shahine
5c7980fa11 feat(imessage): support thumb approval reactions (#85952)
* feat(imessage): support thumb approval reactions

Mirrors openclaw#85477 (WhatsApp) for the iMessage channel. iMessage can now
deliver exec/plugin approval prompts via the existing imsg/BlueBubbles
transport and resolve approvals from 👍 (allow-once) / 👎 (deny) tapbacks.
Allow-always remains on the manual /approve <id> allow-always fallback.

What changed:
- New approval surfaces under extensions/imessage/src/:
  approval-auth.ts, approval-resolver.ts, approval-reactions.ts,
  approval-handler.runtime.ts, approval-native.ts (+ tests for each).
- channel.ts wires base.approvalCapability to the new iMessage capability.
- send.ts appends the 👍/👎 hint to outbound /approve prompts and registers
  the reaction binding (keyed by accountId + chat_guid/chat_identifier/
  chat_id/handle + messageId) after a successful send.
- monitor/monitor-provider.ts resolves approval reactions ahead of the
  normal inbound decision pipeline so resolution bypasses
  reactionNotifications gating and runs its own actor authorization.
- runtime.ts now exports getIMessageRuntime / getOptionalIMessageRuntime so
  approval-reactions can open a persistent keyed store for binding state
  across gateway restarts.

What did NOT change:
- Core approval surfaces in src/gateway/server-methods/* and src/infra/*
  remain channel-agnostic; the channels.imessage.allowFrom field already
  exists and is reused as the approver list for reactions.
- Other channels and the manual /approve sender-authorized path are
  untouched.

* fix(imessage): address codex review findings on thumb approvals

Addresses 15 findings from the multi-angle codex review:

Critical (correctness / blocking):
- Register CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY in the iMessage
  monitor so the gateway can actually deliver native approval prompts via
  approval-handler.runtime.ts (it was dead code without the context lease).
- DM tapback approvals never resolved because send keyed by handle while
  inbound preferred chat_guid. Register and look up under EVERY available
  conversation key (chat_guid / chat_identifier / chat_id / handle); inbound
  probes them all and accepts the first hit.
- Reaction binding now requires the bridge's GUID string (rejecting numeric
  ROWIDs) so the binding key matches inbound reacted_to_guid.
- Outbound regex now requires both a canonical `ID: <approvalId>` header AND
  a matching `/approve <id> <decision>` line, so non-approval messages that
  legitimately mention /approve syntax no longer get a phantom reaction
  binding (and can no longer resolve a colliding live approval).
- Drop is_from_me reaction events so cross-device echoes of the operator's
  own tap cannot self-approve when their handle is in allowFrom.

High (operability / cleanup):
- Non-ApprovalNotFound errors now log at warn via the runtime child logger
  (no longer hidden behind OPENCLAW_LOG_LEVEL=debug).
- In-memory binding is cleared on successful resolve so a toggle 👍👎 (or
  chat.db replay) does not refire and emit a misleading 'expired approval'
  log line. Removed tapbacks are also owned by the shortcut and not surfaced
  as noisy reaction system events.
- Move resolveIMessageReactionContext (and its helpers) to a slim
  monitor/reaction-context.ts so approval-reactions.ts no longer transitively
  pulls monitor/inbound-processing.ts (14+ heavy runtime modules) into the
  hot channel.ts entrypoint per extensions/CLAUDE.md.

Medium (consistency / future-proofing):
- Native runtime exec pending payload now passes agentId, ask, and
  sessionKey through buildExecApprovalPendingReplyPayload so the two
  delivery routes produce identical operator-visible prompts.
- Both delivery paths now use addIMessageApprovalReactionHintToText (single
  insertion point after ID:) so the hint cannot be double-emitted by the
  native runtime path bypassing the idempotency guard.
- Extract replaceApprovalIdPlaceholder into a shared approval-text.ts that
  escapes `$` in the replacement string so an approvalId containing
  `$&`/`$1`-`$9`/`$$` cannot interpolate into the outbound text.
- In-memory Map now stores TTL alongside each entry and prunes expired
  bindings on each register so the gateway no longer accumulates an
  unbounded reaction-target Map.
- bindPending refuses to bind when accountId is missing or the approval is
  already expired, with explicit error logs instead of silent no-ops.
- Reject chat_id=0 as a synthetic key value (chat.db ROWIDs start at 1).
- Drop dead getIMessageRuntime export — only the optional accessor is used.

Documentation:
- docs/channels/imessage.md gains an 'Approval reactions (👍 / 👎)' accordion
  documenting the reaction emoji map, allowFrom approver requirement, the
  /approve <id> allow-always manual fallback, and the deliberate change to
  /approve command authorization for users with non-empty allowFrom.
- CHANGELOG.md entry added under 2026.5.24.

Tests: 411 iMessage tests pass (was 406). Added explicit coverage for the
DM key-mismatch fix, the regex-tightening fix, the is_from_me guard, the
clear-on-success behavior, and the approval-id `$` escape.

* test(imessage): match WhatsApp approval-native test coverage

Backfills the nine cases from extensions/whatsapp/src/approval-native.test.ts
that weren't mirrored in iMessage:

- target-mode exec + plugin prompt rendering with the canonical hint
- target-mode availability when no iMessage target matches
- agentFilter / sessionFilter applied to native handling
- account-scoped target enabled/disabled per account
- shouldSuppressForwardingFallback session-origin exact-match cases
- shouldSuppressForwardingFallback off when native cannot bind (locks down
  the targets-only forwarding path the Lobster live deploy exercised)
- both-mode explicit + unscoped target suppression
- group-origin tapback approvals require explicit approvers

Tests: extensions/imessage/src/approval-native.test.ts 21 passed (was 11).
Total iMessage approval-specific cases now 49 (was 40).

* fix(imessage): preserve service-prefixed direct handles as approvers

ClawSweeper P1 review finding on #85952. normalizeIMessageApproverId was
calling looksLikeIMessageExplicitTargetId() to reject conversation-target
prefixes, but that helper also matches the imessage:/sms:/auto: service
prefixes — which are valid direct-handle forms. Any allowFrom entry like
'imessage:+15551230000' dropped to undefined, leaving approvers empty,
which:
  - silently denied reaction resolution ('reactions require explicit
    approvers'), and
  - let text /approve fall back to implicit same-chat authorization.

Fix: normalize first via normalizeIMessageHandle (strips the service
prefix), then reject only chat_id:/chat_guid:/chat_identifier:
conversation-target shapes that remain after normalization.

Tests:
  - approval-auth.test.ts: assert the resolved approver list contains the
    normalized handle, plus the corollary that a non-matching sender is
    explicitly rejected (no longer masked by the implicit-same-chat
    fallback). Add a separate case covering chat_id/chat_guid/
    chat_identifier rejection (with and without a service prefix).
  - approval-reactions.test.ts: reaction resolution end-to-end with a
    service-prefixed allowFrom entry — proves resolveIMessageApproval is
    called rather than silently denied.

Focused suite: 48 passed (was 47).

* test(imessage): satisfy strict buildPendingPayload signature in render tests

CI check:test-types caught that the render.exec/render.plugin
buildPendingPayload calls were passing accountId (not in the type
signature). The signature is { cfg, request, target, nowMs }. Replace
accountId with target on the four render-test sites so the strict
test-types pass matches the SDK contract:

  - it('renders thumbs-only reaction hints in exec approval prompts')
  - it('renders thumbs-only reaction hints in plugin approval prompts ...')
  - it('renders target-mode exec prompts with concrete thumbs-only ...')
  - it('renders target-mode plugin prompts with concrete thumbs-only ...')

Verified locally with pnpm check:test-types (tsgo:core:test +
tsgo:extensions:test). 49 approval-specific tests still pass.

* fix(imessage): probe every tapback GUID form for approval lookup

ClawSweeper P1 review finding on #85952. readApprovalReactionEvent was
only using reaction.targetGuid (the first/normalized form), but
resolveIMessageReactionContext produces reaction.targetGuids = [normalized,
raw] for both `abc-123` and `p:0/abc-123` forms. If the imsg bridge
returned 'p:0/<guid>' from send() and send.ts registered the binding under
that prefixed key, the inbound resolver probing only the unprefixed form
would miss and the tapback would silently fall through.

Fix:
- Surface every GUID candidate in IMessageApprovalReactionEvent
  (messageIdCandidates).
- maybeResolveIMessageApprovalReaction now probes each candidate in
  precedence order; first hit wins.
- On success / ApprovalNotFoundError, clear the binding under all
  candidate keys so toggle/replay does not refire.

Tests: extensions/imessage/src/approval-reactions.test.ts gains a
'resolves a reaction when the binding was registered under a p:0/…
prefixed GUID and the tapback surfaces both forms' regression case;
22/22 reaction tests pass. Full iMessage suite: 424/424.

* fix(imessage): native approval binding requires GUID, not numeric id

ClawSweeper third P1 review finding on #85952. approval-handler.runtime.ts
deliverPending was using result.messageId as the approval-reaction binding
key, but that field can be a numeric ROWID coerced to a string ('12345')
when the imsg bridge returns only message_id. Inbound tapbacks carry
reacted_to_guid which is always a GUID, so a numeric-id binding can never
match.

Fix mirrors the send.ts forwarding-path treatment:
- IMessageSendResult now exposes a separate guid?: string field, populated
  from the same resolveOutboundMessageGuid helper send.ts already uses for
  the forwarding-path binding. The generic messageId field is unchanged so
  reply-cache, echo-cache, and receipt-building paths still see the
  broadest id form.
- deliverPending now binds against result.guid; when it's undefined (numeric
  ROWID or 'ok'/'unknown' placeholders), the function returns null instead
  of binding against an id the inbound tapback can't possibly match.

Tests: approval-handler.runtime.test.ts gets a deliverPending GUID-only
binding describe block with three regression cases (numeric ROWID refused,
GUID accepted, ok/unknown placeholders refused). vi.mock isolates
sendMessageIMessage so the cases run synchronously without spawning imsg.
11 tests pass across handler.runtime + send specs.

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-24 10:51:21 -07:00
Vincent Koc
ad71a998ff fix(crabbox): default macos aws runs on demand 2026-05-24 19:31:19 +02:00
Vincent Koc
e4332f7cff fix(scripts): preserve test passthrough args 2026-05-24 19:13:03 +02:00
Vincent Koc
8edc671eb4 fix(e2e): harden Windows plugin assertions 2026-05-24 19:10:10 +02:00
Vincent Koc
5f0315467b fix(test): mount upgrade survivor helper 2026-05-24 18:59:34 +02:00
Ayaan Zaidi
c4525104e9 style(android): sharpen voice mode surfaces 2026-05-24 22:06:36 +05:30
Ayaan Zaidi
955909c988 style(android): refine list surface spacing 2026-05-24 22:06:36 +05:30
Ayaan Zaidi
63a2f69601 fix(android): prevent stale chat during session switches 2026-05-24 22:06:36 +05:30
Ayaan Zaidi
d86ed21f3d fix(android): hide internal chat content blocks 2026-05-24 22:06:36 +05:30
Ayaan Zaidi
cc5eb972e6 feat(android): add pair new gateway action 2026-05-24 22:06:36 +05:30
Ayaan Zaidi
94bc18ad75 fix(android): keep permission setup action visible 2026-05-24 22:06:36 +05:30
Andy Ye
102555c6e0 Advance iMessage catchup cursor after live handling (#85475)
Fixes #85363.

Thanks @TurboTheTurtle.
2026-05-24 09:34:16 -07:00
Vincent Koc
79ee70c8ad fix(scripts): ignore forwarded arg separator 2026-05-24 18:13:14 +02:00
Vincent Koc
5a8ce6a885 fix(test): fail empty gateway startup samples 2026-05-24 17:58:12 +02:00
Vincent Koc
87a2eba427 fix(e2e): harden Windows kitchen sink assertions 2026-05-24 17:47:19 +02:00
Vincent Koc
c643370fd8 fix(e2e): harden Telegram credential paths on Windows 2026-05-24 17:17:28 +02:00
Ayaan Zaidi
be9bb775a5 fix(android): complete qr setup operator handoff 2026-05-24 20:38:57 +05:30
Ayaan Zaidi
0b55a6363e fix(android): align setup pairing scopes 2026-05-24 20:38:57 +05:30
Vincent Koc
dbc08f64c1 fix(test): copy cleanup smoke prepare hook 2026-05-24 16:57:26 +02:00
clawsweeper[bot]
675158c896 fix(secrets): allow hash in exec SecretRef ids (#86072)
Summary:
- The branch widens exec SecretRef id validation/schema/docs/test vectors to allow `#` selector syntax, adds a changelog entry, and includes a small `npm pack` filename helper cleanup.
- Reproducibility: yes. Source inspection on current main shows the shared exec SecretRef validator omits `#`, matching the linked gateway startup failure before resolver execution.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs(secrets): document hash exec SecretRef ids
- PR branch already contained follow-up commit before automerge: docs(secrets): sync exec SecretRef hash pattern
- PR branch already contained follow-up commit before automerge: fix(secrets): allow hash in exec SecretRef ids
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8073…

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

Prepared head SHA: 1cf53d95f4
Review: https://github.com/openclaw/openclaw/pull/86072#issuecomment-4528994482

Co-authored-by: Andy Ye <andy@Andys-MacBook-Pro-2.local>
Co-authored-by: Andy Ye <andylye@outlook.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-24 14:56:17 +00:00
Vincent Koc
694d45e535 fix(media): use static image compression metadata 2026-05-24 16:47:59 +02:00
Peter Steinberger
01c5ab8d13 fix(release): verify large plugin npm packs 2026-05-24 15:33:17 +01:00
Vincent Koc
7e51f83aec fix(test): require kitchen sink diagnostic canaries 2026-05-24 16:27:46 +02:00
Vincent Koc
483d7be6c4 fix(scripts): harden Windows upgrade survivor recipe 2026-05-24 16:05:24 +02:00
Vincent Koc
102b2c18e9 fix(installer): count verify progress stage 2026-05-24 15:50:28 +02:00
Tak Hoffman
5bffd17e01 fix: Refine PR template for review state (#86054) 2026-05-24 08:49:02 -05:00
Vincent Koc
125d82cab2 fix(test): repair split agent shard runs 2026-05-24 15:37:59 +02:00
Vincent Koc
ce48e4c197 fix(codex): harden Windows protocol formatting 2026-05-24 15:37:13 +02:00
clawsweeper[bot]
dd01a2e789 fix(openrouter): use endpoint context limits (#86041)
Summary:
- The branch updates OpenRouter dynamic model capability parsing to prefer `top_provider.context_length`, bump ... sk cache version, adds regression coverage and a changelog entry, and adds script helper declaration files.
- Reproducibility: yes. from source and live catalog evidence rather than an authenticated inference turn. Cur ... catalog currently reports a smaller endpoint-specific `top_provider.context_length` for the reported model.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(openrouter): use endpoint context limits
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8594…

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

Prepared head SHA: 76fcc362d2
Review: https://github.com/openclaw/openclaw/pull/86041#issuecomment-4528646655

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-24 13:32:44 +00:00
Peter Steinberger
8473e8933a test(qa): remove brittle capability flip setup turn 2026-05-24 14:30:59 +01:00
Ayaan Zaidi
5cfb12fa5d fix(telegram): migrate account topic cache sidecars 2026-05-24 18:58:02 +05:30
Ayaan Zaidi
eb9b882dae fix(telegram): migrate legacy cache sidecars 2026-05-24 18:58:02 +05:30
Vincent Koc
5be62e779b fix(scripts): harden Windows ZAI fallback repro 2026-05-24 15:11:37 +02:00
Ayaan Zaidi
400d90a4da style(android): sharpen v2 screen rhythm 2026-05-24 18:37:31 +05:30
Peter Steinberger
c91c3c6e5a test(qa): extend capability flip setup budget 2026-05-24 14:02:22 +01:00
Ayaan Zaidi
24ddd18ae1 fix(android): simplify gateway status copy 2026-05-24 18:24:51 +05:30
Ayaan Zaidi
cec52bd279 fix(android): route offline voice to gateway setup 2026-05-24 18:22:28 +05:30
Vincent Koc
581c8a6375 fix(scripts): harden Windows control UI i18n commands 2026-05-24 14:47:07 +02:00
Ayaan Zaidi
5c15859759 fix(android): stop operator chat subscription 2026-05-24 18:16:01 +05:30
Vincent Koc
a72b11d29a fix(test): fail missing kitchen sink rss samples 2026-05-24 14:44:14 +02:00
Peter Steinberger
c7d4e9e1c2 test(qa): widen capability flip restart budget 2026-05-24 13:38:54 +01:00
Ayaan Zaidi
60e6ccdb8c fix(android): smooth gateway pairing recovery 2026-05-24 18:05:40 +05:30
Vincent Koc
6d9b3887ea fix(test): suppress rolldown timing noise 2026-05-24 14:26:19 +02:00
Ayaan Zaidi
01b284cac0 style(android): fix talk mode ktlint formatting 2026-05-24 17:51:53 +05:30
Ayaan Zaidi
996d07ee46 fix(telegram): store topic cache in plugin state 2026-05-24 17:38:27 +05:30
Ayaan Zaidi
2ed52969c5 fix(telegram): store bot info cache in plugin state 2026-05-24 17:38:27 +05:30
Vincent Koc
0f82c810fc fix(test): sync sparse AWS Crabbox runs from full checkout 2026-05-24 14:00:37 +02:00
Vincent Koc
71547678c7 fix(release): harden Windows cross-os command shims 2026-05-24 13:52:18 +02:00
Vincent Koc
98e09e8817 fix(test): harden Docker resource ceilings 2026-05-24 13:38:50 +02:00
Peter Steinberger
e8643f0c15 test(telegram): keep startup limiter coverage focused 2026-05-24 12:36:45 +01:00
Peter Steinberger
04d86e0f47 test(telegram): isolate startup probe limiter timing 2026-05-24 12:23:32 +01:00
Peter Steinberger
578e73f667 test(release): harden plugin prerelease checks 2026-05-24 12:02:29 +01:00
Josh Lehman
62b51a6295 fix(telegram): serialize topic dispatch replies (#85709)
* fix(telegram): serialize topic dispatch replies (clawdbot-b19)

* fix(telegram): normalize dispatch topic context

* fix(telegram): satisfy dispatch race CI checks

* fix(telegram): normalize raw code language tags

* refactor(reply): centralize turn admission

* fix(telegram): persist recovered topic routes

* fix(reply): preserve queue policy admission

* fix(reply): retain active abort owner

* fix(reply): split active abort ownership

* fix(reply): defer busy followup drains

* fix(reply): wire hook abort ownership

* fix(reply): preserve deferred queue summaries

* fix(reply): type queued summary retry

* fix(reply): abort embedded and core runs

* test(reply): keep final abort operation active

* test(reply): stabilize abort normalization test

* fix(reply): keep non-visible admission skips silent

* test(reply): avoid dispatch shard mock bleed

* fix(reply): merge deferred queue summaries

* fix(reply): abort active-lane resolver runs

* fix(reply): compose borrowed lane abort signals

* fix(reply): keep interrupt turns caller-owned

* fix(telegram): keep recovered topic history scoped

* fix(reply): retry deferred summary queues

* fix(reply): document deferred summary restore

* fix(telegram): rebuild recovered topic prompt body

* fix(reply): run admitted session ids

* fix(telegram): recover topic chat actions

* fix(reply): honor pre-dispatch aborts for handled replies

* fix(reply): guard local handled final aborts

* fix(reply): refresh admitted session files

* fix(telegram): trust final current-message marker

* fix(telegram): migrate recovered room history

* fix(telegram): scope recovered topics to current chat

* fix(reply): wait for visible reply lane ownership

* fix(telegram): pass recovered topic body to agent

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 11:49:48 +01:00
Peter Steinberger
3679151c2c test(release): stabilize plugin prerelease checks 2026-05-24 11:40:48 +01:00
Vincent Koc
295339d616 fix(test): fail live gateway false greens 2026-05-24 12:38:23 +02:00
Vincent Koc
3838e450dd fix(test): build startup artifacts for smoke scripts 2026-05-24 12:34:13 +02:00
Peter Steinberger
0a8af67c11 test(telegram): wait for startup probe slots 2026-05-24 11:21:15 +01:00
Peter Steinberger
783290f7ed test(codex): match sandbox exec-server yolo policy 2026-05-24 11:01:15 +01:00
Vincent Koc
9ff4d36c98 fix(test): fail missing explicit test targets 2026-05-24 11:46:51 +02:00
Peter Steinberger
558c1bc39a test(codex): avoid full sandbox exec-server turn run 2026-05-24 10:36:44 +01:00
Vincent Koc
bca1ac03fe fix(ci): keep Crabbox pnpm hydration shims writable 2026-05-24 11:31:36 +02:00
Vincent Koc
75ac11aca2 fix(release): harden Windows release-check npm probes 2026-05-24 11:14:41 +02:00
Peter Steinberger
cf46f2e3a0 fix(docker): parse peer-suffixed lockfile packages 2026-05-24 09:51:39 +01:00
Peter Steinberger
f799da0947 fix(docker): seed lockfile packages before prune 2026-05-24 09:33:34 +01:00
Peter Steinberger
2cd93f1c0d fix(docker): seed lockfile snapshot tarballs before prune 2026-05-24 09:18:04 +01:00
Peter Steinberger
a4ef3a2c9a test(codex): type thread start mock params 2026-05-24 08:53:29 +01:00
Peter Steinberger
11bf6424ca test(codex): avoid full sandbox run in thread-start test 2026-05-24 08:40:58 +01:00
Vincent Koc
abdd8a40cc fix(plugins): harden Windows npm package staging 2026-05-24 09:32:17 +02:00
Peter Steinberger
c14a0c6d63 test(codex): complete sandbox turn inline 2026-05-24 08:19:01 +01:00
Vincent Koc
a56f452972 fix(release): harden Windows npm shim verification 2026-05-24 09:02:44 +02:00
Peter Steinberger
f8789599f0 test(release): type metadata snapshot mock params 2026-05-24 07:56:34 +01:00
Peter Steinberger
e9ca3115f0 test(release): finish plugin metadata prerelease sync 2026-05-24 07:42:56 +01:00
Rubén Cuevas
501f2cbfe4 fix(update): avoid broad tag fetches for dev updates (#84737)
Summary:
- The PR changes dev-channel git updates to fetch branches with `--no-tags`, adds targeted fetching for explicit dev tag refs, updates update-runner tests, and adds a changelog entry.
- Reproducibility: yes. Current main source shows dev updates still run a broad tag fetch, and the PR body sup ... al local bare-remote moved-tag reproducer showing that command fails before the branch update can continue.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(update): avoid broad tag fetches for dev updates

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

Prepared head SHA: 733680b1bc
Review: https://github.com/openclaw/openclaw/pull/84737#issuecomment-4503692161

Co-authored-by: Ruben Cuevas <hi@rubencu.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-24 06:37:21 +00:00
homer-byte
4d150209c3 Fix iMessage slash command acknowledgements (#82642)
Merged via squash.

Prepared head SHA: ecc8791393
Co-authored-by: homer-byte <262247270+homer-byte@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-05-23 23:33:33 -07:00
Peter Steinberger
02f53e6453 test(release): align prerelease contracts 2026-05-24 07:23:32 +01:00
Peter Steinberger
56eb23dda4 test(release): align plugin prerelease checks 2026-05-24 06:47:42 +01:00
Peter Steinberger
0ba6b23534 fix(docker): copy prepare hook before install 2026-05-24 06:21:29 +01:00
Peter Steinberger
d6c9387c0f fix: share signed thinking replay policy 2026-05-24 06:10:47 +01:00
Peter Steinberger
906476af0c fix: preserve signed thinking tool ids 2026-05-24 06:10:47 +01:00
NianJiuZst
41329c0e14 fix(memory): strip invalid thinking signatures for signed-thinking providers 2026-05-24 06:10:47 +01:00
Vincent Koc
d21abb88e4 fix(scripts): harden Windows install checks 2026-05-24 07:03:49 +02:00
Peter Steinberger
b972ac1940 fix(release): keep private QA markers out of bundled alias code 2026-05-24 06:03:21 +01:00
Peter Steinberger
fdfcb0795a fix(discord): harden realtime voice wake joins 2026-05-24 05:54:10 +01:00
Peter Steinberger
3839b48615 test(parallels): harden release VM smoke isolation 2026-05-24 05:50:03 +01:00
Peter Steinberger
0f83c93740 fix: keep blank agent allowlists fail closed (#85849) 2026-05-24 05:40:16 +01:00
Peter Steinberger
88aa713c03 fix: harden session allowlist glob matching (#85849) (thanks @SebTardif) 2026-05-24 05:40:16 +01:00
Sebastien Tardif
1463d3d72c fix(security): replace regex wildcard matching with linear-time glob in session-visibility
The agentToAgent allow-pattern matcher converted user wildcards like
`*a*b*c*` into `^.*a.*b.*c.*$` via RegExp.  Multiple overlapping
`.*` groups cause O(n^k) polynomial backtracking against non-matching
input, where k is the number of wildcards.

Replace the regex path with a segment-based glob matcher that splits on
`*` and checks prefix/suffix/interior segments in order.  The new
matcher runs in O(n*k) worst case and eliminates the regex engine
entirely from this path.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-24 05:40:16 +01:00
Peter Steinberger
ae9308bfe0 docs(changelog): note restart recovery notice 2026-05-24 05:38:59 +01:00
Peter Steinberger
32631eb9d4 fix(telegram): normalize legacy action targets 2026-05-24 05:38:59 +01:00
Paul Frederiksen
cf61b876ec fix: notify chat when main session recovery fails 2026-05-24 05:38:59 +01:00
NianJiu
d4e42d61c9 fix(minimax): normalize OAuth token expiry to absolute millisecond timestamp (#83480)
* fix(minimax): normalize OAuth token expiry to absolute millisecond timestamp

MiniMax returns expired_in from the token endpoint as a relative duration
in seconds (standard OAuth expires_in semantics), but the auth profile
store's hasUsableOAuthCredential() expects an absolute millisecond
timestamp.  Without conversion the token appears perpetually expired,
triggering a slow OAuth refresh network call to api.minimaxi.com on
every request — the root cause of the 30-50s auth-stage delay.

Fixes #83449.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(minimax): cover oauth expiry normalization

* fix: polish minimax oauth expiry normalization (#83480) (thanks @NianJiuZst)

* fix: update minimax raw fetch allowlist (#83480)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 05:21:22 +01:00
David
55f994a8d0 fix(memory-wiki): show vault totals in palace summary (#85824)
* fix(memory-wiki): show vault totals in palace summary

* fix(memory-wiki): avoid zero-page legacy question label

---------

Co-authored-by: nxmxbbd <32288+nxmxbbd@users.noreply.github.com>
2026-05-24 05:11:12 +01:00
clawsweeper[bot]
8deb1ef7db Isolate boot-md startup sessions (#85919)
Summary:
- The branch updates gateway boot startup handling to use an `agent:<id>:boot` session, suppress prompt persis ...  that boot mapping after the run, and adds focused gateway boot regression coverage plus a changelog entry.
- Reproducibility: yes. there is a high-confidence source reproduction path: current main passes the generated ... idence of repeated persisted boot prompts. I did not execute the gateway scenario in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Fix boot-md test lint
- PR branch already contained follow-up commit before automerge: Isolate boot-md startup sessions

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

Prepared head SHA: 5d5338c2d9
Review: https://github.com/openclaw/openclaw/pull/85919#issuecomment-4527318708

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-24 04:05:45 +00:00
AirLin
d0751111a4 Guard OpenAI image compression for PNG outputs (#85776)
* Guard OpenAI image compression for PNG outputs

* Fix OpenAI image compression type narrowing

* docs(changelog): note OpenAI PNG compression fix

* Revert "docs(changelog): note OpenAI PNG compression fix"

This reverts commit b11e4bff01.

---------

Co-authored-by: airlin <airlin@airlins-Mac-mini.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 05:01:55 +01:00
rendrag-git
1d1a7c26d8 fix(agents): clamp proxy completions caps after payload shaping (#85889)
Clamp proxy-like OpenAI Chat Completions output caps against the estimated final outbound request payload after compatibility transforms. This prevents strict local/API-compatible servers from rejecting requests whose prompt already consumes part of the effective context window, while avoiding over-clamping dropped replay turns.

Co-authored-by: rendrag-git <253747599+rendrag-git@users.noreply.github.com>
2026-05-24 04:57:23 +01:00
Peter Steinberger
17dcdead00 fix: gate discord realtime voice by wake name (#85915) 2026-05-24 04:47:16 +01:00
Gio Della-Libera
c074d09f1e fix(update): ignore inherited launchd xpc for respawn (#85789) 2026-05-23 20:42:05 -07:00
Gio Della-Libera
6b337ff3ea fix: allow symlinked workspace write parents (#85818) 2026-05-23 20:42:01 -07:00
Gio Della-Libera
af765100ff fix(agents): preserve latest thinking replay signatures (#85579) 2026-05-23 20:41:57 -07:00
Gio Della-Libera
e6d5b7db96 fix(plugins): return plugin gateway method results (#85785) 2026-05-23 20:41:52 -07:00
Gio Della-Libera
068b9acb51 fix(gateway): hide duplicate ACP chat replies (#85775) 2026-05-23 20:41:45 -07:00
Gio Della-Libera
566d8cdf39 fix(update): ignore restart script spawn failures (#85761) 2026-05-23 20:41:40 -07:00
Gio Della-Libera
617335250e fix(telegram): honor explicit default account warning (#85752) 2026-05-23 20:41:35 -07:00
Gio Della-Libera
82af6119fa fix: honor OPENCLAW_HOME defaults (#85802)
* fix: honor OPENCLAW_HOME defaults

* fix(install): preserve openclaw home upgrade defaults

* fix(install): satisfy shellcheck tilde patterns
2026-05-23 20:39:59 -07:00
Gio Della-Libera
2e8dee7f28 fix(browser): avoid cold mac chrome version timeouts (#85460) 2026-05-23 20:39:47 -07:00
Gio Della-Libera
76221b53c2 fix(doctor): prune stale bundled plugin paths (#85038) 2026-05-23 20:39:42 -07:00
Peter Steinberger
c38a9a883a fix: label meeting note transcript speakers
Include speaker-labeled transcript lines in Meeting Notes summaries and structured summary artifacts.
2026-05-24 04:29:01 +01:00
Peter Steinberger
8f783cdcad fix(release): keep memory plugin npm package small 2026-05-24 04:27:42 +01:00
Peter Steinberger
bae0e3fae5 fix(release): speed plugin bundled dependency installs 2026-05-24 04:27:42 +01:00
Peter Steinberger
4daf1aab55 fix(release): keep plugin bundled install lock-compatible 2026-05-24 04:27:42 +01:00
Peter Steinberger
7a85f1ee94 test(matrix): stabilize thread binding sweep persistence 2026-05-24 04:27:42 +01:00
Kaspre
6008375655 fix(gateway): honor restart drain budget for embedded runs
Honor configured restart drain budgets for embedded runs and avoid a second active-work drain after forced deferral timeout restarts.

Includes maintainer changelog entry.
2026-05-24 04:22:27 +01:00
Peter Steinberger
6e994ad343 fix: preserve provider defaults during config saves (#85903) 2026-05-24 04:22:15 +01:00
Peter Steinberger
7439d78297 fix(release): accept sha-verified publish evidence 2026-05-24 04:17:40 +01:00
Enjou
3b3b2cca9c fix(ui): handle empty strings with minLength constraint in config save (#85850)
* fix(ui): handle empty strings with minLength constraint in config save

Fixes #85831

When saving config in Control UI, required string fields with minLength
constraint (e.g., z.string().min(1)) were sent as empty strings instead
of being unset. This prevented schema defaults from applying.

Solution: coerce empty strings with minLength > 0 to undefined, allowing
schema defaults to take effect during validation.

Added 5 unit tests covering edge cases.

* fix(types): add minLength and maxLength to JsonSchema type
2026-05-24 04:15:21 +01:00
Peter Steinberger
cbdc24895e docs: add changelog for aborted subagent fix 2026-05-24 04:08:15 +01:00
Peter Steinberger
fc4bd448b6 fix: prefer aborted stop reason over blocked lifecycle 2026-05-24 04:08:15 +01:00
Peter Steinberger
8df01a8683 fix: treat aborted subagent lifecycle events as killed 2026-05-24 04:08:15 +01:00
JARVIS-Glasses
4d502b3d1e fix(agents): treat aborted subagent runs as terminal 2026-05-24 04:08:15 +01:00
Peter Steinberger
ba94ca5eff docs: update changelog for whatsapp reply fence 2026-05-24 04:02:59 +01:00
Cavit Erginsoy
bd91107fc6 Fix foreground reply fence visibility 2026-05-24 04:02:59 +01:00
Peter Steinberger
841cb121fb fix(twitch): cancel auth retry disconnects 2026-05-24 03:55:49 +01:00
Peter Steinberger
08159d87d2 fix: address PR review comments 2026-05-24 03:55:49 +01:00
Peter Steinberger
8cc93293a1 fix(tools): tolerate out-of-scope autoreview findings 2026-05-24 03:55:49 +01:00
Peter Steinberger
6a482584ee fix(ci): address review sweep regressions 2026-05-24 03:55:49 +01:00
Peter Steinberger
679b6776d5 fix(node): avoid stale TLS pins when retargeting 2026-05-24 03:55:49 +01:00
Peter Steinberger
97c63e63b1 fix(cli): keep secrets configure JSON singular 2026-05-24 03:55:49 +01:00
Peter Steinberger
9177860373 fix(twitch): wait through auth retry disconnects 2026-05-24 03:55:49 +01:00
Peter Steinberger
6ce9e0dd9b fix(cli): keep completion and Twitch races bounded 2026-05-24 03:55:49 +01:00
Peter Steinberger
e9bf1113fa fix(twitch): cancel pending clients during shutdown 2026-05-24 03:55:49 +01:00
Peter Steinberger
5b2703e24d fix(plugins): avoid Signal and Twitch setup regressions 2026-05-24 03:55:49 +01:00
Peter Steinberger
c617009cbf fix(plugins): stabilize Twitch and Signal setup 2026-05-24 03:55:49 +01:00
Peter Steinberger
25ccadd22a fix(acp): require allow option for auto approvals 2026-05-24 03:55:49 +01:00
Peter Steinberger
bee15d4fa2 fix(browser): validate inputs and redact remote URLs 2026-05-24 03:55:49 +01:00
Peter Steinberger
9410eb30cf fix(cli): preserve explicit command intent 2026-05-24 03:55:49 +01:00
Peter Steinberger
a4e95cf7b1 fix(cli): bound node media file writes 2026-05-24 03:55:49 +01:00
Peter Steinberger
181d55ee1b docs(changelog): note CLI and plugin bug fixes 2026-05-24 03:55:49 +01:00
Peter Steinberger
6d6b2479ad fix(gateway): scope imported history identity 2026-05-24 03:55:49 +01:00
Peter Steinberger
eeb5f12293 fix(plugins): fail stalled runtime operations 2026-05-24 03:55:49 +01:00
Peter Steinberger
9ab0af270a fix(cli): keep plugin command metadata intact 2026-05-24 03:55:49 +01:00
Peter Steinberger
15ff89bf5d fix(cli): preserve command option state 2026-05-24 03:55:49 +01:00
Peter Steinberger
308af85991 fix(cli): harden generated completions 2026-05-24 03:55:49 +01:00
Peter Steinberger
459cee5315 fix(cli): reject malformed timeout options 2026-05-24 03:55:49 +01:00
Kaspre
96959ec3d7 fix(codex): defer native-hook-relay unregister to avoid cleanup race
Keep successful Codex native hook relays alive through a bounded grace window so late hook callbacks still reach OpenClaw enforcement, while interrupted, aborted, timed-out, and failed turns unregister immediately.\n\nCo-authored-by: Kaspre <kaspre@gmail.com>
2026-05-24 03:53:00 +01:00
NianJiu
0abedd546a fix(models): preserve source snapshots for SecretRef providers
* fix(models): preserve source snapshots for SecretRef providers

* docs: add models SecretRef changelog entry

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 03:48:05 +01:00
Peter Steinberger
bc6d430d00 fix: recover discord realtime playback state 2026-05-24 03:44:31 +01:00
samzong
31145e0dd9 [Fix] Preflight runtime SecretRefs before config writes (#84454)
* fix(config): preflight runtime secret refs before writes

* fix(config): restore include rollback env

* docs(changelog): note SecretRef config preflight

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 03:40:15 +01:00
Shakker
81dee15406 test: narrow transcript rewrite message content access 2026-05-24 03:37:43 +01:00
Shakker
5534cad6fc test: cover control ui source reply final retention 2026-05-24 03:37:43 +01:00
Shakker
5e2857477b fix: keep source reply finals live in control ui 2026-05-24 03:37:43 +01:00
Shakker
321d98b982 test: cover source reply media transcript backing 2026-05-24 03:37:43 +01:00
Shakker
39226ea35b fix: back source reply media in transcripts 2026-05-24 03:37:43 +01:00
Shakker
b074dc5395 fix: guard transcript source reply rewrites 2026-05-24 03:37:43 +01:00
Shakker
17fc1d1143 fix: ignore replayed empty TUI finals 2026-05-24 03:37:43 +01:00
Shakker
a5568ddfe0 fix: preserve source reply metadata through TTS 2026-05-24 03:37:43 +01:00
Shakker
a10e152519 fix: avoid duplicate media source reply transcripts 2026-05-24 03:37:43 +01:00
Shakker
a238f03521 fix: preserve reply metadata through media normalization 2026-05-24 03:37:43 +01:00
Shakker
6a0e030a47 fix: avoid double terminal chat events for source replies 2026-05-24 03:37:43 +01:00
Shakker
f5b415f138 fix: bound Codex post-reasoning source reply waits 2026-05-24 03:37:43 +01:00
Shakker
c93dda9423 fix: keep long Codex source replies alive 2026-05-24 03:37:43 +01:00
Shakker
84d278ad81 fix: keep TUI watchdog runs active 2026-05-24 03:37:43 +01:00
Shakker
59b8aea09e fix: render late source reply finals in TUI 2026-05-24 03:37:43 +01:00
Shakker
589fd923ce docs: add TUI source reply changelog 2026-05-24 03:37:43 +01:00
Shakker
84ac31b6db fix: broadcast source reply finals for chat runs 2026-05-24 03:37:43 +01:00
Shakker
bfcd8017c4 fix: preserve reply payload metadata 2026-05-24 03:37:43 +01:00
WhatsSkiLL
b13166bc0c fix: gracefully escalate process supervisor cancellations (#85865)
* fix: gracefully escalate supervisor cancellations

* fix: preserve process-tree cancellation during grace

* fix: satisfy signal monitor allSettled lint

* fix(process): split graceful cancel signal escalation

---------

Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 03:35:37 +01:00
brokemac79
f55e98671a fix: preserve internal handoff status attribution [AI-assisted] (#85726)
* fix: preserve status attribution for internal handoffs

* fix: preserve internal handoff status attribution (#85726) (thanks @brokemac79)

* fix: surface internal fallback failures (#85726)

* fix: preserve internal handoff session continuity (#85726)

* fix: skip internal fallback auto overrides (#85726)

* fix: preserve direct internal handoff state (#85726)

* fix: authorize internal announce handoff (#85726)

* fix: preserve handoff accounting without hiding transcript (#85726)

* test: fix session-store cli backend fixture (#85726)

* fix: trust-gate handoff accounting preservation (#85726)

* fix: avoid stale preserve-mode session writes (#85726)

* fix: avoid preserve-mode session identity writes (#85726)

* fix: hide internal handoff usage footers (#85726)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 03:24:27 +01:00
Peter Steinberger
029472c6de fix: keep discord realtime audio playback alive 2026-05-24 03:20:01 +01:00
Masato Hoshino
069c7b87eb fix(browser): thread snapshot timeoutMs through agent tool and helpers (#75702)
Summary:
- Threads browser snapshot `timeoutMs` through the agent action, client/proxy request, snapshot route plan, Ch ...  Playwright/CDP helpers, regression tests, changelog, and one JSDoc-only shrinkwrap script type annotation.
- Reproducibility: yes. source reproduction is high-confidence: current main accepts top-level browser `timeou ...  helpers drop it. I did not rerun the original macOS or Browserbase live scenario in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(browser): apply default snapshot timeout to proxy path and add Pl…
- PR branch already contained follow-up commit before automerge: docs(changelog): add browser snapshot timeout propagation fix entry
- PR branch already contained follow-up commit before automerge: fix(browser): thread snapshot timeoutMs through agent tool and helpers
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-7570…

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

Prepared head SHA: 0eec196962
Review: https://github.com/openclaw/openclaw/pull/75702#issuecomment-4359923127

Co-authored-by: masatohoshino <g515hoshino@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-24 02:15:58 +00:00
Rohit
d581415026 Disable Chrome MCP telemetry watchdog by default (#85886)
Summary:
- The PR adds the Chrome DevTools MCP `--no-usage-statistics` default launch arg, honors explicit profile usage-statistics `mcpArgs`, adds regression tests, and adds a changelog entry.
- Reproducibility: yes. source-reproducible: current main builds Chrome MCP launch args without the upstream o ... etry is initialized. I did not run a fresh failing current-main process leak loop in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Disable Chrome MCP telemetry watchdog by default

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

Prepared head SHA: 68249b1f58
Review: https://github.com/openclaw/openclaw/pull/85886#issuecomment-4526997996

Co-authored-by: Rohit <rohitjavvadi2@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-24 02:02:37 +00:00
Peter Steinberger
12f82270cf perf: cache stable gateway metadata 2026-05-24 02:54:28 +01:00
openclaw-release-bot
fc3c9791ad chore(release): update appcast for 2026.5.22 2026-05-24 01:52:49 +00:00
Dmitry Golubev
7b3be04582 fix(codex): ensure codex subagent bootstrap parity with pi subagents - only inject AGENTS.md and TOOLS.md (#85811)
* fix: limit Codex native subagent bootstrap context

* fix: preserve Codex turn instructions accounting

* fix: split Codex workspace instruction renderers

* fix(codex): keep persona files turn-scoped

---------

Co-authored-by: Beru <beru@lastguru.lv>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 02:48:03 +01:00
Gio Della-Libera
1f28c3e42b fix(update): escape systemd update handoffs (#85414) 2026-05-23 18:44:01 -07:00
scotthuang
5dcbd385f7 fix(media-understanding): restore image description token default
Restore the describeImageWithModel default token budget to the helper-level 4096-token default instead of forcing 512 before resolution.

Add regression coverage for the default and for smaller model caps, and record the user-facing fix in the changelog.

Co-authored-by: scotthuang <scotthuang@tencent.com>
2026-05-24 02:42:18 +01:00
Peter Steinberger
0cba872e38 chore: bump version to 2026.5.24 2026-05-24 02:40:16 +01:00
Peter Steinberger
6c210668ed docs: note WebChat done ordering fix 2026-05-24 02:36:07 +01:00
Neerav Makwana
c614b59f03 fix(ui): delay WebChat done indicator until reply renders
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 02:36:07 +01:00
Peter Steinberger
40d36b5bbc docs(talk): document realtime active-run control
Co-authored-by: Colin <colin@solvely.net>
2026-05-24 02:35:04 +01:00
Peter Steinberger
4ffa07d136 feat(discord): control active realtime voice runs
Co-authored-by: Colin <colin@solvely.net>
2026-05-24 02:35:04 +01:00
Peter Steinberger
13c0b1952e feat(ui): control active realtime talk runs
Co-authored-by: Colin <colin@solvely.net>
2026-05-24 02:35:04 +01:00
Peter Steinberger
a1f47bccb5 feat(gateway): steer realtime relay agent runs
Co-authored-by: Colin <colin@solvely.net>
2026-05-24 02:35:04 +01:00
Peter Steinberger
bbf9c45ba7 feat(talk): add realtime active-run control
Co-authored-by: Colin <colin@solvely.net>
2026-05-24 02:35:04 +01:00
Jason O'Neal
ee09481a88 fix(tui): handle German AltGr input (#83947)
Summary:
- The PR updates the TUI CustomEditor to ignore Kitty key-release events, decode German-layout AltGr printable CSI-u input, and adds regression tests plus a changelog entry.
- Reproducibility: yes. The PR body supplies a before/after PTY/raw-stdin path for the exact Kitty CSI-u bytes ... es to pi-tui, whose printable decoder rejects Alt/Ctrl AltGr input and can insert printable release events.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Merge main into fix/issue-48897
- PR branch already contained follow-up commit before automerge: Merge upstream/main into fix/issue-48897
- PR branch already contained follow-up commit before automerge: chore: remove unrelated import churn from AltGr fix

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

Prepared head SHA: ccd1057c05
Review: https://github.com/openclaw/openclaw/pull/83947#issuecomment-4484076134

Co-authored-by: Jason O'Neal <jason.allen.oneal@gmail.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-24 01:31:03 +00:00
Peter Steinberger
6e03d1ca5b docs: clarify commit fallback provenance 2026-05-24 02:27:39 +01:00
Peter Steinberger
d92501dbf3 fix(pdf): move MiniMax PDF fallback policy to metadata 2026-05-24 02:26:47 +01:00
Neerav Makwana
4f95cc3dac fix(pdf): preserve image fallback precedence 2026-05-24 02:26:47 +01:00
Neerav Makwana
89bb62e2d7 fix(pdf): use MiniMax text model fallback 2026-05-24 02:26:47 +01:00
clawsweeper[bot]
1a60c19743 fix(ui): preserve source config for Control UI saves (#85879)
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-24 02:23:09 +01:00
Andy Tien
aa050a6e95 fix(doctor): repair stale contextWindow for DeepSeek V4 Flash (#85840)
* fix(doctor): repair stale contextWindow for DeepSeek V4 Flash

Problem:
- Older releases configured deepseek-v4-flash with contextWindow: 200000
- Official DeepSeek V4 Flash context window is 1,000,000 (1M)
- Users switching from smaller models see incorrect progress bar (e.g.,
  50% instead of 10%) because stale config value overrides catalog

Fix:
- Add 'models.providers.*.models.*.contextWindow-stale' migration
- Detects deepseek-v4-flash models with 200K contextWindow
- Repairs to 1M to match catalog default
- Handles both bare and provider-prefixed model IDs
- 7 unit tests covering repair, passthrough, edge cases

Fixes: #85834

* fix(doctor): preserve custom DeepSeek context windows

* fix(doctor): detect stale DeepSeek context windows

* fix(doctor): scope DeepSeek context repair

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 02:22:53 +01:00
scotthuang
a66898209a Feat/fix dashboard timeout error display (#85815)
* fix(gateway): broadcast error to UI when chat.send fails synchronously

* test(gateway): verify broadcastChatError is called on chat.send error

* test(gateway): import GatewayRequestContext from local server-methods barrel

Fixes the chat error-broadcast regression test so it can resolve its
type import. The previous `../types.js` path does not exist in the
gateway tree; the shared types are re-exported from
`src/gateway/server-methods/types.ts`, so the test must use `./types.js`.

Addresses ClawSweeper review on PR #85815.

---------

Co-authored-by: scotthuang <scotthuang@tencent.com>
2026-05-24 02:22:32 +01:00
AMARA
27a3290b53 fix(memory): write fallback dream diary on narrative timeout (#85821) 2026-05-24 02:22:12 +01:00
alkor2000
72744fd5fd fix(twitch): fail fast when auth provider cannot bind user (#85794)
createAuthProvider swallowed addUserForToken rejections in a .catch()
that only logged, so getClient returned and cached a ChatClient backed
by a RefreshingAuthProvider with no bound user. The failure surfaced
later as an opaque auth error on first send instead of failing fast.

Re-throw in the catch so getClient rejects and does not cache the broken
client. Adds regression tests for the rejection and the no-cache behavior.

Fixes #83853
2026-05-24 02:21:59 +01:00
Peter Steinberger
9a73ddc394 docs: clarify PR provenance roles 2026-05-24 02:16:56 +01:00
Vincent Koc
32f91503be fix(scripts): harden Windows QA runners 2026-05-24 02:55:08 +02:00
Peter Steinberger
acf265d4d5 docs(skills): prefer latest Parallels snapshots 2026-05-24 01:30:25 +01:00
狼哥
f05f243824 fix(telegram): normalize durable group retry targets (#85656)
Summary:
- The PR normalizes legacy Telegram `group:<numeric>` durable retry targets before text/media/payload/poll sends and delivered-message pinning, with regression tests and a changelog entry.
- Reproducibility: yes. Source inspection shows recovery passes `entry.to` unchanged into the Telegram outbound path, and current send resolution rejects bare `group:-100...` as a non-numeric Telegram chat ID.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(telegram): normalize durable retry pin targets
- PR branch already contained follow-up commit before automerge: fix(telegram): normalize durable group retry targets
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8565…

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

Prepared head SHA: 272bc225dd
Review: https://github.com/openclaw/openclaw/pull/85656#issuecomment-4524463510

Co-authored-by: luoyanglang <hanwanlonga@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-24 00:30:11 +00:00
clawsweeper[bot]
fa39bef389 fix #84857: skip CLI runtime harness preflight during compaction (#85862)
Summary:
- The PR skips agent-harness compaction preflight for provider-owned or configured CLI runtime sessions, adds claude-cli regression coverage, includes a changelog entry, and applies small test/type cleanups.
- Reproducibility: yes. at source level. Current main still routes provider-owned `claude-cli` runtime compaction preflight through harness selection, where `claude-cli` is not a registered embedded harness.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix #84857: skip CLI runtime harness preflight during compaction
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8487…

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

Prepared head SHA: 1dd8a88d21
Review: https://github.com/openclaw/openclaw/pull/85862#issuecomment-4526794976

Co-authored-by: 张贵萍0668001030 <zhang.guiping@xydigit.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-24 00:00:55 +00:00
Gio Della-Libera
4ffbd07c06 docs(policy): add policy rule reference tables (#85795) 2026-05-23 16:59:33 -07:00
Gio Della-Libera
1e2e614748 Policy: add tool posture conformance checks (#85482)
* feat(policy): add tool posture conformance

* fix(policy): attest tool alsoAllow posture
2026-05-23 16:44:42 -07:00
njuboy
a1eb765f0a fix(session-lock): enforce maxHoldMs in shouldReclaim during lock acquisition (#85764)
* fix(session-lock): enforce maxHoldMs in shouldReclaim during lock acquisition

- Adds optional maxHoldMs parameter to inspectLockPayload
- Inspect now marks locks as stale when held longer than maxHoldMs
- Passes maxHoldMs through inspectLockPayloadForSession
- acquireSessionWriteLock's shouldReclaim callback now passes maxHoldMs

This ensures that when a live process holds a lock for longer than
maxHoldMs (default 5min), other processes can reclaim it during
acquisition — matching the watchdog's existing enforcement.

Previously shouldReclaim only used staleMs (30min default), meaning
a lock held for 10+ minutes by a live PID would never be reclaimable,
causing 60s timeout failures and gateway freezes.

Closes #85762

* fix(session-lock): add dead-PID fast-path before retry loop

Adds a fast-path check at the top of acquireSessionWriteLock:
if the lock file's owner PID is dead, remove it immediately
before entering the retry loop. This saves up to timeoutMs (60s)
of futile waiting when the previous lock holder has died.

The shouldReclaim callback already handles this case, but only
iteratively through the retry loop. The fast-path eliminates
that unnecessary delay.

* fix(session-lock): enforce max hold during acquisition

* fix(session-lock): revalidate max hold safely

* fix(session-lock): honor holder max-hold policy

* fix(session-lock): keep cleanup from reclaiming live holders

* fix(session-lock): remove stale locks only when unchanged

* fix(session-lock): skip self-held max-hold reclaim

* fix(ci): refresh gateway protocol checks

---------

Co-authored-by: njuboy11 <njuboy11@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 00:38:01 +01:00
Peter Steinberger
a1c2d093c2 refactor: simplify channel catalog cache 2026-05-24 00:31:01 +01:00
Peter Steinberger
d4299dcbaa docs: codify gateway plugin metadata stability 2026-05-24 00:31:01 +01:00
Peter Steinberger
e5534dd2f3 perf: reduce gateway benchmark filesystem churn 2026-05-24 00:31:01 +01:00
Peter Steinberger
e2249d8d1e fix: order meeting notes startup around channels 2026-05-24 00:30:39 +01:00
Peter Steinberger
a0f6ce03ce fix: preserve sandbox skill overlay precedence (#85591) 2026-05-24 00:28:49 +01:00
Peter Steinberger
68487f494c fix: close remote sandbox skill symlink aliases (#85591) 2026-05-24 00:28:49 +01:00
Peter Steinberger
a8f68877a5 fix: guard remote sandbox skill roots (#85591) 2026-05-24 00:28:49 +01:00
Peter Steinberger
a3526789a4 fix: harden sandbox skill mounts (#85591) 2026-05-24 00:28:49 +01:00
Jason O'Neal
10942102e3 test(sandbox): allow remote writes under absent skill roots 2026-05-24 00:28:49 +01:00
Jason O'Neal
dd5fb1e71f fix(sandbox): filter remote skill mounts by existing roots 2026-05-24 00:28:49 +01:00
Jason O'Neal
07abb19431 test(sandbox): resolve skill roots read-only 2026-05-24 00:28:49 +01:00
Jason O'Neal
7152806950 fix(sandbox): block remote bridge writes to skills 2026-05-24 00:28:49 +01:00
Jason O'Neal
9e5b416130 fix(sandbox): block bridge writes to workspace skills 2026-05-24 00:28:49 +01:00
Jason O'Neal
1b7bf4a56f fix(sandbox): mount workspace skills read-only 2026-05-24 00:28:49 +01:00
Abdel Gomez-Perez
5c4a733912 fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars (#83117)
* fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars

`buildCliSessionHistoryPrompt` was prefix-slicing the rendered history,
dropping the most recent assistant turns from the reseed prompt. After
#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is
exposed to this on session_expired when the rendered transcript exceeds
12288 chars. The truncation marker landed mid-word in real reproductions.

Fix:
- Tail-slice (keep the recent suffix, drop the older prefix)
- Pin the compaction summary as a prefix when present, only cap the
  post-summary transcript (loadCliSessionReseedMessages deliberately
  places the summary first)
- When the summary alone exceeds maxHistoryChars, head-slice the summary
  itself to honor the cap; drop the post-summary tail in that case
- Move the truncation marker to the lead since what follows is the
  recent tail, not what was dropped

Closes #83157

* fix(cli-runner): retain recent tail with oversize summaries

* fix(cli-runner): cap summary block plus marker against maxHistoryChars

ClawSweeper P2 on #83117 flagged that when `summaryRendered.length` is
less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n`
separator) meets or exceeds it, the `remainingBudget <= 0` arm of
`buildCliSessionHistoryPrompt` appends the truncation marker after the
already-full summary block. A 199-char rendered summary under a 200-char
cap produced a 257-char history block — defeating the cap that prevents
reseeding fresh CLI sessions with unexpectedly huge prompts.

Fix the budget edge by truncating the summary in this branch as well so
`summary + separator + marker` stays within `maxHistoryChars`. The tail
still drops (the summary alone consumes the budget) and the marker still
leads its own line so the prompt announces what was discarded. Mirrors
the existing oversize-summary branch's pattern of head-slicing the
summary against an explicit budget that reserves marker + separator.

Add a focused regression in `session-history.test.ts` covering exactly
the gap the finding called out: `summaryRendered.length < maxHistoryChars`
with a non-empty post-summary tail. Asserts the rendered history block
stays within `maxHistoryChars` and the truncation marker is present.

* fix(cli-runner): keep tail for near-cap summaries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-24 00:07:11 +01:00
Peter Steinberger
846f56642b docs: thank backup hardlink contributor (#83938) 2026-05-24 00:05:55 +01:00
Jason O'Neal
77d1157618 fix(backup): reject missing hardlink targets 2026-05-24 00:05:55 +01:00
Jason O'Neal
d8a2cd5204 fix(backup): dereference archive hardlinks 2026-05-24 00:05:55 +01:00
Peter Steinberger
d73f3ac85d refactor: split subagent delivery state 2026-05-24 00:05:48 +01:00
Peter Steinberger
3cf806d172 fix(telegram): cache outbound replies for context
Co-authored-by: Keshav's Bot <keshavbotagent@gmail.com>
2026-05-24 00:04:16 +01:00
Peter Steinberger
ec0e76792c docs: require blame-backed bug provenance 2026-05-24 00:02:22 +01:00
Jason O'Neal
cf70bdcceb fix(webchat): scope attachment button input 2026-05-23 23:59:48 +01:00
brokemac79
0c044596c5 fix(twitch): evict client manager on disconnect failure (#85796) 2026-05-23 23:58:55 +01:00
Youssef Hemimy
f0ec7309fc fix(whatsapp): serialize Error in auto-reply delivery log (#85777)
The auto-reply "delivery failed" log path passes a raw Error
under the `err` field. tslog's default JSON serialization
renders bare Error instances as `{}` because Error own data
properties are non-enumerable. Every delivery failure in
production therefore logs `err: {}`, forcing operators to
guess the underlying Baileys error from timestamp alone.

Convert Error to `{ type, message, stack }` plus own-enumerable
properties at the log site, so Boom-style subclass diagnostics
(output.statusCode, data) and custom OutboundDeliveryError
fields (stage, results) survive. Non-Error rejection values
pass through unchanged.

Tests cover Error, Error subclass (Boom-style), string
rejection, and object rejection paths.

AI-assisted: Claude Code (Opus 4.7) authored, codex review
locally addressed.
2026-05-23 23:58:51 +01:00
JC
0050245bc7 fix(gateway): omit stream-error placeholders from agent prompts (#85652)
* fix(gateway): omit stream-error placeholders from agent prompts

* fix(gateway): omit internal placeholder prompts

* fix(gateway): filter placeholder by role

* fix(gateway): preserve current prompt text

* test(plugin): align cold-boundary model normalization expectation

* fix(gateway): mark internal stream-error prompt entries

* fix(gateway): preserve empty tool prompt entries

* test(plugin): expect static xai normalization

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 23:58:47 +01:00
Peter Steinberger
bb947eed6c docs: add changelog for webchat tool result fix (#84773) 2026-05-23 23:55:10 +01:00
Jason O'Neal
83c2e96a16 fix: summarize internal webchat message tool results 2026-05-23 23:55:10 +01:00
Matthew Kern
a37ebb2d49 fix(browser): bypass managed proxy for loopback CDP
Keep browser CDP managed-proxy bypasses on the private bundled-plugin SSRF helper, strip WebSocket URL credentials before registering exact bypass URLs, and document the managed-browser loopback proxy behavior.

Co-authored-by: Matthew Kern <matthew@matthewkern.xyz>
2026-05-23 23:53:27 +01:00
Peter Steinberger
69e8039f9a fix: omit empty proxy tools (#85835) 2026-05-23 23:52:02 +01:00
rendrag-git
75081569b0 fix(agents): omit empty tools array for proxy-like openai-completions endpoints
Strict OpenAI-compatible servers (vLLM, LocalAI, llama.cpp, LM Studio) and
current OpenAI itself reject requests containing tools: []. Strip the empty
tools array (and the orphan tool_choice) from outbound chat-completions
payloads when usesExplicitProxyLikeEndpoint is true. Native OpenAI/Azure/
OpenRouter routes are byte-identical.

Supersedes #70790 at the canonical payload builder seam so the gateway,
embedded runner, and public plugin-SDK consumers (zai/xiaomi/deepseek) all
benefit.
2026-05-23 23:52:02 +01:00
Peter Steinberger
6394dd1ac5 fix: preserve gateway lifecycle error cleanup (#85256) (thanks @samzong) 2026-05-23 23:50:55 +01:00
samzong
bc2d501b1d fix(gateway): preserve lifecycle cleanup
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-23 23:50:55 +01:00
samzong
9d56f4aa14 fix(gateway): preserve deferred lifecycle errors 2026-05-23 23:50:55 +01:00
Peter Steinberger
4cc2b293db ci: mount local installer scripts in smoke containers 2026-05-23 23:43:36 +01:00
Peter Steinberger
b52c31fe0e fix: speed up agent tool tests 2026-05-23 23:38:11 +01:00
Peter Steinberger
4314674054 perf: reuse plugin metadata snapshots (#85843)
* perf: reuse plugin metadata snapshots

* test: update plugin metadata snapshot mocks
2026-05-23 23:34:19 +01:00
Tyler Bea
45fbf2d81a fix(channels): honor /verbose in group sessions (#85488)
* codex: honor verbose in group dispatch

* codex: address group verbose review findings

Record the final local review pass for the group /verbose PR.

Codex review against origin/main completed clean after tightening the shared group progress gate, keeping public plugin hook types stable, preserving ACP hidden tool boundaries, and adding regressions for live verbose gating and progress-callback suppression.

* codex: require explicit group verbose progress

Normal group tool/progress summaries now require an explicit session verbose override instead of inherited agent verbose defaults.

This addresses the PR review concern that existing verboseDefault configurations could expose group progress after upgrade. DMs and forum-topic behavior continue to use the effective verbose state, while normal groups use the live explicit session verbose state set by /verbose on|full|off.

* codex: document Slack group verbose caveat

* fix(channels): simplify verbose progress gating

* docs(changelog): note verbose channel fix

* fix(channels): preserve quiet default for group progress

* fix(channels): keep verbose error policy dynamic

* fix(channels): default verbose progress off everywhere

* fix(channels): keep followup verbose default quiet

* fix(channels): latch visible tool-error progress

* fix(channels): track failed verbose progress events

* fix(channels): latch delivered tool errors

* fix(channels): prevent progress opt-out bypass

* fix(channels): isolate followup error warning state

* fix(channels): keep full verbose followup warnings

* fix(channels): latch tool errors after visible progress

* fix(channels): require visible followup failure progress

* fix(channels): refresh followup verbose state

* fix(channels): honor live verbose for error details

* test(channels): expect live verbose off warning mode

* fix(channels): preserve static tool error suppression semantics

* fix(channels): bypass acp for colon verbose commands

* fix(channels): narrow dynamic tool warning override

* fix(channels): gate compaction notices on live verbose

* fix(channels): suppress quiet followup compaction callbacks

* fix(channels): suppress tts for hidden tool summaries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 23:14:11 +01:00
Peter Steinberger
2cd73d4c89 chore: sync autoreview skill snapshot 2026-05-23 23:10:40 +01:00
Peter Steinberger
1b68dbe95a test: isolate Codex replay timeout outcome 2026-05-23 23:06:41 +01:00
Peter Steinberger
32a80d9954 test: isolate Codex hook channel context 2026-05-23 22:55:52 +01:00
Peter Steinberger
f6204d081f test: isolate Codex duplicate terminal diagnostics 2026-05-23 22:45:19 +01:00
Peter Steinberger
fa5c8345f3 test: isolate Codex terminal diagnostic fallback 2026-05-23 22:34:39 +01:00
Jason (Json)
f603fa58fe fix(discord): keep forced voice consult diagnostics private (#84411)
Summary:
- The PR removes forced consult diagnostics from Discord and phone-call realtime consult payloads, adds private debug logs and regression tests, and records the fix in the changelog.
- Reproducibility: yes. by source inspection. Current main builds the forced Discord consult message with the  ... gent_consult` diagnostic string, and the phone-call fallback passes the same diagnostic as consult context.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(discord): log forced consult fallback reason
- PR branch already contained follow-up commit before automerge: fix(discord): keep forced voice consult diagnostics private

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

Prepared head SHA: c1592530c6
Review: https://github.com/openclaw/openclaw/pull/84411#issuecomment-4494164784

Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@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>
2026-05-23 21:33:23 +00:00
Peter Steinberger
a705a9c911 test: drain Codex app-server attempts 2026-05-23 22:24:25 +01:00
Gio Della-Libera
05c6e7a553 feat(agents): expose estimated context budget status
Expose a path-free estimated context budget status on session entries and gateway session rows, render it in status when fresh provider usage is unavailable, and clear stale estimates across reset, refresh, compaction, and session-rotation boundaries.

Verification: focused local Vitest covered session persistence, status rendering, gateway rows, model resets, compaction, and session rotation; GitHub CI passed on clean head cad199e43d.

Refs #80594, #54996, #77992, #84490, #83177, #43009, #83526, #8635.
2026-05-23 14:17:44 -07:00
Peter Steinberger
cd102efb70 test: isolate Codex native item release guard 2026-05-23 22:13:22 +01:00
Peter Steinberger
38e1654e09 fix: route Codex image API keys through OpenAI 2026-05-23 22:05:07 +01:00
Kevin Lin
5fbaf2a8a2 feat(whatsapp): support thumb approval reactions (#85477)
* feat(whatsapp): support emoji approval reactions

* fix(whatsapp): simplify approval resolved text

* fix(whatsapp): gate approvals on forwarding config

* ci: ignore injected secrets helpers in oxlint

* fix(whatsapp): use thumb reactions for approvals

* ci: keep secret helpers linted

* fix(approvals): preserve plugin turn source routes

* docs(approvals): remove whatsapp exec approval field refs
2026-05-23 13:58:00 -07:00
Peter Steinberger
6a3781dd7f test: isolate Codex terminal batch scheduler 2026-05-23 21:57:34 +01:00
Peter Steinberger
4c210e22fa Adapt image compression quality by model (#85742)
* feat: adapt image compression quality

* refactor: move image limits into model metadata

* test: cover adaptive image downscaling

* test: cover image tool live providers

* fix: apply media metadata to all image paths

* fix: align providerless image compression

* fix: add chutes runtime image limits

* fix: optimize image data urls with model limits

* fix: type media metadata merge

* fix: optimize data url byte limits after decode

* fix: preserve data url optimizer fallback

* fix: keep low-side image compression fallbacks

* fix: enforce data url image compression policy

* fix: preserve gif data url media policy

* fix: satisfy adaptive image type checks

* test: keep cron provider-runtime mock current
2026-05-23 21:45:55 +01:00
Peter Steinberger
00388134c4 test: isolate Codex terminal release decision 2026-05-23 21:31:17 +01:00
Peter Steinberger
c4f0da00a9 refactor: use channel target resolution APIs (#85814)
* refactor: use channel target resolution apis

* refactor: satisfy delivery lint

* refactor: remove unused target parsing shim

* fix: preserve routed cron topic targets
2026-05-23 21:26:55 +01:00
Kaspre
fd2a9adbe6 fix(ollama): bypass managed proxy for loopback embeddings (#85707)
* fix(ollama): bypass proxy for local embeddings

* fix(ollama): keep managed proxy bypass loopback-only

* fix(ollama): keep proxy bypass internal

* fix(ollama): keep proxy bypass private

* fix(ollama): harden internal proxy bypass

* chore(plugin-sdk): refresh api baseline

* fix(ollama): keep internal bypass out of qa aliases

* test(ollama): keep ssrf runtime mock complete

* fix(ollama): keep dist sdk aliases public-only

* fix(ollama): keep fetch bypass out of infra runtime

* fix(ollama): preserve packaged private sdk alias

* test(ollama): harden private ssrf alias coverage

* test(ollama): cover private ssrf resolver edges

* fix(ollama): scope private sdk native aliases

* test(ollama): audit blocked loopback bypasses

* fix(plugins): keep staged sdk aliases public-only

* test(ollama): harden proxy bypass proof

* test(ollama): cover origin mismatch proxy path

* test(ollama): cover ipv6 and batch bypass paths

* fix lint findings in Ollama proxy tests

* refactor: tighten Ollama proxy bypass

* fix: widen private sdk owner registry type

* test: stabilize Ollama proxy PR checks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 21:17:55 +01:00
Peter Steinberger
f6b332c735 test: make Codex diagnostic test deterministic 2026-05-23 21:13:41 +01:00
pashpashpash
8ede81af66 fix(image): hint safe provider timeout (#85812) 2026-05-23 13:10:03 -07:00
Peter Steinberger
2656f13ff8 docs(skills): require autoreview before bug sweep URLs 2026-05-23 20:51:46 +01:00
Peter Steinberger
6fc9d7b14f test: bound Codex app-server cleanup waits 2026-05-23 20:48:03 +01:00
sallyom
266f38b261 fix(control-ui): restore light select arrows
Signed-off-by: sallyom <somalley@redhat.com>
2026-05-23 15:43:42 -04:00
Linux2010
ae79e6e5ec fix(web-ui): improve light theme visibility for select arrows and bubble hover
Problem:
- Select dropdown arrow uses hardcoded #888 SVG stroke, barely visible on
  light backgrounds
- Chat bubble hover border uses 28% accent blend, too subtle in light theme
  for meaningful visual feedback

Fix:
- Darken dropdown arrow SVG to #444 in light theme (.cfg-select)
- Increase chat-bubble:hover accent blend from 28% to 48% in light theme
- Add subtle box-shadow on bubble hover for clearer feedback

Fixes: #85713
2026-05-23 15:43:42 -04:00
Peter Steinberger
d2e9f91cec test: align full release dispatch assertion 2026-05-23 20:24:30 +01:00
Vincent Koc
353d13248e fix(scripts): route check stages through managed runner 2026-05-23 21:20:52 +02:00
Peter Steinberger
9cef99f184 test: clean up Codex app-server run failures 2026-05-23 20:12:44 +01:00
Peter Steinberger
ee61f79b90 ci: retry release child workflow dispatch 2026-05-23 19:56:23 +01:00
Peter Steinberger
071c3e364b test: isolate Codex report snapshot tests 2026-05-23 19:48:06 +01:00
Peter Steinberger
edbd833351 perf(gateway): reduce startup filesystem probes 2026-05-23 19:38:53 +01:00
Peter Steinberger
fcb9c46af0 ci: retry GHCR docker login 2026-05-23 19:28:03 +01:00
Peter Steinberger
d42bc0b684 ci: harden manual checkout auth 2026-05-23 19:11:13 +01:00
Vincent Koc
208a0679e2 fix(scripts): avoid Windows shell argv warnings 2026-05-23 20:00:24 +02:00
Peter Steinberger
02b1c8c902 ci: fix release reachability auth 2026-05-23 18:59:14 +01:00
Peter Steinberger
388b24a34f docs: note docs publishing routing 2026-05-23 18:57:47 +01:00
Peter Steinberger
41f4605020 ci: harden release package validation 2026-05-23 18:48:17 +01:00
Peter Steinberger
3e14f54ffc ci(testbox): expose stable pnpm through corepack 2026-05-23 18:32:04 +01:00
Peter Steinberger
1f2d8f98ba ci(testbox): avoid ready raw runners after hydration failure 2026-05-23 18:30:37 +01:00
Peter Steinberger
f1226aeb6c perf(gateway): defer startup-idle runtime work 2026-05-23 18:27:04 +01:00
Peter Steinberger
391f29baad ci: harden beta release validation flakes 2026-05-23 18:23:39 +01:00
Peter Steinberger
86a0502711 test: type codex thread request mocks 2026-05-23 18:03:19 +01:00
Peter Steinberger
85664f8e71 test: avoid codex heartbeat lifecycle timeout 2026-05-23 17:56:26 +01:00
Vincent Koc
8a94e825cd fix(scripts): run Windows check commands through shims 2026-05-23 18:30:14 +02:00
Peter Steinberger
f4b5e58231 fix: aggressively prune retired model catalogs 2026-05-23 17:29:50 +01:00
Jason O'Neal
7fffbf60b0 fix: harden package URL downloads (#85578)
* fix: harden package URL downloads

Guard package acceptance URL downloads with HTTPS-only validation, no embedded credentials, private/special-use DNS and IP rejection, manual redirect checks, bounded timeout/size limits, pinned lookup, and atomic temp-file writes. Add tooling tests for unsafe URLs, redirect validation, size limits, and successful writes.

* fix: cancel redirect response bodies before closing dispatcher

ClawSweeper P2: the redirect branch in openPackageDownloadResponse cleared
the timeout and awaited dispatcher.close() without first cancelling
response.body. Undici's close() is graceful — it waits for in-flight
requests to complete — so a malicious redirect with a slow/never-ending
body could hang the hardened downloader.

Fix: call response.body?.cancel() before dispatcher.close() to abort the
redirect body immediately.

Test: add a regression test that uses a ReadableStream with an indefinite
interval to simulate a hanging body, and asserts cancel() was called.

Refs: clawsweeper review on PR #85512

* test: harden redirect body cancellation race in regression test

Guard the ReadableStream controller.enqueue() call with a cancelled
flag and try/catch to prevent ERR_INVALID_STATE when the interval
fires after cancel() closes the controller.

* fix: cancel final response body before closing dispatcher in downloadUrl

ClawSweeper P2: the HTTP-error and declared-oversize early-exit paths
in downloadUrl threw before consuming or canceling response.body. The
finally block then cleared the timeout and awaited graceful
dispatcher.close() with the body still open, allowing a slow/never-ending
response to hang release tooling.

Fix: add response.body?.cancel() in the finally block before
dispatcher.close().

Tests: add two regressions:
- HTTP 500 with slow body: asserts cancel() called before dispatcher close
- Declared content-length oversize with slow body: same assertion

* fix: add trusted package URL source policy

* fix: keep package URL resolver dependency-free

* test: cover encoded IPv6 package URL bypasses

* docs: sync package acceptance source overview

* docs: restore release doc formatting

* docs: sync package acceptance trusted-url source

* test: cover dotted IPv4 embedded IPv6 package URLs

* fix: parse dotted IPv4 embedded in IPv6 package URLs

* test: isolate anthropic pruning defaults

* test: move anthropic dated model coverage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 17:28:29 +01:00
Peter Steinberger
35969ff440 ci: retry npm Telegram release dispatch 2026-05-23 17:19:00 +01:00
Peter Steinberger
a04566da11 test: isolate Telegram spooled timeout from stall watchdog 2026-05-23 17:08:00 +01:00
Sebastien Tardif
9dc1afe9bb fix(exec-approvals): add .catch() to expiry delivery fire-and-forget (#83106)
* fix(exec-approvals): add .catch() to expiry delivery fire-and-forget

When exec-approval expiry fires, deliverToTargets is called as a
fire-and-forget promise with no .catch(). If delivery fails, the
unhandled rejection swallows the error and the notification is lost.

Add .catch() with log.warn to match the ackDelivery error handling
pattern. Keep pending.delete() before the await (the entry is expired
regardless of delivery success).

Closes #83113

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix(approvals): label expiry delivery errors by kind

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 16:56:58 +01:00
davidbennett1979
983a3b94c9 fix(memory-core): avoid double bulleting promoted snippets (#85724) 2026-05-23 16:56:06 +01:00
Joey Frasier (Boothe)
ec65b71f5e fix(doctor): skip empty entries and memoize routes in plugin session repairs (#85718)
* fix(doctor): skip empty entries and memoize routes in plugin session repairs

runPluginSessionStateDoctorRepairs called resolveConfiguredDoctorSessionStateRoute
once per session-store key, even for entries that carry no plugin route state
fields. On stores with many CLI sessions (observed ~800 entries), each call
takes ~1.5s due to resolveAgentHarnessPolicy walking config and provider
metadata, so the doctor's state-integrity contribution hangs for minutes
and the surrounding 'openclaw doctor' run effectively never completes.

scanEntryForOwner can only produce repair/manual-review findings when the
entry exposes one of the fields covered by entryMayContainPluginSessionRouteState
(providerOverride/modelOverride/agentHarnessId/cliSessionBindings/etc.), so
the route resolution for empty entries was pure waste. The route itself is
also a function of agentId (sessionKey is only used to derive agentId), so
sessions sharing an agent can reuse one resolved route.

Filter the store by entryMayContainPluginSessionRouteState before resolving,
and memoize resolveConfiguredDoctorSessionStateRoute by agentId within the
remaining entries. On the repro store this drops the contribution from
'never completes' to <100ms.

Adds a guard test that builds a 200-entry store with 2 route-state-carrying
entries and asserts (a) the repair fires exactly once on the codex owner
and (b) the run completes in under 2s (pre-fix would take >5 minutes).

* fix(doctor): skip manifest model-id normalization in plugin session repairs

After the previous filter+memoize fix, runPluginSessionStateDoctorRepairs was
still ~38s on a 230-entry store because every scanned entry calls parseModelRef
on its runtime model. That implicitly enters manifest-driven model-id
normalization via normalizeStaticProviderModelId, which calls
loadPluginMetadataSnapshot when no current snapshot is bound to process state.

loadPluginMetadataSnapshot is filesystem-heavy and is only memoized when a
'current' snapshot is bound (it is not, during doctor), so each parseModelRef
call paid ~40ms of fresh plugin-metadata loading. 672 calls × ~40ms = ~27s
of doctor wall-clock, all of it useless for doctor's purposes: the scan only
needs the normalized provider id of the configured runtime/route to compare
against an owner's providerIds, never the manifest-normalized model id.

Pass allowManifestNormalization: false alongside the existing
allowPluginNormalization: false on all three parseModelRef call sites in
this file. normalizeStaticProviderModelId short-circuits to
normalizeBuiltInProviderModelId when allowManifestNormalization is false,
which is what doctor wants here.

On the same 230-entry store doctor:state-integrity drops from ~38s to ~2.4s
and total openclaw doctor wall-clock drops from ~91s to ~56s.
2026-05-23 16:55:35 +01:00
Peter Steinberger
6191750deb ci: avoid duplicate release-check auth headers 2026-05-23 16:55:03 +01:00
Peter Steinberger
b6530beb05 fix: prune retired model catalog entries 2026-05-23 16:46:59 +01:00
Peter Steinberger
0c192e2915 ci: authenticate release-check reachability fetches 2026-05-23 16:45:13 +01:00
Peter Steinberger
c5f1344faf docs(changelog): note Telegram attachment action fix 2026-05-23 16:43:23 +01:00
Peter Steinberger
054002529d refactor(telegram): simplify action media sends 2026-05-23 16:43:23 +01:00
Keshav's Bot
fdf01db62b fix(telegram): send attachment paths as media 2026-05-23 16:43:23 +01:00
Gio Della-Libera
c897384ae9 fix(doctor): canonicalize git checkout detection (#85735) 2026-05-23 08:42:23 -07:00
Peter Steinberger
030b7bb4b7 test(ci): update plugin prerelease checkout expectation 2026-05-23 16:31:34 +01:00
Peter Steinberger
d9f73cfe33 ci: persist checkout credentials for release validation 2026-05-23 16:17:24 +01:00
Peter Steinberger
5e8c71bf9f test(codex): avoid searchable-tool registration flake 2026-05-23 16:03:45 +01:00
Gio Della-Libera
056378efd5 refactor: simplify doctor repair checks (#83753) 2026-05-23 07:55:12 -07:00
Peter Steinberger
24de3047e5 docs(changelog): credit landed bug sweep PRs 2026-05-23 15:50:38 +01:00
Will.hou
bf84b3089d perf(utils): preserve message identity in stripInlineDirectiveTagsFromMessageForDisplay (#85682)
Consume the existing { text, changed } signal from
stripInlineDirectiveTagsForDisplay so unchanged text-parts keep their
references and the original message is returned when nothing was
stripped. Avoids spurious downstream rerenders/diff churn for consumers
relying on reference equality, and keeps the public SDK helper's text
output and message shape stable.

Fixes #37589.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 15:50:06 +01:00
Sebastien Tardif
49e9c3eb13 fix(agents): add openai-responses family to non-visible turn retry guard (#85603)
openai-codex-responses can return turns where usage.output > 0 but
assistantTexts is empty (hidden reasoning tokens only). The empty
response retry guard only covered openai-completions, anthropic-messages,
and Ollama, so these turns passed through as successful completions
with no content delivered to the user.

Add the full openai-responses API family (openai-responses,
openai-codex-responses, azure-openai-responses, and their transport
variants) to RETRY_GUARD_MODEL_APIS so the empty response and
reasoning-only retry paths can fire for these providers.

Closes #85364

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-23 15:50:01 +01:00
ItsOtherMauridian
6e289b4889 fix(status): show configured cost for aws-sdk models (#85619)
* fix(status): show configured cost for aws-sdk models

Decouple status cost display from provider auth mode so explicit model pricing is used for Bedrock and other non-api-key providers. Include cache read/write tokens in the status cost estimate and cover the behavior with regression tests.

* fix: show configured response usage costs

* docs: align configured cost visibility

* fix(status): keep usage tokens mode cost-free

---------

Co-authored-by: ItsOtherMauridian <165866613+ItsOtherMauridian@users.noreply.github.com>
Co-authored-by: ItsOtherMauridian <itsothermauridian@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 15:49:57 +01:00
Roslin Mahmud Joy
ec43acb432 fix(microsoft-foundry): DeepSeek V4 models incorrectly use openai-completions API (#85549)
When onboarding Microsoft Foundry-hosted DeepSeek-V4 models (Pro/Flash),
the onboarding wizard assigned api: 'openai-completions' because
usesFoundryResponsesByDefault() only matched GPT/o-series models.

These V4 models require the Responses API (openai-responses) to work
correctly against the Foundry endpoint. Without this fix, all calls fail
with 'provider rejected the request schema or tool payload'.

Fix: Add 'deepseek-v4' prefix to usesFoundryResponsesByDefault() so only
the verified V4 family defaults to openai-responses. Older DeepSeek
families (e.g., V3) remain on openai-completions until proven compatible.

Closes: DeepSeek V4 models deployed via Microsoft Foundry onboarding
failing immediately due to wrong API adapter.

Co-authored-by: Roslin <rmj010203@gmail.com>
2026-05-23 15:49:53 +01:00
ANIRUDDHA ADAK
74e65f4d85 fix(skills): show empty state notice in config wizard (#85032)
* fix(skills): show empty state notice when no dependencies to install

* fix(skills): gate empty dependency notice

* fix(skills): tighten all-ready dependency notice

---------

Co-authored-by: Aniruddha Adak <aniruddhaadak80@users.noreply.github.com>
Co-authored-by: Gio Della-Libera <giodl73@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 15:49:49 +01:00
Peter Steinberger
ef7e652ec4 test(codex): avoid forced-tool allowlist flake 2026-05-23 15:35:37 +01:00
Peter Steinberger
3e8fd4944f fix: avoid gateway startup event-loop stalls
Defer Gateway channel startup until after readiness, remove startup model prewarm, and move model catalog data onto manifest/static paths so startup no longer loads broad provider runtimes.

Verification:
- focused gateway/catalog/auth/QA Vitest runs
- autoreview clean
- Blacksmith Testbox-through-Crabbox tbx_01ksahn65rsrsqz3q1qyxwf929: pnpm check:changed, exit 0
- PR CI green on ee2b631c72
2026-05-23 15:08:58 +01:00
Peter Steinberger
f6ab188db0 test(codex): type forced-tool request mock 2026-05-23 15:07:49 +01:00
Peter Steinberger
8d1ab83cb3 test(codex): avoid forced-tool turn flake 2026-05-23 14:55:05 +01:00
Peter Steinberger
9ede882f08 test(codex): avoid startup cleanup socket flake 2026-05-23 14:32:13 +01:00
Nyx
2e5be0c7ff fix(gateway): pin relative state dir at startup
* fix(gateway): normalize explicit state dir overrides at startup

* test(gateway): simplify state-dir startup coverage

* test: fix state dir startup coverage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 14:30:32 +01:00
Peter Steinberger
b47bace014 fix(whatsapp): persist inbound delivery in plugin state (#85506)
* fix(whatsapp): persist inbound delivery in plugin state

* fix(whatsapp): type durable inbound payload

* fix(channels): close durable receive insert race

* fix(whatsapp): skip owned durable pending duplicates
2026-05-23 14:20:53 +01:00
Peter Steinberger
b4b2ef192d test(codex): make sandbox cleanup proof deterministic 2026-05-23 14:04:12 +01:00
Peter Steinberger
9175491906 fix(cron): route topic targets through channel plugins
Route cron announce topic target parsing through channel plugin target parsers instead of Telegram-specific cron core code. Keep supported Telegram topic forms in the Telegram plugin and document the channel-owned shorthand.
2026-05-23 13:52:06 +01:00
brokemac79
f4b92f5e6c fix(agents): simplify subagent completion handoff
Simplify native subagent completion handoff and remove manual subagent control surfaces.

Co-authored-by: brokemac79 <martin_cleary@yahoo.co.uk>
2026-05-23 13:50:08 +01:00
Peter Steinberger
2ad507c031 fix(release): allow large beta smoke run lists 2026-05-23 13:39:43 +01:00
Peter Steinberger
9c26b87114 ci(release): isolate npm publish concurrency 2026-05-23 13:39:43 +01:00
Peter Steinberger
0e3726305b ci(release): allow beta publish after npm preflight 2026-05-23 13:39:43 +01:00
Peter Steinberger
c689f71805 ci(release): retry child workflow polling 2026-05-23 13:39:43 +01:00
Peter Steinberger
e5dab55aca ci(release): poll child workflows through actions api 2026-05-23 13:39:43 +01:00
zhouhe-xydt
25fa46bd61 fix(bootstrap): guard bootstrap name checks against undefined names (#85523) (#85615)
* fix(bootstrap): guard bootstrap name checks against undefined names

Add optional chaining to isAgentsBootstrapFile and isAgentsBootstrapName
to prevent TypeError: Cannot read properties of undefined (reading 'toLowerCase')
when bootstrap file entries have undefined name properties.

This crash was observed in 2026.5.20 where a workspace bootstrap file entry
with an undefined name caused every incoming message to fail during bootstrap
context building, completely blocking all agent replies.

Fixes #85523

* test(agents): cover unnamed bootstrap truncation entries

* test(agents): keep bootstrap truncation fixture typed

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 13:37:03 +01:00
Mikael Goderdzishvili
eca9645365 fix(cli): waitForever must keep the event loop alive (#85694)
`waitForever()` is a public library export used by long-running embeds to
block until the host process is asked to exit. It called `interval.unref()`
on the keep-alive timer, which removes the timer from Node's active-handle
set. With no other ref'd handles, `await waitForever()` exits the process
in ~3ms with exit code 13 ("unsettled top-level await") instead of waiting.

Drop the `.unref()` so the interval actually keeps the loop alive, and
update the existing unit test (and comment) to lock in the new contract.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 13:36:56 +01:00
zhouhe-xydt
84229d995a fix(cli-output): ignore cumulative usage from result events in stream-json parser (#85573) (#85625)
* fix(cli-output): ignore cumulative usage from result events in stream-json parser

Claude-cli's stream-json result event reports cumulative cache_read across
all tool sub-calls, not the per-call value. The parser was overwriting the
last assistant-event usage with this inflated sum, causing sessionEntry.totalTokens
to climb 6-13x on tool-heavy turns and trip the preemptive-compaction gate.

Fix: skip reading usage from result events in createCliJsonlStreamingParser,
keeping the last per-call usage from assistant events instead.

Fixes #85573

* fix(agents): keep Claude result usage as fallback

* fix(agents): read Claude assistant stream usage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 13:36:51 +01:00
alkor2000
bb52b54134 fix(secrets): show irreversible warning after interactive apply confirmation (#85638)
Fixes #83883.

In `secrets configure`, the one-way-migration irreversibility warning was
computed from `opts.apply` (the original --apply flag) rather than
`shouldApply`. On the interactive path the user confirms "Apply this plan
now?", which sets shouldApply=true while opts.apply stays false, so the
warning was silently skipped and the irreversible plaintext migration was
applied without the second confirmation.

Derive the guard from shouldApply so the irreversibility warning fires on
both the --apply path and the interactive-confirm path. Adds regression
tests covering the interactive path (warning shown; declining it cancels
the apply).
2026-05-23 13:36:47 +01:00
Brian Potter
2c3b7eaa7e fix(agents/harness): pass CLI runtime aliases through to PI in selectAgentHarnessDecision (#85631)
* fix(agents/harness): pass CLI runtime aliases through to PI in selectAgentHarnessDecision

When a model defines `agentRuntime.id` as a CLI runtime alias
(`claude-cli`, `google-gemini-cli`) or a configured `cliBackends` id, the
explicit-non-`auto` branch of `selectAgentHarnessDecision` previously
threw `MissingAgentHarnessError` because the alias has no agent harness
plugin counterpart. Model dispatch is unaffected (the CLI-runtime
short-circuit in `assertModelFallbackCandidateHarnessAvailable` runs
first), but every non-dispatch caller — delivery-mirror metadata
lookups, lane preflight, channel projection — surfaces the throw. On
Slack `[[reply_to:]]` deliveries the warning text gets substituted into
the assistant message synthesized as `provider: openclaw,
model: gateway-injected`, poisoning the thread.

Mirror the existing implicit-codex escape hatch in the same function:
when the runtime is a CLI alias (`isCliRuntimeAlias`) or a configured
CLI backend (`isCliProvider`), return PI with the new
`selectedReason: "cli_runtime_passthrough_pi"`. Actual CLI dispatch is
already routed by callers that consult model runtime policy, so PI here
is just a transcript-composition placeholder — non-CLI typos still
throw as before.

Refs #85582.

* fix(agents): validate CLI harness aliases by provider

* fix(agents): keep custom CLI harness ids fail-closed

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 13:36:41 +01:00
Peter Steinberger
3c6bc5f0b0 docs: add bug-sweep changelog entries 2026-05-23 13:36:30 +01:00
Vincent Koc
1f32a4855a fix(release): run npm shims on Windows 2026-05-23 14:08:48 +02:00
Vincent Koc
0d7d99befa fix(ci): repair crabbox hydrate replay (#85706) 2026-05-23 20:02:07 +08:00
Daniel Marta
4ec85762ab feat(auth): support named model login profiles
* docs(auth): document named OAuth profile logins

* feat(auth): support --profile-id in models auth login

* docs: note named model login profiles

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 12:44:57 +01:00
NianJiu
55a0c9b1f4 fix(gateway): restore WebChat image understanding routing
Restores WebChat image uploads to the media-understanding flow without one-turn model overrides.

- removes image-model override plumbing from the reply run
- stages WebChat images as MediaPaths for enrichment
- avoids replaying already-understood images to text-only reply models while preserving undescribed images

Co-authored-by: NianJiuZst <3235467914@qq.com>
2026-05-23 12:31:43 +01:00
haoyu-haoyu
353dfeb108 fix(anthropic): migrate 1M context to GA handling
* feat(anthropic): migrate 1M context from beta to GA

Anthropic has graduated the 1M context window from beta to GA.
This commit:

- Stops injecting the context-1m-2025-08-07 beta header when
  context1m: true is configured
- Removes the OAuth token skip logic that was needed because
  Anthropic previously rejected the context-1m beta with OAuth auth
  (OAuth now supports 1M natively)
- Strips the legacy beta header from user-configured anthropicBeta
  arrays to prevent sending a stale header
- Removes the now-unused isAnthropic1MModel helper,
  ANTHROPIC_1M_MODEL_PREFIXES constant, and logger import from
  the stream wrappers

The context1m config param continues to be respected for context
window sizing in context.ts — only the beta header injection is
removed.

Closes #45550 (Phase 1)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(anthropic): migrate 1M context handling to GA

* fix(clownfish): address review for ghcrawl-156721-autonomous-smoke (1)

* fix(anthropic): restrict ga 1m context models

* docs(anthropic): align ga 1m context guidance

* fix(anthropic): normalize ga 1m model metadata

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 12:29:31 +01:00
Vincent Koc
5c535df0a2 fix(scripts): resolve Crabbox shims on Windows 2026-05-23 13:23:49 +02:00
Vincent Koc
68bcd4e39d test(ci): harden installer smoke coverage 2026-05-23 13:19:12 +02:00
Gio Della-Libera
f7c05dcc9e fix(status): bound deep docker audit probes (#85476)
* fix(status): bound deep docker audit probes

* chore(status): defer changelog entry to landing

* docs(changelog): note status docker probe timeout

* fix(status): surface Docker probe timeouts

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 12:08:48 +01:00
Peter Steinberger
a7e0fa08e7 docs: expand meeting notes docs 2026-05-23 11:58:29 +01:00
Jayesh Betala
44d470f7eb fix(cli): validate tasks audit limit (#84901)
* fix(cli): validate tasks audit limit

* docs(changelog): note tasks audit limit validation

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 11:56:42 +01:00
alkor2000
71ddc016a8 fix(twitch): preserve newer message handler during cleanup (#85425)
* fix(twitch): preserve newer message handler during cleanup

Fixes #83888.

`TwitchClientManager.onMessage` returns a cleanup closure that called
`messageHandlers.delete(key)` unconditionally. When a second onMessage()
for the same account replaced the handler, running the earlier cleanup
deleted the newer handler, leaving the account with no handler and
silently dropping all inbound messages.

Guard the delete with a referential check so the cleanup only removes
the handler it registered. Adds regression tests covering both the
stale-cleanup case (newer handler must survive) and the normal case
(current handler is still removed).

* fix(twitch): distinguish handler registrations

* fix(signal): avoid dangling test export name

* test(meeting-notes): use public sdk imports

* test(sdk): classify meeting-notes subpath

* fix(discord): keep channel entrypoint imports narrow

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-23 11:49:01 +01:00
Vincent Koc
1e21121021 fix(ci): require live docker credentials by resource 2026-05-23 12:39:02 +02:00
Lion0710
e0bafc588c fix(diagnostics): drop snake case otel ids (#72645)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-23 18:34:39 +08:00
Vincent Koc
3a1d4dd43f fix(ci): clear signal and docs guard blockers (#85693)
* fix(signal): use lint-safe test api export

* docs: avoid private key sentinel example
2026-05-23 18:27:12 +08:00
Vincent Koc
cc6c3728c7 fix(ci): require factory auth for droid live docker 2026-05-23 12:20:26 +02:00
Peter Steinberger
a4a1abbe30 fix: honor disabled synthetic auth lookup 2026-05-23 11:13:30 +01:00
Vincent Koc
4e34ac483c fix(scripts): repair live docker auth shellcheck 2026-05-23 11:58:16 +02:00
Vincent Koc
5db773fad8 fix(docker): avoid printing gateway token 2026-05-23 11:54:53 +02:00
Vincent Koc
6e3b3183dd fix(cli): keep logs follow on live gateway state
Use the passive backend Gateway client for implicit local logs reads, and route Linux follow-mode local RPC failures to a bounded/redacted active systemd journal fallback instead of stale configured-file logs.

Fixes #83656
Fixes #66841
2026-05-23 17:54:23 +08:00
Vincent Koc
15d9134fc6 fix(e2e): prefer x64 MinGit on Windows 2026-05-23 11:46:25 +02:00
Peter Steinberger
07694c639d feat: add meeting notes plugin
Adds source-only external meeting notes plugin, SDK source-provider contract, CLI access, date-sharded storage, and Discord voice source.
2026-05-23 10:38:09 +01:00
Peter Steinberger
9e55383c3f docs: update changelog for memory artifacts (#85060) (thanks @brokemac79) 2026-05-23 10:32:59 +01:00
brokemac79
e6288cab9a fix(memory): preserve sidecar capability hooks 2026-05-23 10:32:59 +01:00
brokemac79
aac1abeaff fix(memory-lancedb): expose public memory artifacts 2026-05-23 10:32:59 +01:00
Vincent Koc
6657b493e2 fix(e2e): scrub Windows update config on PowerShell 5.1 2026-05-23 11:24:36 +02:00
Peter Steinberger
2c536a8626 docs: absorb documentation PR sweep 2026-05-23 10:23:34 +01:00
Vincent Koc
6b04170167 fix(agents): stabilize Linux fallback tests 2026-05-23 11:10:18 +02:00
Vincent Koc
bcf756ce36 fix(codex): preserve native web search action metadata (#85378) 2026-05-23 17:06:01 +08:00
Peter Steinberger
492d656d74 test: refresh Codex prompt snapshots 2026-05-23 09:56:44 +01:00
Peter Steinberger
99a1107b61 docs: absorb hook and subagent guidance PRs 2026-05-23 09:47:37 +01:00
Vincent Koc
09dd051e78 fix(agents): audit tool policy blocks (#85673)
* fix(agents): audit tool policy blocks

* fix(agents): sanitize tool policy audit fields

* fix(agents): include matched tool policy rule

* fix(agents): bound matched tool policy rules
2026-05-23 16:43:29 +08:00
Peter Steinberger
d485464dbc ci: fix plugin npm bundled dependency install 2026-05-23 09:22:25 +01:00
Gaurav Prasad
558a05b6d0 feat(diagnostics): classify skill and tool usage (#80370)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-23 16:08:55 +08:00
Alex Knight
0b476b9bbb docs(skills): clarify control ui recording proof (#85568) 2026-05-23 17:56:17 +10:00
Vincent Koc
c29967bcc2 test(agents): repair main failure fixtures 2026-05-23 09:53:04 +02:00
samzong
4f0c902012 feat(diagnostics): trace gateway secret preparation (#83019)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-23 15:45:39 +08:00
Vincent Koc
7f05be041e fix(diagnostics): harden observability exports and smokes (#85371)
* test(diagnostics): widen observability smokes

* fix(diagnostics): sanitize observability exports

* docs(diagnostics): format otel export docs
2026-05-23 15:27:43 +08:00
Vincent Koc
0b2ab6c93c fix(stepfun): drop stale auth choice metadata 2026-05-23 09:12:38 +02:00
Vincent Koc
73c1e375e4 test(e2e): sample kitchen sink RSS on Windows 2026-05-23 09:07:31 +02:00
Vincent Koc
c298dfe013 test(plugins): fail gauntlet on load diagnostics 2026-05-23 08:55:44 +02:00
Vincent Koc
9ff1a4371f fix(build): preserve tsdown heap floor 2026-05-23 08:39:17 +02:00
Mason Huang
31c269f0ed fix(tools): honor config apiKey in media tool preflight (#85570)
Summary:
- The branch adds a config-aware tool auth helper, routes image/PDF/media generation preflight and list selection through it, threads `workspaceDir`, and adds focused regression tests plus a changelog entry.
- Reproducibility: yes. by source inspection. Current main gates affected media/PDF/generation preflight paths on env/profile auth while the runtime auth contract already accepts usable `models.providers.*.apiKey`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(tools): fall back to config apiKey in capability preflight
- PR branch already contained follow-up commit before automerge: fix(tools): honor config apiKey in media tool preflight
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8557…

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

Prepared head SHA: b8c9242d77
Review: https://github.com/openclaw/openclaw/pull/85570#issuecomment-4523770355

Co-authored-by: Mason Huang <masonxhuang@tencent.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: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-05-23 06:27:03 +00:00
Vincent Koc
b4f62c9afc fix(e2e): support macOS script wrappers 2026-05-23 08:23:31 +02:00
Vincent Koc
743fd4c9db fix(ci): scope changed shrinkwrap checks 2026-05-23 08:17:56 +02:00
github-actions[bot]
33df3be6ca chore(ui): refresh fa control ui locale 2026-05-23 05:41:25 +00:00
github-actions[bot]
908464bbe8 chore(ui): refresh nl control ui locale 2026-05-23 05:41:17 +00:00
github-actions[bot]
62b75f44e0 chore(ui): refresh vi control ui locale 2026-05-23 05:41:05 +00:00
github-actions[bot]
fc4ba31958 chore(ui): refresh th control ui locale 2026-05-23 05:40:51 +00:00
github-actions[bot]
5b1bdd1af8 chore(ui): refresh id control ui locale 2026-05-23 05:40:46 +00:00
github-actions[bot]
534d4b142e chore(ui): refresh pl control ui locale 2026-05-23 05:40:42 +00:00
github-actions[bot]
055c3bd6a5 chore(ui): refresh uk control ui locale 2026-05-23 05:40:21 +00:00
github-actions[bot]
89c5a68951 chore(ui): refresh ar control ui locale 2026-05-23 05:40:11 +00:00
github-actions[bot]
44ca805650 chore(ui): refresh it control ui locale 2026-05-23 05:40:09 +00:00
github-actions[bot]
933b53bf55 chore(ui): refresh tr control ui locale 2026-05-23 05:40:07 +00:00
github-actions[bot]
2240b0e77b chore(ui): refresh fr control ui locale 2026-05-23 05:39:41 +00:00
github-actions[bot]
5fa250b2ed chore(ui): refresh ko control ui locale 2026-05-23 05:39:36 +00:00
github-actions[bot]
f4ea401ccf chore(ui): refresh ja-JP control ui locale 2026-05-23 05:39:30 +00:00
github-actions[bot]
751dde052c chore(ui): refresh es control ui locale 2026-05-23 05:39:27 +00:00
github-actions[bot]
72a9b5b9bc chore(ui): refresh de control ui locale 2026-05-23 05:39:03 +00:00
github-actions[bot]
501b6e075a chore(ui): refresh pt-BR control ui locale 2026-05-23 05:39:00 +00:00
github-actions[bot]
58aa908660 chore(ui): refresh zh-CN control ui locale 2026-05-23 05:38:59 +00:00
github-actions[bot]
88dee79270 chore(ui): refresh zh-TW control ui locale 2026-05-23 05:38:50 +00:00
Kevin Lin
5656f687c1 Add Slack approval QA checkpoints (#85141)
* test: add slack approval qa checkpoints

* fix(slack): scope plugin approval session fallback

* ci(mantis): allow slack approval checkpoint dispatch

* ci(mantis): use on-demand aws slack desktops

* ci(mantis): run slack smoke from candidate checkout

* ci(mantis): pin aws ssh ingress to runner

* test(mantis): skip crabbox actions hydrate for slack desktop

* ci(mantis): use fresh pr checkout for slack desktop

* ci(mantis): start slack desktop smoke from source

* fix(mantis): use relative slack qa output dir

* test(mantis): surface slack smoke failure logs

* fix(mantis): write slack approval watcher script

* fix(mantis): accept successful slack qa metadata

* fix(mantis): tighten slack approval evidence

* fix(mantis): repair slack evidence manifest

* fix(mantis): render slack approval checkpoint proof

* fix(mantis): quote approval checkpoint renderer html

* fix(mantis): preserve slack approval failure artifacts

* fix(mantis): timeout silent slack desktop runs

* fix(mantis): keep slack desktop runs chatty

* fix(mantis): keep slack workflow harness trusted

* fix(qa-lab): make slack approval evidence robust

* fix(qa-lab): harden slack approval workflow proof

* test(qa-lab): surface slack approval diagnostics

* test(qa-lab): loosen slack approval readiness
2026-05-22 22:04:15 -07:00
Gio Della-Libera
d7a078f196 fix(agents): mirror internal ui message tool replies (#85564)
* fix(agents): mirror internal ui message tool replies

* test(tui): prove internal source reply rendering

* fix(agents): preserve source reply idempotency
2026-05-22 21:21:14 -07:00
Dallin Romney
463929d794 perf(whatsapp): narrow runtime setter entry (#85589) 2026-05-22 20:59:21 -07:00
Alex Knight
bb5abefcf5 fix: smooth chat focus mode layout
Collapse the focused chat chrome, suppress focused-mode header scroll churn, and seed the mock chat UI with enough history to exercise scrolling.
2026-05-23 13:52:02 +10:00
Josh Avant
b7450820a9 Fix Telegram missing harness spool poison (#85605)
* fix telegram spool missing harness poison

* docs changelog telegram spool poison
2026-05-22 20:45:44 -07:00
clawsweeper[bot]
679a46d01e fix(session): surface previous-transcript archive failures on /new rotation (#81984) (#85586)
Summary:
- Adds an optional archive-error callback for session transcript archiving, wires `/new` reset rotation to log previous-transcript archive failures, adds regression coverage, and updates the changelog.
- Reproducibility: yes. source-reproducible. Current main catches and ignores `archiveFileOnDisk` failures ins ... and the source PR proof exercises the same rename failure boundary with a real filesystem permission error.

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

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

Prepared head SHA: 9d5f4c0c70
Review: https://github.com/openclaw/openclaw/pull/85586#issuecomment-4523917139

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-23 03:27:48 +00:00
Gio Della-Libera
a94f3444a0 Policy: add agent workspace conformance checks (#85096)
* feat(policy): add agent workspace conformance

* chore(policy): refresh agent workspace checks

* fix(policy): require enabled sandbox for workspace policy

* fix(policy): align agent workspace evidence with runtime
2026-05-22 20:24:31 -07:00
Vincent Koc
2edd6e2462 fix(installer): fail failed Windows git builds 2026-05-23 11:08:04 +08:00
Josh Avant
e0405ecc9b fix(codex): honor node exec policy for native surfaces (#85534)
* fix(codex): honor node exec policy for native surfaces

* docs(changelog): note codex node exec policy fix

* fix(codex): keep node exec policy private
2026-05-22 20:01:29 -07:00
Vincent Koc
304ff68c79 fix(qa-lab): stabilize codex runtime parity fixtures 2026-05-23 10:16:22 +08:00
Dallin Romney
6b52dff22d fix(github): preserve sufficient proof against negative relabel (#85567) 2026-05-22 19:13:33 -07:00
joshavant
5ca734ff8a docs: add changelog for context pressure preflight 2026-05-22 18:46:04 -07:00
Jason (Json)
c08400ea7d Fix context pressure preflight for tool-heavy sessions (#85541)
* fix context pressure preflight for tool payloads

* fix codex rendered context preflight

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-22 18:39:37 -07:00
Gio Della-Libera
959b935f3d fix(doctor): classify codex asset notice as info (#85119) 2026-05-22 18:28:45 -07:00
Alex Knight
fe121632ba fix(ui): keep chat picker search current (#85547)
* fix(ui): keep chat picker search current

* test(ui): expand chat picker mock data

* test(openai): satisfy video buffer test typing

* fix(ui): avoid duplicate chat picker search on blur
2026-05-23 11:23:07 +10:00
Shakker
f022b056bd fix: preserve message-tool delivery evidence 2026-05-23 02:22:04 +01:00
Josh Avant
f2365053d3 fix(codex): add API key paste auth (#85533)
* fix codex api key auth paste

* changelog for codex api key auth

* support piped codex api key auth

* fix codex auth prompt validator type

* normalize pasted codex auth secrets

* honor codex auth profile type at runtime
2026-05-22 17:53:05 -07:00
Shakker
743caedb05 fix: satisfy openai video test typecheck 2026-05-23 01:30:16 +01:00
Shakker
6c3fcb8bfc fix: route openai video edits to edits endpoint 2026-05-23 01:27:06 +01:00
Syu
227b4bffee fix(qmd): normalize direct file collection paths (#65212)
* fix(qmd): normalize direct file collection paths

Port fix from PR #65212 to new package location.

When a QMD custom collection path config entry points directly to a file
instead of a directory, normalize into:
- path = parent directory
- pattern = exact filename

This ensures direct file targets are handled correctly regardless of any
user-supplied glob pattern.

Original commit: 3570aa55a7 (fix/flow-runs-legacy-migration)

* fix(qmd): escape direct file collection patterns

* fix(qmd): escape direct file collection masks
2026-05-22 17:16:53 -07:00
Vincent Koc
58e9628300 fix(testbox): preserve clean sparse checkouts 2026-05-23 07:51:55 +08:00
Gio Della-Libera
ad19dd8691 fix(ui): run ui script through junction paths (#85525)
* fix(ui): run ui script through junction paths

* test(ui): make junction direct-execution test portable

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-22 16:50:14 -07:00
Alex Knight
60582b671b docs: fix troubleshooting logs link (#85545) 2026-05-23 09:49:53 +10:00
Alex Knight
d69bcfd933 Revert chat session picker inline search (#85527)
* Revert "fix(ui): keep chat session search inline (#85490)"

This reverts commit 260145374f.

* fix(ui): clear applied chat picker search on empty input

* fix(ui): keep chat picker search current

* fix(ui): scope chat picker svg color fix
2026-05-23 09:39:17 +10:00
Shakker
efbf9f3d46 fix: retry guarded video downloads 2026-05-23 00:35:23 +01:00
Shakker
ed7d99aa0e fix: preserve guarded video operation cleanup 2026-05-23 00:35:23 +01:00
Shakker
31b5145594 fix: thread openai video request policy 2026-05-23 00:35:23 +01:00
Shakker
cc48c34f91 fix: honor openai video provider request network policy 2026-05-23 00:35:23 +01:00
joshavant
c1273342d3 docs(changelog): note heartbeat message-tool fix 2026-05-22 15:57:47 -07:00
Fermin Quant
951bbe67b0 fix: use fs-safe trash for agent delete (#84394) 2026-05-22 15:53:41 -07:00
Neerav Makwana
bd9c78f957 Fix heartbeat message-tool delivery policy (#85357)
* fix(heartbeat): honor message-tool delivery policy

Keep scheduled heartbeat turns aligned with group/channel message-tool visibility and model-specific runtime policy so internal tool errors remain private.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(heartbeat): cover delivery chat type inference

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-22 15:52:57 -07:00
Andy Ye
91d85e70c3 Scope config preflight note suppression (#84439) 2026-05-22 15:51:34 -07:00
Zhaocun Sun
1cd6dce075 fix(cron): document best-effort edit delivery mode (#84526) 2026-05-22 15:49:41 -07:00
Rohit
2e15830d02 fix(dreaming): open report cards from memory palace (#85144) 2026-05-22 15:48:16 -07:00
Luke
49ce171aa5 fix(openai-codex): preserve image input capability (#85393) 2026-05-22 15:46:30 -07:00
Josh Lehman
c52daa4cdf fix(codex): stabilize heartbeat dynamic tool schema (#84681) 2026-05-22 18:45:10 -04:00
Peter Steinberger
658be7f1c7 docs: absorb small documentation PRs 2026-05-22 23:42:51 +01:00
Alex Knight
13a4c57991 fix(scripts): preserve bws resolver parse errors (#85528) 2026-05-23 08:41:55 +10:00
狼哥
f2d4f9328c fix(telegram): honor outbound media max bytes (#83478) 2026-05-22 15:38:54 -07:00
Jackal Xin
1dd3b52cb7 fix(skills): restore executable bit on bundled whisper script + release-time check (#41351)
* Enforce executable shell scripts in bundled skills

* fix: format CONTRIBUTING.md (oxfmt trailing whitespace)

* fix: skip shell script executable check on Windows

Windows does not support Unix permission bits — chmod is a no-op and
statSync().mode never reports execute bits.  Skip the runtime check
and the corresponding tests on win32.

* style: restore contributing formatting

* chore(ci): refresh detect-secrets baseline

* fix(skills): mark video-frames frame script executable

* fix: revert unrelated CI/secrets changes from whisper chmod PR

* chore(ci): retrigger full PR checks

* test: annotate executable-bit regression suite

* test(tts): mock resolveModelAsync in summarizeText tests

* test(whatsapp): make append history test use stale timestamp

* test(models): tolerate registry loader option expansion

* docs: add changelog for bundled skill executable fix

* fix(config): allow partial Codex web search location

* Drop unrelated formatting from PR 41351

* Fix bundled plugin bridge source expectation

* test: restore bundled plugin bridge npm expectation

---------

Co-authored-by: xaeon2026 <xaeon2026@gmail.com>
Co-authored-by: Jackal Xin <jackal092927@users.noreply.github.com>
Co-authored-by: xaeon2026 <xaeon2026@users.noreply.github.com>
2026-05-22 15:37:15 -07:00
ZC
2d5bda9199 fix(google): print Gemini OAuth URL before browser launch (#71469) 2026-05-22 15:35:32 -07:00
Peter Steinberger
b3622beecb docs: absorb contributor documentation fixes
Co-authored-by: ayesha-aziz123 <moizs4644@gmail.com>
Co-authored-by: dishraters <dishraters@gmail.com>
Co-authored-by: hougangdev <devchain7890@gmail.com>
Co-authored-by: Brandon Lipman <brandon@offdeck.com>
2026-05-22 23:28:31 +01:00
Peter Steinberger
8f8638393e docs: tighten landable bug sweep gates 2026-05-22 23:21:35 +01:00
Dallin Romney
299ed80834 fix: reuse provider auth lookup facts (#85499)
* fix: reuse provider auth lookup facts

* test: update model auth mocks

* fix: scope synthetic auth registry lookup
2026-05-22 15:14:04 -07:00
Peter Steinberger
7e1237032b fix: keep session picker focus separate 2026-05-22 23:01:34 +01:00
clawsweeper[bot]
464ffc1003 feat: start onboarding for fresh CLI installs (#85519)
Summary:
- This PR routes bare `openclaw` to classic onboarding for missing, empty, or metadata-only configs; keeps aut ... cs/changelog/tests; and narrows a Docker E2E boundary-check exception for an existing source-checkout lane.
- Reproducibility: not applicable. this is a feature/default-routing PR rather than a bug report. The branch p ... ill includes a fresh-state terminal run reaching `OpenClaw setup` and tests for the relevant config states.

Automerge notes:
- PR branch already contained follow-up commit before automerge: feat: start onboarding for fresh CLI installs

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

Prepared head SHA: f4b2572f2e
Review: https://github.com/openclaw/openclaw/pull/85519#issuecomment-4522938004

Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@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-22 22:00:21 +00:00
Peter Steinberger
64d13c017a docs: refresh contributor docs
Co-authored-by: Quratulain-bilal <umayaimanshah@gmail.com>
Co-authored-by: Mariano Belinky <mbelinky@gmail.com>
Co-authored-by: tao <itaofe@gmail.com>
Co-authored-by: julian <julian@tencent.com>
Co-authored-by: xenouzik <xenouziq@gmail.com>
Co-authored-by: Olamiposi <56056759+posigit@users.noreply.github.com>
Co-authored-by: surlymochan <surlymo@apache.org>
Co-authored-by: Janaka A <contact@janaka.co.uk>
Co-authored-by: choiking <samsamuels1927@gmail.com>
2026-05-22 22:58:27 +01:00
Jason (Json)
84f6b5c7f8 fix(update): prepack npm git update specs
Prepack npm GitHub/git source update specs into temporary tarballs before the staged global npm install. Extends coverage to hosted GitHub HTTPS URLs without a `.git` suffix.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-22 22:58:16 +01:00
Peter Steinberger
7e16a50c7e fix: simplify chat session search 2026-05-22 22:54:21 +01:00
Peter Steinberger
0556958d82 fix: use native mac settings sidebar 2026-05-22 22:53:32 +01:00
Peter Steinberger
dd07fb400f chore: ignore antigravity cli state 2026-05-22 22:53:08 +01:00
Peter Steinberger
0622fb6d90 fix(media): replace Gemini CLI fallback with sandboxed Antigravity (#85518)
* fix(media): prefer antigravity over gemini cli fallback

* fix(media): pass antigravity workspace before prompt

* fix(media): keep antigravity prompt argument single-line

* fix(media): sandbox antigravity media fallback

* test(media): isolate antigravity cli override

* fix(media): isolate antigravity capability probe
2026-05-22 22:49:45 +01:00
Peter Steinberger
faad2b0a71 docs: add bugbash landing changelog entries 2026-05-22 22:34:49 +01:00
Zhaocun Sun
9b5c281a3a fix(diffs): continue after card hydration errors (#84775)
* fix(diffs): continue after card hydration errors

* fix(diffs): satisfy bundled extension lint

* fix(diffs): continue after card hydration errors

* fix(diffs): satisfy bundled extension lint

* fix(diffs): keep failed hydration controllers out

* fix(diffs): restore clean current-main diff
2026-05-22 22:33:54 +01:00
Sebastien Tardif
e008bc92c3 fix(proxy): add missing clientSocket error handler in CONNECT tunnel (#82444)
The CONNECT handler pipes clientSocket and upstreamSocket together but
only registers an error handler on upstreamSocket. If the client
disconnects abruptly (ECONNRESET), the unhandled error event on
clientSocket causes the Node process to crash.

Add a clientSocket error handler that logs the event and destroys the
upstream socket. Also change clientSocket.end() to clientSocket.destroy()
in the upstream error handler since destroy() is more appropriate for
error cleanup of piped sockets.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-22 22:33:46 +01:00
Sergio Cadavid
7134a95c90 fix(memory): report qmd workspace cwd probe failures (#63167) 2026-05-22 22:31:59 +01:00
luna system
bf1a22ced4 fix(agents): handle parallel tool call deltas in openai-completions stream (#82263)
* fix(agents): handle parallel tool call deltas in openai-completions stream

The OpenAI completions streaming parser tracked only a single
`currentBlock` for tool calls and ignored `toolCall.index`. When the
API sends multiple `delta.tool_calls` entries (e.g., parallel tool
call scaffolding from kimi-for-coding), the parser created a new
block for every entry with a differing `id`, spawning phantom tool
calls with empty names and misrouting arguments.

Replace single-block tracking with Maps keyed by `index` and `id`,
matching the correct logic already present in the bundled
`@earendil-works/pi-ai` dependency. This ensures parallel and
interleaved tool call deltas accumulate to the correct block.

Fixes phantom "unknown" tool calls and empty arguments on
parameterized tools for providers that emit multiple tool_call
indices in streaming deltas.

* fix(agents): finalize tool-call blocks in place to keep maps live

ClawSweeper review [P1]: finishCurrentBlock() and finishAllToolCallBlocks()
were creating new block objects and replacing them in output.content,
but toolCallBlocksByIndex / toolCallBlocksById still pointed at the old
objects. Later deltas for those indices would mutate detached blocks,
causing argument loss and incorrect contentIndex in stream events.

Fix by finalizing arguments in place on the existing block objects.

Add regression test for parallel tool calls with split indices:
- two tool-call slots introduced in one chunk
- argument deltas arriving for each index in subsequent chunks

* fix(agents): keep byte counters out of emitted tool-call blocks

ClawSweeper review [P2]: partialArgsBytes was being stored directly on
the tool-call block objects pushed into output.content, exposing parser
scratch state to emitted stream events and final transcripts.

Replace the inline field with a WeakMap keyed by block object, keeping
byte tracking internal to the parser without polluting the public block
shape.

* refactor(agents): extract ToolCallBlock type for map declarations

ClawSweeper review [P1]: NonNullable<typeof currentBlock> at the map
declaration point was unreliable because currentBlock is initialized to
null and flow-narrowed. Define a local ToolCallBlock alias and use it
for toolCallBlocksByIndex, toolCallBlocksById, and toolCallBlockBytes
to give the maps a precise, stable type.

* fix(agents): iterate typed tool-call map in finishAllToolCallBlocks

ClawSweeper review [P1]: output.content elements are typed as
Record<string, unknown>, so block.partialArgs remained unknown even
after checking block.type === "toolCall". Latest CI failed strict
type checking at parseStreamingJson(block.partialArgs).

Fix by iterating toolCallBlocksByIndex.values() instead — the Map
values are already typed as ToolCallBlock, so partialArgs is known
to be a string and parseStreamingJson compiles cleanly.
2026-05-22 22:31:52 +01:00
Dallin Romney
423f525438 test: align release validation package acceptance check (#85515) 2026-05-22 14:30:35 -07:00
Peter Steinberger
44d5330993 fix: recover stuck Codex compaction
- Restart the shared Codex app-server client when native server-side compaction times out.
- Retry native compaction once on the fresh app-server while preserving stale-thread cleanup only for `thread not found`.
- Add regression coverage and changelog entry for the preflight compaction recovery path.

Verification:
- `pnpm test extensions/codex/src/app-server/compact.test.ts`
- `env -u OPENCLAW_TESTBOX -u OPENCLAW_TESTBOX_REMOTE_RUN pnpm check:changed`
- `.agents/skills/autoreview/scripts/autoreview --mode local`

CI note: `build-artifacts` is red due inherited latest-main workflow/test drift, reproduced locally outside this PR diff and tracked in the pre-merge PR comment.
2026-05-22 22:30:06 +01:00
Alex Knight
8174bfc734 docs: require visual proof for control ui e2e (#85513) 2026-05-23 07:24:20 +10:00
Gio Della-Libera
dcc5e45b50 Policy: add gateway exposure checks (#81981)
* feat(policy): add gateway exposure conformance

* fix(policy): align custom bind exposure evidence
2026-05-22 14:18:01 -07:00
Peter Steinberger
dcfc7e58fa ci: unblock advisory Tideclaw alpha release checks 2026-05-22 22:09:18 +01:00
Vincent Koc
684a9b2e6e fix(installer): tolerate WSL UNC launch cwd 2026-05-23 04:59:08 +08:00
Peter Steinberger
bb5010b89a docs: absorb docs sweep
Co-authored-by: Kai <kai@itskai.dev>
Co-authored-by: Weihang <gwh7078@163.com>
Co-authored-by: Scott Long <longstoryscott@gmail.com>
Co-authored-by: moejaberr <mjaber@uoguelph.ca>
Co-authored-by: huihui0822 <109355071+huihui0822@users.noreply.github.com>
2026-05-22 21:52:01 +01:00
Peter Steinberger
60e3749de3 fix: cancel stale provider auth prewarms (#85503) 2026-05-22 21:51:43 +01:00
Dallin Romney
0a50cbdf34 Add TUI PTY integration coverage (#85485)
* test: add TUI PTY integration coverage

* test: stabilize TUI PTY CI

* test: speed up TUI PTY coverage

* test: bound TUI PTY local waits

* ci: keep TUI PTY gate fast

* test: route TUI PTY project in full suite

* ci: run TUI PTY on routing edits
2026-05-22 13:42:58 -07:00
Sebastien Tardif
7bc4a333aa fix(security): escape entry.id in HTML export to prevent attribute XSS (#83104)
* fix(security): escape entry.id in HTML export to prevent attribute XSS

Apply escapeHtmlAttr to entry.id in renderEntry and renderCopyLinkButton
to prevent attribute injection via crafted entry IDs in HTML exports.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* chore: remove proof helper scripts from branch

ClawSweeper P2: committed proof scripts can provide false-positive
validation. Proof output is in the PR body instead.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-22 21:27:14 +01:00
Logan Ye
76a025c2fd fix: guard openai-completions tool payload with supportsTools compat flag (#74738)
* fix: guard openai-completions tool payload with supportsTools compat flag (#74664)

* docs(changelog): note OpenAI completions tool compat fix

* test(agents): use real tool history fixtures
2026-05-22 21:27:06 +01:00
in-liberty420
995a02033d fix(slack): surface auth.test failure + normalize explicit-bot mention check (#85101)
When the Slack adapter's startup auth.test call fails (bad token,
transient error, etc.), the bot user id silently stays empty for the
life of the process. The downstream explicit-bot mention check is
`botUserId && mentionedUserIds.includes(botUserId)`, which always
returns false when botUserId is empty. The result is that explicit
<@bot> mentions are silently classified as non-mentions with no log
trace explaining why.

Changes:
- provider.ts: stop swallowing auth.test failures; emit a warn log at
  boot so the degraded state is observable. Empty user_id is treated
  as a failure too.
- prepare.ts + subteam-mentions.ts: export the existing normalizeSlackId
  helper and apply it to both sides of the explicit-bot equality check
  (and to the mentioned-ids list). Real Slack ids are already uppercase,
  so this is a no-op on healthy traffic, but it locks the invariant down
  and removes the asymmetry between collected ids and the ctx bot id.
- prepare.test.ts: add two regression tests pinning the exact symptom:
  positive case (botUserId set -> explicit_bot), negative case
  (botUserId='' -> not explicit_bot, mention_source not explicit_bot).

🤖 AI-assisted.

Co-authored-by: in-liberty420 <in-liberty420@users.noreply.github.com>
2026-05-22 21:26:59 +01:00
Peter Steinberger
4df34cb790 chore(release): bump version to 2026.5.22 2026-05-22 21:25:16 +01:00
Peter Steinberger
260145374f fix(ui): keep chat session search inline (#85490)
* fix(ui): keep chat session search inline

* fix(ui): tolerate partial chat session search state
2026-05-22 20:52:29 +01:00
Gio Della-Libera
c85feace54 Policy: add secret and auth conformance checks (#81974)
* feat(policy): add secrets auth conformance

* fix(policy): include sandbox ssh secret data

* fix(policy): complete secret input provenance

* fix(policy): cover media request secrets

* fix(policy): satisfy policy lint

* fix(policy): narrow secret conformance evidence

* fix(policy): cover request bearer token secrets
2026-05-22 12:48:14 -07:00
Sebastien Tardif
f75789f803 fix(delivery): log failDelivery errors instead of silently swallowing (#84449)
Replace empty .catch(() => {}) on two failDelivery calls with
log.warn() so delivery queue mark-failed errors leave a diagnostic
trail instead of being silently discarded.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-22 20:45:26 +01:00
Jayesh Betala
5c866a17d7 fix(cli): validate debug proxy numeric options (#84260) 2026-05-22 20:45:17 +01:00
Andy Tien
501e74ddf3 fix(daemon): use exit code instead of localized text for schtasks fallback (#85347)
* fix(daemon): use exit code instead of localized text for schtasks fallback

Problem:
- shouldFallbackToStartupEntry() only matched English/Spanish error messages
  ("access is denied" / "acceso denegado"), causing silent fallback failure
  on non-English Windows systems (Chinese, Japanese, French, German, etc.)

Fix:
- Replace regex matching with exit code check (params.code === 1)
- schtasks returns exit code 1 for access denied / generic failure
  regardless of system locale

Fixes: #85255

* test(daemon): cover localized schtasks fallback

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-22 20:43:40 +01:00
infracore
5c614de29a fix(auto-reply): enforce word boundary in slash command prefix match (#84634)
`parseSlashCommandActionArgs` used a naive `startsWith` against the
configured slash prefix. When a skill name shares a prefix with a
built-in command (e.g. a skill named `config-check` vs the built-in
`/config`), the longer name was captured by the shorter built-in
handler and surfaced as an invalid action:

  ⚠️  /config is disabled. Set commands.config=true to enable.

Any skill whose name starts with a built-in command prefix
(`config-*`, `debug-*`, `models-*`, etc.) was unreachable via slash
invocation from any channel.

Fix: after the prefix match, require that the next character is
whitespace, a colon, or end-of-string. Otherwise the prefix
collided with a longer command name and we return `no-match` so the
longer handler — or the skill router — gets a chance to claim it.

Adds a regression test file `commands-slash-parse.test.ts` covering:
- `/config-check <args>` returns null (the reported case)
- `/configfoo` (no separator) returns null
- `/modelsy` returns null for the `/models` prefix
- `/config:json` still matches (colon is a valid boundary)
- `/config show enabled` still parses cleanly (whitespace boundary)
- empty body still returns the default action

Fixes #84572.

Co-authored-by: infracore <infracore@users.noreply.github.com>
2026-05-22 20:42:22 +01:00
zhang-guiping
63545693a0 fix(message-tool): normalize send body aliases (#84102) 2026-05-22 20:38:08 +01:00
Peter Steinberger
e0fda55cf7 docs: absorb maintainer docs sweep
Co-authored-by: Bob Du <i@bobdu.cc>
Co-authored-by: alitariksahin <alitariksah@gmail.com>
Co-authored-by: Jefsky <hwj3344@hotmail.com>
Co-authored-by: Musaab Hasan <m9.3b@Hotmail.com>
Co-authored-by: Intern Dev <dev@wukongai.io>
Co-authored-by: majin.nathan <majin.nathan@bytedance.com>
2026-05-22 20:29:10 +01:00
Peter Steinberger
d946a02a13 fix(gateway): coalesce provider auth rewarms
Coalesce provider auth-state rewarms after auth-profile failures and include event-loop delay in provider auth warm logs.
2026-05-22 20:28:13 +01:00
狼哥
57178b188b docs(voyage): clarify API key setup (#81803) 2026-05-22 20:20:22 +01:00
吴杨帆
88f50e8cd1 docs(config): quote bracket config paths (#83058) 2026-05-22 20:20:10 +01:00
Riive
14b2b8ac48 docs: link Copilot model availability (#76252) 2026-05-22 20:19:52 +01:00
Peter Steinberger
9fae5f7697 test(telegram): await watchdog registration event 2026-05-22 20:15:53 +01:00
Peter Steinberger
4b63502279 ci: run binding command escape in release checks 2026-05-22 20:12:53 +01:00
Dallin Romney
b741ddb66f fix(tui): dismiss watchdog notice when response actually arrives (#77375)
* fix(tui): dismiss watchdog notice when response actually arrives

The streaming watchdog renders 'This response is taking longer than
expected. Send another message to continue.' after 30s without a chat
delta. If a delta or final then arrives — common for runs that are slow
but not stuck — the notice stays in the log alongside the recovered
response and contradicts what the user sees.

Track the notice by runId in the chat log via a new `addPendingSystem`
+ `dismissPendingSystem` pair (mirroring the existing pendingUsers
pattern) and dismiss it from `handleChatEvent` whenever any further chat
event for that run is processed. The watchdog's internal cleanup
(`activeChatRunId` reset, status idle, history reload) is unchanged.

Refs #67052, #69081 (closed). Prior attempt #69026 raised the threshold
and suppressed the notice entirely; this is the narrower fix that keeps
the warning useful for genuinely stuck runs.

* fix(tui): adapt pending notice to repeatable system entries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-22 20:02:36 +01:00
Peter Steinberger
d756e1c500 test: add docker proof for plugin binding command escape 2026-05-22 19:58:04 +01:00
Peter Steinberger
7c9127c94d test(telegram): wait for polling watchdog deterministically 2026-05-22 19:50:51 +01:00
Peter Steinberger
0241a6e7ae ci: skip pnpm auto repair in Crabbox shell 2026-05-22 19:47:16 +01:00
Sebastien Tardif
99e44f623e fix(gateway): add .catch() to SIGTERM/SIGUSR1 signal handlers (#83131)
The SIGTERM handler's fire-and-forget IIFE can reject if the graceful
drain or tunnel-teardown throws. Without a catch, this becomes an
unhandled promise rejection. Add .catch() that logs the error and
falls back to a hard stop request. Same treatment for SIGUSR1.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-22 19:47:09 +01:00
yozakura-ava
247e536fa6 fix: release cron runtime state after isolated runs (#85053)
* fix: release cron runtime state after isolated runs

After an isolated cron/subagent run completes, the prepared context retains
references to the full in-memory session store and the registered agent run
context. Over many runs, these retained objects accumulate -- heap snapshots
showed ~2.0 GiB from ~113k copies of the skill prompt string flowing through
skillsSnapshot.prompt -> session entry -> cronSession.store -> cron run context.

Changes:
- Add disposeCronRunContext() to runCronIsolatedAgentTurn's finally block
- Calls clearAgentRunContext(sessionId) to remove the run context from the
  global agent-events map
- Nulls cronSession.store to release the in-memory session registry copy
- Export clearAgentRunContext from run-execution.runtime.ts barrel
- The disposal is shallow O(1) -- no deep traversal, no hot-path disk writes
- Session persistence is unaffected (on-disk sessions.json is untouched)

The finally block guarantees cleanup on both success and error paths,
including timeout/abort scenarios.

Includes unit tests for clearAgentRunContext, store disposal, and
sweepStaleRunContexts.

* fix: remove duplicate storePath property in test fixture

* fix: remove unused clearAgentRunContext import from run-executor

* fix(cron): use initial sessionId for disposeCronRunContext in finally block

finalizeCronRun calls adoptCronRunSessionMetadata() which can rotate
sessionEntry.sessionId before the finally block runs. Capturing the
sessionId before the try block ensures clearAgentRunContext clears the
correct registered context instead of the potentially-rotated one.

Also removes unused imports (vi, beforeEach) from the runtime cleanup test.

* chore: trigger CI re-check for proof gate

* chore: retrigger CI proof gate

* test(cron): prove isolated run cleanup path

* fix(cron): keep shared run contexts active

* test(cron): avoid spreading typed-never fixture

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-22 19:46:58 +01:00
Sergio Cadavid
0c7220f5da fix(cron): suppress fatal error completion announce (#83724)
* fix(cron): suppress fatal error completion announce

* fix(cron): preserve cleanup for fatal announce suppression

* test(cron): avoid spreading typed-never announce fixture

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-22 19:46:54 +01:00
amittell
34c441c746 fix(exec): parse nested approval metadata in async followups (#72268)
* fix(exec): parse nested approval metadata in followups

(cherry picked from commit 10ff9b318e77cda3d65f40d59bbab0f4a3f59da8)

* docs(changelog): note exec approval nested-paren parser fix

* fix(exec): sanitize denied-reason literals in (...)-delimited approval messages

The exec-approval followup wire format is `Exec denied (gateway id=..., <deniedReason>): cmd`. The producer at `src/agents/bash-tools.exec-host-gateway.ts:606` was emitting `approval-timeout (allowlist-miss)`, which embedded literal parens inside the metadata segment and broke the metadata/body boundary for naive parsers. Switch the literal to a colon-separated form (`approval-timeout: allowlist-miss`) so the surrounding `(...)` delimiter stays unambiguous.

The Gateway node-event surface at `src/gateway/server-node-events.ts:734` interpolates an untrusted `obj.reason` into the same `Exec denied (node=..., <reason>)` format. Strip parens from that field before interpolation so a buggy or hostile node payload cannot smuggle metadata into the body slot.

The robust nested-paren parser already in `src/agents/exec-approval-result.ts` stays as defense in depth. Extend `exec-approval-result.test.ts` to cover the canonical colon-separated `deniedReason` and confirm `formatExecDeniedUserMessage` still maps it to the timeout copy.

* fix(exec): require gateway/node metadata source to reject spoofed approval wrappers

The exec-approval result parser previously accepted any string starting with
"Exec denied (..." or "Exec finished (..." as a structured approval wrapper.
Generic command stdout that happened to start with these tokens would be
classified as kind: "denied" or "finished", letting a tool's output spoof a
resolved-approval event in pi-embedded-subscribe.handlers.tools.ts:1173.

Reported by Aisle as CWE-841 (Improper Enforcement of Behavioral Workflow),
medium severity. The fix validates that the parenthesized metadata starts with
either "gateway id=" or "node=" — both prefixes are emitted by the legitimate
approval generators (bash-tools.exec-host-gateway.ts, bash-tools.exec-host-node.ts,
gateway/server-node-events.ts) and are unlikely to appear in arbitrary command
output. Inputs that fail this check now return kind: "other", which all callers
already handle as a no-op.

* fix(exec): keep sandbox_blocked classification for raw exec-denied messages

After the spoof-guard tightening of parseExecApprovalResultText, inputs that
lack a gateway/node-sourced metadata prefix (such as the synthetic
"exec denied (allowlist-miss):" string used in classifier tests) no longer
return kind: "denied" and therefore no longer trigger formatExecDeniedUserMessage,
so isSandboxBlockedErrorMessage stopped recognising them.

Add a direct \bexec denied\s*\( alternative to SANDBOX_BLOCKED_RE so the
classifier still treats any raw "exec denied (" prefix as sandbox-blocked,
independent of whether the parser accepts the surrounding wrapper. This keeps
classifyProviderRuntimeFailureKind's existing behavior for unstructured exec-
denied messages.
2026-05-22 19:46:49 +01:00
Peter Steinberger
7552634996 ci: share Crabbox hydrate pnpm store 2026-05-22 19:37:46 +01:00
Peter Steinberger
736e7de1ae chore(release): refresh plugin SDK baseline 2026-05-22 19:32:30 +01:00
Peter Steinberger
b6940b5dc4 ci(release): pass node pin to pnpm setup 2026-05-22 19:27:56 +01:00
Peter Steinberger
a26aba67a8 ci(release): harden node setup before pnpm cache 2026-05-22 19:27:56 +01:00
Peter Steinberger
b00d3065cf ci: use stable pnpm wrapper for Crabbox hydrate 2026-05-22 19:25:19 +01:00
Peter Steinberger
86b87df7e3 docs: refine maintainer docs sweep
Co-authored-by: Niels Kaspers <kaspersniels@gmail.com>
Co-authored-by: Zhaocun <zhaocunsun@gmail.com>
Co-authored-by: Henson <zccyman@163.com>
2026-05-22 19:22:40 +01:00
cassthebandit
bd04b1ea7c docs(memory): add guidance for action-sensitive memories (#82788) 2026-05-22 19:21:00 +01:00
oak
d012065ecf docs(feishu): add dynamicAgentCreation and per-user isolation docs (#82793)
Add documentation for the dynamicAgentCreation feature used to create
isolated agents per Feishu/Lark user. Covers:

- dynamicAgentCreation configuration fields (enabled, workspaceTemplate,
  agentDirTemplate, maxAgents)
- Automatic agent/workspace creation flow
- Session isolation with dmScope
- Template variables ({agentId}, {userId})
- Verification steps and example deployment

Refs: feature available since OpenClaw 2026.4.25+

Co-authored-by: li <li@lideMac-mini.local>
2026-05-22 19:20:46 +01:00
alexgduarte
ce5dcb0ab2 docs(secrets): clarify agent-readable plaintext boundary (#84574)
Co-authored-by: alexgduarte <24414784+alexgduarte@users.noreply.github.com>
2026-05-22 19:20:25 +01:00
Dr. Claw
bbbed264b6 docs(channels): document ackReactionScope for Slack & Telegram (DM gotcha) (#84233)
* docs(channels/slack,telegram): document ackReactionScope and its DM-excluding default

The Slack and Telegram channel docs documented `ackReaction` but not
`ackReactionScope`, even though the scope (defaulting to
`group-mentions`) silently excludes DMs. People who set `ackReaction`
and expect to see an emoji on DMs are surprised when nothing fires.

This adds:

- The resolution order for `ackReactionScope` (per-account → channel →
  `messages.ackReactionScope` → default `group-mentions`).
- The full list of scope values (`all`, `direct`, `group-all`,
  `group-mentions`, `off`/`none`).
- A Note callout flagging that the default does not react in DMs and
  that `messages.ackReactionScope` requires a gateway restart to take
  effect.
- A short JSON example for the common case (`ackReactionScope: "all"`).

Mirrors the structure already used in `docs/channels/matrix.md`.

Found while configuring Slack DMs to show `👀` ack reactions and
discovering that the docs covered the emoji but not the scope gate. AI-assisted.

* fixup: scope is messages-only for Slack & Telegram (not per-account)

Reviewer correctly noted that the Slack and Telegram runtimes only read
`cfg.messages?.ackReactionScope` and the per-account/per-channel
`ackReactionScope` keys don't exist in those schemas (only Discord and
Matrix support them). Drop the misleading resolution-order bullets and
document `messages.ackReactionScope` only.

Verified against:
- extensions/slack/src/monitor/provider.ts:243
- extensions/telegram/src/bot-core.ts:262
- src/config/types.slack.ts (no ackReactionScope in account schema)
- src/config/types.telegram.ts (no ackReactionScope in account schema)

Keeps the DM-default gotcha, the full enum, and the gateway-restart note,
which were the original value of the PR.

---------

Co-authored-by: Dr. Claw <drclaw-iq@users.noreply.github.com>
2026-05-22 19:20:10 +01:00
Peter Steinberger
a0702e195d build(pnpm): use packageManager as pnpm source
Recreated from #85108 because the original branch could not be updated by maintainers.

Preserves current-main pnpm install hardening while switching workflow pnpm setup to packageManager, and adds exact version-scoped release-age exclusions for already-locked packages that pnpm 11.2.2 audits during install.

Co-authored-by: Altay <altay@hey.com>
2026-05-22 19:17:43 +01:00
Peter Steinberger
f6840acc21 ci: export Crabbox hydrate pnpm layout 2026-05-22 19:16:33 +01:00
CodeReclaimers
6f416537ee fix(gateway): preserve fresh agent session state
Fixes #5369.

Preserve fresh session-store state when the agent handler observes a stale cached session entry, including model/provider overrides, send policy, delivery metadata, lifecycle timestamps, and fresh session rotations.

Co-authored-by: CodeReclaimers <github@codereclaimers.com>
2026-05-22 19:11:20 +01:00
Vincent Koc
77c3bdb3ca fix(gateway): attribute agent wait timeouts 2026-05-23 02:07:44 +08:00
Peter Steinberger
489ea84819 ci: keep Crabbox hydrate runs reusable 2026-05-22 19:02:52 +01:00
safrano9999
936dfaaac9 Speed up /models browse replies (#84735)
Summary:
- keep default `/models` browse replies on the bounded read-only catalog path
- share the browse catalog loading policy with Gateway model listing
- add helper coverage, preserve full catalog loading for `all` and provider wildcard views, and add the maintainer changelog entry

Verification:
- `node scripts/run-vitest.mjs run --config test/vitest/vitest.agents.config.ts src/agents/model-catalog-browse.test.ts`
- `node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/server-methods/models.test.ts`
- `node scripts/run-vitest.mjs run --config test/vitest/vitest.auto-reply-reply.config.ts src/auto-reply/reply/commands-models.test.ts src/auto-reply/reply/directive-handling.model.test.ts`
- `pnpm check:changed` via Blacksmith Testbox `tbx_01ks8bs93c60rjt4ayde91fnjq`
- autoreview clean: no accepted/actionable findings
- GitHub CI, CodeQL, CodeQL Critical Quality, OpenGrep, Workflow Sanity green on `107282aebc2aadde9a3c2acf0cb39fb84b55ade3` before latest changelog-only rebase

Co-authored-by: safrano9999 <240768512+safrano9999@users.noreply.github.com>
2026-05-22 18:56:15 +01:00
Peter Steinberger
9fc5346a97 docs: update changelog for plugin binding command escape (#85188) 2026-05-22 18:54:37 +01:00
Andy Ye
af12082ec8 Let binding commands escape plugin routes 2026-05-22 18:54:37 +01:00
Peter Steinberger
c9b17c5142 ci: fix Crabbox hydrate pnpm modules dir 2026-05-22 18:47:29 +01:00
Pavan Kumar Gondhi
10cb0a5ec0 Restore Control UI gateway token pairing [AI] (#85459)
* fix: restore control ui gateway token pairing

* docs: add changelog entry for PR merge
2026-05-22 23:13:32 +05:30
Peter Steinberger
5e97045345 fix(docker): accept single-object pnpm list output 2026-05-22 18:42:12 +01:00
Peter Steinberger
59aef2ff0d fix: apply docs sweep updates 2026-05-22 18:40:20 +01:00
Vincent Koc
769fd0b14a fix(update): roll back failed git updates 2026-05-23 01:37:39 +08:00
Peter Steinberger
9f1472ed8f test(docker): expect prod store seed command 2026-05-22 18:31:11 +01:00
狼哥
46de078b2a fix(agents): bound embedded compaction write locks
Fixes the embedded attempt session write-lock watchdog so the fallback max hold time follows the resolved compaction timeout plus the existing lock grace window, instead of inheriting the full run timeout.

Adds regression coverage for the helper and settled-compaction lock lifecycle, plus a changelog entry thanking @luoyanglang.

Verification:
- `pnpm test src/agents/session-write-lock.test.ts src/agents/pi-embedded-runner/run/attempt.test.ts src/agents/pi-embedded-runner/run/attempt.session-lock.test.ts`
- `pnpm check:changed` via Blacksmith Testbox `tbx_01ks8b6vn8se5cg1dfn3te3g47` / https://github.com/openclaw/openclaw/actions/runs/26301988670
- Autoreview clean: `/Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- PR CI green on `79e8c5f1a637981d263c0268bf5666967ff4e778`: https://github.com/openclaw/openclaw/actions/runs/26302152844 and https://github.com/openclaw/openclaw/actions/runs/26302152798

Co-authored-by: luoyanglang <hanwanlonga@gmail.com>
2026-05-22 18:30:38 +01:00
Peter Steinberger
de8a82a693 fix(update): repair managed npm plugin peers (#83794) (thanks @fuller-stack-dev) 2026-05-22 18:29:14 +01:00
FullerStackDev
571f364cd7 fix(update): repair managed npm plugin peers 2026-05-22 18:29:14 +01:00
Peter Steinberger
7fc691a426 fix(telegram): honor table mode in outbound chunks (#85455) 2026-05-22 18:26:04 +01:00
Peter Steinberger
d8b973638e fix(docker): precreate owned named volume targets (#85454) 2026-05-22 18:25:19 +01:00
Peter Steinberger
664611c1a5 fix(ui): strip ANSI from displayed gateway logs (#85453)
* fix(ui): strip ANSI from displayed gateway logs

* fix: reuse ansi stripping for ui logs
2026-05-22 18:24:22 +01:00
Peter Steinberger
9210dfc091 fix(skills): accept macos os requirement on darwin (#85451)
* fix(skills): accept macos os requirement on darwin

* fix: satisfy lint for macos os alias
2026-05-22 18:23:31 +01:00
Eva
87b2046575 fix(gateway): preserve message-tool replies in chat history
Preserve current-chat message.send replies in gateway history and live SSE refreshes, while keeping explicit routed sends out of the active chat.

Proof posted on the PR before merge: https://github.com/openclaw/openclaw/pull/84268#issuecomment-4521077098

Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
2026-05-22 18:18:18 +01:00
Peter Steinberger
9a816f41a9 test: track Docker prod store seed command 2026-05-22 18:17:36 +01:00
Peter Steinberger
d5247d0bfb fix: satisfy prod store package list lint 2026-05-22 18:13:13 +01:00
Vincent Koc
30333b2e0b test(plugins): clear lookup metadata memo 2026-05-22 19:10:10 +02:00
Peter Steinberger
6788aa1943 fix(docker): seed prod store before offline prune 2026-05-22 18:08:47 +01:00
Peter Steinberger
48bf0374c8 fix(memory): expand home paths in extra memory paths (#85449)
* fix(memory): expand home paths in extra memory paths

* build: refresh shrinkwrap metadata
2026-05-22 18:08:29 +01:00
Peter Steinberger
718cc1b9b6 docs: add security FAQ guidance
Co-authored-by: stevojarvisai-star <stevojarvisai@gmail.com>
2026-05-22 18:05:24 +01:00
Vincent Koc
67c56f34c6 chore(deps): refresh npm shrinkwraps
Refresh root and bundled plugin npm shrinkwraps so the shrinkwrap guard passes on main.
2026-05-23 01:02:18 +08:00
Peter Steinberger
5bb94caef8 docs: clarify OpenAI HTTP client guidance
Refs #52075.
Refs #54275.

Co-authored-by: Francisco <franciscopino1997@gmail.com>
Co-authored-by: 孔祥俊 <xiangjunkong90@gmail.com>
2026-05-22 18:00:53 +01:00
Peter Steinberger
00d3dcaa75 docs: remove stale showcase intro videos 2026-05-22 17:59:14 +01:00
Aman113114-IITD
6ab32bed5c fix(gateway): point model override error to config docs
Summary:
- Point allowModelOverride denial errors to the current configuration reference anchor.

Verification:
- Source check: docs/gateway/configuration-reference.md documents plugins.entries.<id>.subagent.allowModelOverride.
- PR CI: gateway tests and required shards succeeded.
2026-05-22 17:59:10 +01:00
Aman113114-IITD
a003960f26 docs: document secrets provider plan fields
Summary:
- Document providerUpserts and providerDeletes in secrets apply plans.

Verification:
- Source check: src/secrets/plan.ts validates providerUpserts/providerDeletes and src/secrets/apply.ts treats exec provider upserts as exec references.
- PR CI: check-docs succeeded.
2026-05-22 17:59:05 +01:00
Aman113114-IITD
c876fecbe7 docs: clarify media directive formatting
Summary:
- Document that MEDIA directives must be plain-text line-start metadata.

Verification:
- Source check: src/media/parse.ts only recognizes lines whose trimmed start begins with MEDIA: and skips fenced code blocks.
- PR CI: check-docs succeeded.
2026-05-22 17:59:01 +01:00
Aman113114-IITD
884aa1b2eb docs: align memory search cache default
Summary:
- Align memorySearch cache.enabled docs with current runtime default.

Verification:
- Source check: src/agents/memory-search.ts defines DEFAULT_CACHE_ENABLED = true and uses it when cache.enabled is unset.
- PR CI: check-docs succeeded.
2026-05-22 17:58:56 +01:00
Peter Steinberger
c94c513714 refactor(ios): centralize setup auth parsing
Centralizes iOS setup-code auth parsing so token/bootstrap/password trimming and the bootstrap-clears-stale-credentials rule live in one parsed value.

Verification:
- `git diff --check`
- `swiftformat --lint --config config/swiftformat --unexclude apps/ios/Sources apps/ios/Sources/Gateway/GatewayConnectionController.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Sources/Onboarding/OnboardingWizardView.swift apps/ios/Sources/Settings/SettingsTab.swift`
- `swiftlint lint --config apps/ios/.swiftlint.yml apps/ios/Sources/Gateway/GatewayConnectionController.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Sources/Onboarding/OnboardingWizardView.swift apps/ios/Sources/Settings/SettingsTab.swift`
- `AUTOREVIEW_AUTO_TESTS=0 .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- GitHub merge state clean for `fc35f31e95e73850a153149edaf471c10379dff2`
2026-05-22 17:54:53 +01:00
Peter Steinberger
a0358bbf18 test(release): wait for config reload log proof 2026-05-22 17:44:11 +01:00
Peter Steinberger
d93c59732b refactor(ios): consolidate manual auth override inputs
Consolidates repeated iOS manual-auth override assembly into `ManualAuthOverride.currentManualInput` and reuses the existing `normalized` constructor directly for setup-code pending auth state.

Verification:
- `git diff --check`
- `swiftformat --lint --config config/swiftformat --unexclude apps/ios/Sources apps/ios/Sources/Gateway/GatewayConnectionController.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Sources/Onboarding/OnboardingWizardView.swift apps/ios/Sources/Settings/SettingsTab.swift`
- `swiftlint lint --config apps/ios/.swiftlint.yml apps/ios/Sources/Gateway/GatewayConnectionController.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Sources/Onboarding/OnboardingWizardView.swift apps/ios/Sources/Settings/SettingsTab.swift`
- `AUTOREVIEW_AUTO_TESTS=0 .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- GitHub merge state clean for `cb6f3bcf8f121b570e22dcb8eed6dc9aaa89fc55`
2026-05-22 17:43:23 +01:00
Peter Steinberger
bb4d88e557 fix(ui): hide thinking options for non-reasoning models (#85406)
* fix(ui): hide thinking options for non-reasoning models

* test(ui): satisfy thinking selector lint

* chore(deps): refresh generated shrinkwraps

* test(ui): remove redundant thinking selector assertion
2026-05-22 17:35:33 +01:00
Peter Steinberger
a03a8d91f6 fix(ui): attach pasted data image text (#85392) 2026-05-22 17:35:14 +01:00
Peter Steinberger
d9c6c5f600 fix(gateway): preserve OpenAI usage aliases in chat history (#85383) 2026-05-22 17:34:24 +01:00
Colin Johnson
e730e9bd0b feat(ios): add realtime talk relay mode
Adds realtime Gateway Talk relay support for iOS, including OpenAI realtime provider selection and voice selection controls.

Maintainer fixups preserved provider auth fallback resolution, kept setup-code/manual auth through TLS trust prompts, recomputed pairing auth from current form fields, fixed the realtime voice label Swift compile issue, added provider auth regression coverage, and refreshed shrinkwrap metadata for the current CI merge base.

Verification:
- `fnm exec --using 24.15.0 pnpm deps:shrinkwrap:check`
- `git diff --check`
- `swiftformat --lint --config config/swiftformat --unexclude apps/ios/Sources apps/ios/Sources/Gateway/GatewayConnectionController.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Sources/Onboarding/OnboardingWizardView.swift apps/ios/Sources/Settings/SettingsTab.swift apps/ios/Sources/Voice/TalkModeGatewayConfig.swift`
- `swiftlint lint --config apps/ios/.swiftlint.yml apps/ios/Sources/Gateway/GatewayConnectionController.swift apps/ios/Sources/Onboarding/GatewayOnboardingView.swift apps/ios/Sources/Onboarding/OnboardingWizardView.swift apps/ios/Sources/Settings/SettingsTab.swift apps/ios/Sources/Voice/TalkModeGatewayConfig.swift`
- `AUTOREVIEW_AUTO_TESTS=0 .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- GitHub CI clean for `8a76c829611c0eb70d4c3b5328f1868aaf3516e1` (cancelled `auto-response` ignored)

Co-authored-by: Colin Johnson <colin@solvely.net>
2026-05-22 17:34:06 +01:00
Peter Steinberger
933f01cb39 fix(browser): hint WSL portproxy CDP empty replies (#85379)
* fix(browser): hint WSL portproxy CDP empty replies

* build: refresh shrinkwrap metadata after rebase
2026-05-22 17:33:58 +01:00
Vincent Koc
5b90a48e9d fix(installer): persist portable Git on Windows 2026-05-23 00:20:34 +08:00
Peter Steinberger
d22bcfc23a fix(opencode-go): strip Kimi reasoning replay fields (#85377) 2026-05-22 17:13:48 +01:00
Peter Steinberger
81d22c817d fix(build): normalize cache paths on Windows (#85437) 2026-05-22 17:05:44 +01:00
Vincent Koc
adc6adccd8 fix(update): detect nested macOS gateway ancestry (#85391)
* fix(update): detect nested macOS gateway ancestry

* fix(release): refresh shrinkwrap for CI npm

* fix(update): inherit gateway runtime pid for update guard
2026-05-23 00:00:38 +08:00
Peter Steinberger
faf2a6cb9e fix(docker): seed offline prune store in runtime stage 2026-05-22 16:58:12 +01:00
Vincent Koc
21bedd3964 fix(ci): stabilize npm shrinkwrap metadata 2026-05-22 17:50:22 +02:00
Vincent Koc
5cc0dbce86 fix(codex): route node exec through OpenClaw tools
Fixes https://github.com/openclaw/openclaw/issues/85012.\n\nSupersedes https://github.com/openclaw/openclaw/pull/85090 and closes out https://github.com/openclaw/openclaw/pull/83429 as the wrong direction.\n\nVerification before merge:\n- git diff --check origin/main\n- node scripts/run-vitest.mjs extensions/codex/src/app-server/run-attempt.test.ts\n- codex review --base origin/main\n\nNote: the GitHub Real behavior proof check on this maintainer PR was a maintainer bypass, not the live Linux gateway/container plus macOS node proof. User approved merge with this caveat preserved on the PR thread.
2026-05-22 23:43:24 +08:00
Vincent Koc
9364b21e51 test(installer): track portable node root helper 2026-05-22 17:24:59 +02:00
Peter Steinberger
99d7c7077e fix(ui): sync talk transcript translations 2026-05-22 16:19:29 +01:00
Vincent Koc
8fc48af091 fix(ui): localize talk transcript labels
Localize realtime talk transcript aria labels and regenerate Control UI i18n fallback metadata.
2026-05-22 23:12:02 +08:00
Peter Steinberger
cc91ff04cc fix(release): stabilize config restart QA 2026-05-22 15:53:50 +01:00
Peter Steinberger
e842869003 fix(installer): extract portable Node directly 2026-05-22 15:46:28 +01:00
Vincent Koc
dcd98bf1ef test(qa-lab): report scenario pack coverage 2026-05-22 22:35:31 +08:00
Vincent Koc
d70dc4be19 fix(plugins): drop stale tlon tool contract 2026-05-22 16:32:09 +02:00
Peter Steinberger
a54a8813bf fix(installer): prefer tar for portable Node extraction 2026-05-22 15:31:16 +01:00
Bryan P
f9d35dc681 fix(codex): deliver native subagent completions
Deliver Codex-native subagent completions through the generic plugin harness task runtime.

Proof:
- Autoreview clean on final branch.
- Testbox changed gate: tbx_01ks80eqs7d2e3jq3p99zbm4wd, pnpm check:changed, exit 0.
- Live Codex harness: tbx_01ks80p4ky32sqv2ksan2p0w0q, codex/gpt-5.5 API-key auth, native parent/child bridge tokens observed, exit 0.

Co-authored-by: bryanpearson <bryanmpearson@gmail.com>
2026-05-22 15:28:46 +01:00
Josh Lehman
cff5244a5b feat: add context-engine host capability requirements (#84994)
* feat(context-engine): add host capability requirements

* fix(context-engine): advertise pi host capabilities

* fix: repair incompatible context engine slots
2026-05-22 10:28:08 -04:00
Vincent Koc
9d24fde283 fix(release): keep shrinkwrap pinned to pnpm lock 2026-05-22 16:21:52 +02:00
Peter Steinberger
dc04503a7e fix: surface plan updates as status notices 2026-05-22 15:21:19 +01:00
Vincent Koc
fe7d13ca84 test(google): narrow web search fake timers
Narrow Google web search freshness tests to fake Date only.
2026-05-22 22:19:04 +08:00
Peter Steinberger
ffa6cd888f fix(installer): extract portable Node with ZipFile 2026-05-22 15:16:13 +01:00
clawsweeper[bot]
69255f8f32 fix(gateway): defer provider auth prewarm after startup (#85369)
Summary:
- The PR moves gateway provider auth-state prewarm into cancelable post-ready gateway lifetime work, uses current runtime config for delayed warms, and adds related gateway/provider-auth tests plus a changelog entry.
- Reproducibility: no. high-confidence runtime reproduction was run in this review. Source inspection shows th ... th on current main, and the source PR supplies live after-fix proof for the focused startup-ordering slice.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(gateway): defer provider auth prewarm after startup

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

Prepared head SHA: 31ea4288e3
Review: https://github.com/openclaw/openclaw/pull/85369#issuecomment-4519123491

Co-authored-by: Bob <dutifulbob@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: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-22 14:14:50 +00:00
VACInc
683ad75b31 fix(talk): stabilize realtime voice consults
Stabilize realtime Talk playback, transcript ordering, and consult routing across Android, Web, and the gateway relay.

- serialize Android realtime playback and transcript updates
- add opt-in forced consult routing for Talk realtime sessions
- keep web/gateway consult turns behind OpenClaw results with ordered transcript bubbles
- document the new `talk.realtime.consultRouting` config and keep prompt wording generic

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
2026-05-22 15:12:39 +01:00
Peter Steinberger
29118a0f0f test(qa): tolerate slow gateway rpc startup 2026-05-22 15:10:38 +01:00
Vincent Koc
ab684f5088 chore(diagnostics): refresh plugin sdk baseline 2026-05-22 22:01:41 +08:00
Vincent Koc
513195b462 fix(diagnostics): surface async queue drops 2026-05-22 22:01:41 +08:00
Vincent Koc
bdcaac06c6 fix(diagnostics): bound diagnostic buffers 2026-05-22 22:01:41 +08:00
Peter Steinberger
c21ca883b0 fix(installer): copy portable Node into place 2026-05-22 14:51:26 +01:00
Mason Huang
6ea907cec1 fix(cli): recover replaced device approvals (#85342)
Summary:
- The PR teaches `openclaw devices approve <requestId>` to approve a compatible same-device replacement request during local fallback and adds focused CLI, infra, and changelog coverage.
- Reproducibility: yes. Source inspection shows current main rejects the gateway's replacement requestId as a  ...  adds focused infra and CLI tests for the churn path; I did not run tests because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs: note device approval recovery

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

Prepared head SHA: 1d2f2e9b2f
Review: https://github.com/openclaw/openclaw/pull/85342#issuecomment-4518449317

Co-authored-by: masonxhuang <masonxhuang@tencent.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-05-22 13:44:15 +00:00
Peter Steinberger
0def3e20e4 test(release): align prerelease validation 2026-05-22 14:43:36 +01:00
Peter Steinberger
2890b1a24a fix(installer): install portable Node directory atomically 2026-05-22 14:36:50 +01:00
zhang-guiping
937a756f7f fix(runtime-llm): avoid duplicate provider prefix in allowlist diagnostics (#84946)
normalizeAllowedModelRef() and the resolved override ref interpolated
${provider}/${model} after normalizeModelRef(), so a provider-qualified
model id like openrouter/gpt-5.4-mini surfaced as
openrouter/openrouter/gpt-5.4-mini in the allowlist set and policy
denial message, masking the actionable model ref.

Route both sites through modelKey() (src/agents/model-ref-shared.ts)
so the provider segment is collapsed when the model id already starts
with it. Add regression tests covering allowlist hit and denial paths
for the OpenRouter shape.

Fixes #84887
2026-05-22 21:36:13 +08:00
Jayesh Betala
66d1d13889 fix(gateway): include openclaw bin in service PATH (#84475)
* fix(gateway): include openclaw bin in service PATH

* fix(doctor): accept expected service PATH

* docs(changelog): mention managed service PATH bin fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-22 21:28:50 +08:00
googlerest
ba86716999 fix(gateway): handle concurrent launchd bootstrap restart race (#84722)
* Handle concurrent launchd bootstrap restart

* docs(changelog): mention launchd bootstrap restart race fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-22 21:21:23 +08:00
Peter Steinberger
31a189db0a feat: support pi and opencode autoreview engines 2026-05-22 14:20:54 +01:00
Vincent Koc
52759294ca ci(package): gate acceptance on package integrity 2026-05-22 21:17:20 +08:00
Peter Steinberger
fea89cd384 ci(release): bypass pnpm for tsdown package build 2026-05-22 14:16:37 +01:00
Peter Steinberger
04ebdc6da5 test(release): align prerelease validation baselines 2026-05-22 14:15:46 +01:00
Peter Steinberger
7b1fbe1c37 ci(release): harden docker package build 2026-05-22 14:15:46 +01:00
Peter Steinberger
c3531fcd7b fix(codex): skip native web search transcript mirroring (#85346)
* fix(codex): skip native web search transcript mirroring

* test(codex): type transcript snapshot assertion
2026-05-22 14:14:01 +01:00
NianJiu
fc7a531f6c fix(gateway): harden launchd reload handoff race recovery (#84641)
* fix(gateway): harden launchd reload handoff race recovery

* docs(changelog): mention launchd reload handoff race fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-22 21:13:26 +08:00
Huvee
ca2b9ad289 fix: honor per-model provider transport overrides (#80488)
Summary:
- Honor per-model api/baseUrl overrides during custom provider auth hook lookup and transport selection.
- Keep models-add metadata safeguards intact and add focused auth/model resolver regression coverage.
- Add maintainer changelog credit for @huveewomg.

Verification:
- git diff --check
- GitHub CI green on 277629e992
- GitHub CodeQL green on 277629e992
- GitHub CodeQL Critical Quality green on 277629e992
- GitHub Real behavior proof green on 277629e992
- Local focused Vitest was stopped after 8 minutes on a busy host without producing a result; PR CI supplied the final proof.

Co-authored-by: huveewomg <wongrenthou1265@gmail.com>
2026-05-22 14:12:11 +01:00
openperf
19ff77e9c9 fix(skills): document watcher edge cases, add teardown/rebuild tests, add changelog 2026-05-22 14:10:56 +01:00
openperf
bb73f0a5c3 fix(skills): type watcher mock calls in dedupe regression tests 2026-05-22 14:10:56 +01:00
openperf
3e94290460 fix(skills): dedupe shared-directory watchers across agent workspaces (#84968) 2026-05-22 14:10:56 +01:00
Peter Steinberger
47d66fe343 fix(infra): allow macos browser open over ssh env (#85340) 2026-05-22 14:07:19 +01:00
Vincent Koc
a15797ad11 fix(update): preserve package service state during cutover (#83026)
* fix(update): preserve package service state during cutover

* docs(changelog): mention package service state cutover fix
2026-05-22 21:02:58 +08:00
Vincent Koc
07e61fc847 fix(gateway): broadcast agent-run error payloads (#85355) 2026-05-22 20:58:36 +08:00
Vincent Koc
a28f1297ab test(e2e): avoid synthetic channel config in plugin smoke 2026-05-22 14:55:10 +02:00
Peter Steinberger
a00c58363a fix(cli): suppress systemd hints for live gateway (#85336)
* fix(cli): suppress systemd hints for live gateway

* test(cli): type systemd hint mock
2026-05-22 13:52:21 +01:00
Peter Steinberger
fc47c1f55e fix(cli): honor agent for model auth logout (#85326) 2026-05-22 13:46:07 +01:00
Tung, Hsiao-Yu
4a9138556e fix(gateway): eager-load lifecycle runtime to survive in-place upgrades (#84890)
* fix(gateway): eager-load lifecycle runtime to survive in-place upgrades

After a package-swap update (e.g. via update.run), dist/ chunk hashes
rotate while the gateway is still running. The SIGUSR1 listener's first
dynamic import of the lifecycle runtime module then throws
ERR_MODULE_NOT_FOUND inside its async IIFE, silently rejects, and leaves
restart.ts's emittedRestartToken permanently unconsumed. From that point
every scheduleGatewaySigusr1Restart() — including the one update.run
schedules for itself — returns { coalesced: true } without scheduling
anything, and the gateway never restarts until manually kickstarted.

Fix:

1. Eagerly resolve the lifecycle runtime module as the first statement
   of runGatewayLoop, before any signal listener is installed. lifecycle.runtime
   is a 36-line re-export hub, so loading it once pulls the entire restart
   / respawn / queue / sentinel / handoff graph into memory, immune to
   later disk rotation. If the module is missing at startup, fail fast
   with a loud error so the supervisor can recover instead of running
   half-broken.

2. Defense in depth: catch SIGUSR1 IIFE rejections and call
   markGatewaySigusr1RestartHandled() via the eagerly captured reference,
   so a transient listener failure doesn't permanently stick the restart
   token.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(changelog): mention lifecycle restart eager load

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-22 20:44:05 +08:00
Peter Steinberger
111bad1065 fix(doctor): point codex asset warning at migrate plan (#85324) 2026-05-22 13:40:15 +01:00
Jason O'Neal
1bafc23ae3 fix(update): harden managed handoff cwd (#83875)
* fix(update): harden managed handoff cwd

* docs(changelog): mention managed update handoff cwd fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-22 20:35:17 +08:00
Peter Steinberger
e43282e701 docs(release): prepare 2026.5.21 notes 2026-05-22 13:30:19 +01:00
Peter Steinberger
f4bdfd46a9 ci(crabbox): harden docker hydration 2026-05-22 13:28:53 +01:00
Mason Huang
57db041365 refactor(crabbox): parse provider list from binary help instead of hardcoding (#85302)
Summary:
- The branch replaces the Crabbox wrapper's hardcoded provider allow-list with help-output parsing, preserves current aliases and a known help omission, adds wrapper tests, and updates the changelog.
- Reproducibility: yes. source-reproducible: current main only rejects selected providers that are already in  ... rovider names can bypass wrapper validation. I did not run the PR branch because this review was read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: refactor(crabbox): parse provider list from binary help instead of ha…
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8530…

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

Prepared head SHA: c99388d92a
Review: https://github.com/openclaw/openclaw/pull/85302#issuecomment-4517730136

Co-authored-by: masonxhuang <masonxhuang@tencent.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: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-05-22 12:25:15 +00:00
Vincent Koc
84329182a7 test(plugins): keep rpc source walk on source call gateway 2026-05-22 14:13:21 +02:00
Vincent Koc
01e7f64629 test(plugins): run kitchen sink rpc lane without tsx 2026-05-22 14:13:21 +02:00
Vincent Koc
6f6da5f5ba test(plugins): add kitchen sink rpc docker lane 2026-05-22 14:13:21 +02:00
Vincent Koc
2b396131e4 test(qa-lab): add bus tool trace scenario 2026-05-22 20:12:49 +08:00
Peter Steinberger
ebfb834dcd fix(cron): classify network retry errors (#85344) 2026-05-22 13:07:40 +01:00
Vincent Koc
3551e98433 fix(installer): bootstrap portable Windows Node 2026-05-22 19:59:45 +08:00
Alex Knight
1fdc73ae4b fix(ui): move chat session search into picker (#85303)
* fix(ui): move chat session search into picker

* fix(ui): smooth chat picker search controls

* test(ui): add mocked chat picker e2e harness
2026-05-22 21:57:00 +10:00
Peter Steinberger
84af5e6e76 fix: honor shrinkwrap when bundling plugin deps 2026-05-22 12:56:10 +01:00
Peter Steinberger
9914e25638 fix: opt acpx out of bundled runtime deps 2026-05-22 12:56:10 +01:00
Peter Steinberger
8b0537c409 test: refresh shrinkwrap after rebase 2026-05-22 12:56:10 +01:00
Peter Steinberger
fcecbd8655 fix: opt codex out of bundled runtime deps 2026-05-22 12:56:10 +01:00
Peter Steinberger
249f79be42 fix: make bundled plugin packages portable 2026-05-22 12:56:10 +01:00
Peter Steinberger
86faf654db fix: keep bundled plugin peers nested 2026-05-22 12:56:10 +01:00
Peter Steinberger
976da39038 fix: publish explicit plugin bundled dependencies 2026-05-22 12:56:10 +01:00
Peter Steinberger
3784270670 chore: refresh shrinkwrap metadata 2026-05-22 12:56:10 +01:00
Peter Steinberger
de022bb69d feat: bundle plugin npm dependencies 2026-05-22 12:56:10 +01:00
Vincent Koc
0d28040092 fix: honor overrides in npm shrinkwrap generation 2026-05-22 12:56:10 +01:00
Vincent Koc
bfa5b39648 fix: cover plugin package locks in dependency review 2026-05-22 12:56:10 +01:00
Vincent Koc
a1b05aae7c test: update shrinkwrap packaging expectations 2026-05-22 12:56:10 +01:00
Vincent Koc
82f69a269b fix: include plugin shrinkwraps in dependency reports 2026-05-22 12:56:10 +01:00
Vincent Koc
b2dc4492f0 chore: refresh shrinkwrap for Testbox npm 2026-05-22 12:56:10 +01:00
Peter Steinberger
b6c8807ca0 chore: add shrinkwrap to plugin npm packages 2026-05-22 12:56:10 +01:00
Peter Steinberger
c56067e34f chore: harden npm shrinkwrap release path 2026-05-22 12:56:10 +01:00
Eva
56308a7144 fix: limit subagent bootstrap defaults
Limit sub-agent bootstrap context to AGENTS.md and TOOLS.md without adding a new config surface. Preserve the existing cron minimal bootstrap behavior.

Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
2026-05-22 12:55:42 +01:00
Peter Steinberger
ab1fedb63f feat: update autoreview engine coverage 2026-05-22 12:38:15 +01:00
Peter Steinberger
89c59a89fb fix(agents): preserve OpenAI transport error metadata 2026-05-22 12:38:06 +01:00
Neerav Makwana
0a95e53602 fix(messages): strip unsupported citation markers (#85204) (thanks @neeravmakwana)
Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
2026-05-22 12:33:03 +01:00
Vincent Koc
fda0baf98d test(qa-lab): report live transport coverage lanes 2026-05-22 19:31:32 +08:00
Chunyue Wang
136c927140 fix(gateway): close child ACP sessions on parent reset/delete
Close child ACP runtimes during parent reset/delete through a shared direct-child session lookup, covering spawnedBy and parentSessionKey lineage across combined agent stores.

Also adds focused regression coverage for direct child discovery, non-ACP/unrelated negatives, reset cleanup, delete cleanup, cross-store children, and concurrent stuck-child cleanup.

Co-authored-by: openperf <16864032@qq.com>
2026-05-22 12:29:24 +01:00
clawsweeper[bot]
77a1b7625d fix: preserve Google Gemini 3 cron thinking (#85300)
Summary:
- The branch adds a Google provider thinking-policy resolver and opt-in profile flag, updates shared thinking validation and cron/proof-policy tests, and adjusts ClawSweeper proof parsing.
- Reproducibility: yes. source-reproducible: current main applies the generic off-only profile before provider ... figured thinking through that resolver. I did not execute a live systemd cron run in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: preserve Google Gemini 3 cron thinking

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

Prepared head SHA: a6cd2e826e
Review: https://github.com/openclaw/openclaw/pull/85300#issuecomment-4517662575

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.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-22 11:21:57 +00:00
Peter Steinberger
85e468d275 docs(skills): exclude SDK boundary bug sweeps 2026-05-22 12:17:02 +01:00
Peter Steinberger
c9a0f03dd7 feat(plugin-sdk): add generic channel poll sender (#85299)
* feat(plugin-sdk): add generic channel poll sender

* test(channels): follow durable capability list

* test(channels): update poll capability expectations

* fix(channels): normalize poll receipt parts
2026-05-22 12:16:07 +01:00
Peter Steinberger
0ddf51cf71 fix(agents): preserve OpenAI reasoning token usage 2026-05-22 12:14:12 +01:00
Vincent Koc
1a8625529e test(e2e): harden plugin smoke cleanup 2026-05-22 13:06:03 +02:00
Peter Steinberger
6b1c8687b5 fix(plugins): resolve native plugin sdk aliases (#85298)
* fix(plugins): resolve native plugin sdk aliases

* fix(plugins): satisfy native resolver lint

* fix(plugins): keep native sdk aliases on js artifacts
2026-05-22 12:05:37 +01:00
Vincent Koc
03f61cd1b5 fix(update): keep service logs out of json output 2026-05-22 12:46:00 +02:00
Vincent Koc
ff79299d68 fix(agent): retry transient gateway handshake closes 2026-05-22 12:24:50 +02:00
Krzysztof Probola
8523e0930e fix(codex): keep interrupted turns visible-answer eligible (#84494)
* fix(codex): keep interrupted turns visible-answer eligible

* docs(changelog): note codex interrupted recovery

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-22 18:22:20 +08:00
Vincent Koc
6bd430ee35 test(agents): narrow bundle mcp e2e setup 2026-05-22 11:39:24 +02:00
Alex Knight
e2f82d4d30 test: add mocked Control UI E2E tests and playwright for local verification and development (#85278)
* test: add control ui mocked e2e
2026-05-22 19:36:38 +10:00
Peter Steinberger
70dd31506b fix: land code-mode structured worker errors (#83444) (thanks @Kaspre) 2026-05-22 10:36:18 +01:00
Kaspre
edab653178 fix(code-mode): return structured worker error codes 2026-05-22 10:36:18 +01:00
Peter Steinberger
0d8c9ca914 fix: preserve route-bound direct thread events 2026-05-22 10:32:12 +01:00
Peter Steinberger
0acfb7ba13 fix: route direct thread event wakes to main DMs 2026-05-22 10:32:12 +01:00
Peter Steinberger
4ee8a2ac2e fix: break plugin metadata snapshot cycle 2026-05-22 10:32:12 +01:00
Peter Steinberger
7b489560f3 test: align exec event routing proof (#83743) (thanks @Kaspre) 2026-05-22 10:32:12 +01:00
Kaspre
37207c6925 test node exec event wake metadata 2026-05-22 10:32:12 +01:00
Kaspre
e53612a639 fix heartbeat event routing for main-scoped DMs 2026-05-22 10:32:12 +01:00
Vincent Koc
d24cfcfa21 test(plugins): retry bundled smoke health probes 2026-05-22 17:21:48 +08:00
Vincent Koc
2b1c01f769 test(gateway): bind auth-free websocket harness to loopback 2026-05-22 11:17:22 +02:00
Vincent Koc
a12e3022db test(plugins): keep npm peer prune mock directory-safe 2026-05-22 11:12:17 +02:00
github-actions[bot]
40b8dd88d8 chore(ui): refresh fa control ui locale 2026-05-22 09:11:58 +00:00
Vincent Koc
b859654641 fix(ci): allow release update restarts 2026-05-22 11:11:53 +02:00
github-actions[bot]
cc6d222ae3 chore(ui): refresh nl control ui locale 2026-05-22 09:11:46 +00:00
github-actions[bot]
b59ab5b1f0 chore(ui): refresh vi control ui locale 2026-05-22 09:11:42 +00:00
github-actions[bot]
f483f59b6c chore(ui): refresh th control ui locale 2026-05-22 09:11:27 +00:00
github-actions[bot]
c222ef01e9 chore(ui): refresh id control ui locale 2026-05-22 09:11:08 +00:00
github-actions[bot]
0050b8e89a chore(ui): refresh pl control ui locale 2026-05-22 09:11:05 +00:00
github-actions[bot]
6b4aec9fb9 chore(ui): refresh uk control ui locale 2026-05-22 09:11:02 +00:00
github-actions[bot]
940a950e47 chore(ui): refresh tr control ui locale 2026-05-22 09:10:27 +00:00
github-actions[bot]
d11c2e421d chore(ui): refresh ar control ui locale 2026-05-22 09:10:22 +00:00
github-actions[bot]
c99a29d0a8 chore(ui): refresh it control ui locale 2026-05-22 09:10:19 +00:00
github-actions[bot]
a7ba47c4ee chore(ui): refresh fr control ui locale 2026-05-22 09:10:13 +00:00
github-actions[bot]
a5fa944c69 chore(ui): refresh ko control ui locale 2026-05-22 09:09:46 +00:00
github-actions[bot]
f3a984dcbb chore(ui): refresh ja-JP control ui locale 2026-05-22 09:09:43 +00:00
github-actions[bot]
4711bb529a chore(ui): refresh es control ui locale 2026-05-22 09:09:40 +00:00
github-actions[bot]
4d6b3845f1 chore(ui): refresh zh-TW control ui locale 2026-05-22 09:09:30 +00:00
github-actions[bot]
d6fc2f34a3 chore(ui): refresh pt-BR control ui locale 2026-05-22 09:09:11 +00:00
github-actions[bot]
3222e35322 chore(ui): refresh zh-CN control ui locale 2026-05-22 09:09:05 +00:00
github-actions[bot]
ea5b5d78d5 chore(ui): refresh de control ui locale 2026-05-22 09:09:01 +00:00
Alex Knight
5d01be1070 Add chat picker search and pagination 2026-05-22 19:07:21 +10:00
Peter Steinberger
b3ec11b052 docs: add changelog for swept bugfix PRs 2026-05-22 10:00:35 +01:00
Vincent Koc
bf64de9191 fix(plugins): keep derived metadata snapshots fresh
Keep derived plugin metadata snapshots out of the process memo/current snapshot cache so newly added plugins under derived discovery paths are found without restart.
2026-05-22 17:00:09 +08:00
Vincent Koc
beccdde5bf fix(qa): isolate patched suite scenarios 2026-05-22 10:59:23 +02:00
吴杨帆
a80476fbe9 fix(telegram): preserve fenced code languages (#85209)
Co-authored-by: wuyangfan <yangfan.wu@succaiss.com>
2026-05-22 09:59:06 +01:00
Julyan
6f933656e5 fix: strip -plugin suffix in deriveIdHint to match manifest ids (#85170)
The deriveIdHint function already strips -provider from unscoped
package names (@openclaw/anthropic-provider -> anthropic) but does
not strip -plugin (@openclaw/xai-plugin -> xai-plugin instead of
xai). This causes ~30 spurious 'plugin id mismatch' warnings on
gateway startup for built-in plugins whose package names end in
-plugin.

Closes #85048
2026-05-22 09:58:56 +01:00
Sergio Cadavid
1b0a5d1627 fix(openai): preserve codex gpt-5.5 image input (#85095) 2026-05-22 09:58:43 +01:00
Noah
fb61de8c88 fix(gemini): strip sub-second precision from web_search time_range_filter (#85071)
* fix(gemini): strip sub-second precision from web_search time_range_filter

Gemini's google_search.time_range_filter rejects any non-zero fractional
seconds with "[FIELD_INVALID] Granularity of nano is not supported", even
though the underlying google.protobuf.Timestamp type accepts 0/3/6/9
fractional digits per its public spec. The grounding endpoint enforces a
stricter rule than the underlying type.

Date.prototype.toISOString() always emits millisecond precision, so every
freshness call (and any date_after/date_before call hitting the "now"
fallback for endTime) failed with the above 400 after #66498's fix shipped
in 2026.5.19.

Introduce toGeminiTimeRangeTimestamp() which strips the fractional-second
component before serializing, and route all four timeRangeFilter timestamp
sites through it. isoDateExclusiveEnd happens to produce all-zero
fractional today (so Gemini accepts it), but routing it through the helper
keeps the contract uniform and resilient to future changes.

Why this slipped past the original CI: the existing freshness test used
vi.setSystemTime(new Date("2026-04-15T12:00:00Z")), which always
serializes back as ".000Z" — the one fractional form Gemini happens to
accept. Wall-clock new Date() in production always has non-zero ms. The
new test uses setSystemTime(new Date("2026-04-15T12:00:00.123Z")) to
exercise the realistic case.

Verified empirically against the live Gemini REST API:
  ".123Z" → 400 "Granularity of nano is not supported"
  ".000Z" → grounded content (the one fractional form accepted)
  "Z"     → grounded content

Fixes #85061.

* test(gemini): use realistic non-zero ms in existing freshness test

The original test set the fake clock to a moment with zero fractional
seconds, so toISOString() produced ".000Z" — the one fractional form
Gemini's google_search.time_range_filter happens to accept. Wall-clock
new Date() in production produces non-zero ms, which Gemini rejects.

Bumping the fake time to .123Z makes the existing test exercise the
realistic case alongside the dedicated regression test.

---------

Co-authored-by: Noah R <Noerr@users.noreply.github.com>
2026-05-22 09:58:29 +01:00
Vincent Koc
9bd97d2c60 test(qa-lab): remove generic evidence wording 2026-05-22 16:54:04 +08:00
Vincent Koc
a9176e9190 fix(ci): reject embedded fallback release turns 2026-05-22 10:51:55 +02:00
Peter Steinberger
88ad5cb2f4 feat: update autoreview skill 2026-05-22 09:47:22 +01:00
Peter Steinberger
25e489395a docs: add changelog for code mode hook params (#83483) (thanks @Kaspre) 2026-05-22 09:46:27 +01:00
Kaspre
1e1e45b72b fix(code-mode): align outer exec hook params 2026-05-22 09:46:27 +01:00
Pavan Kumar Gondhi
ea5f2abb48 fix(integrations): enforce channel read target allowlists [AI] (#84982)
* fix: enforce message read target allowlists

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing codex review

* addressing ci

* addressing ci

* docs: add changelog entry for PR merge
2026-05-22 14:11:06 +05:30
Peter Steinberger
23961fe472 fix(codex): bound app-server client-close retries
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
2026-05-22 09:37:35 +01:00
Vincent Koc
0a4b30191d fix(ci): time packaged fresh release phases 2026-05-22 10:24:17 +02:00
Jason (Json)
37a9f58d1b Fix media completion duplicate delivery (#84006)
Summary:
- The PR changes generated-media duplicate guards, completion delivery fallback behavior, transcript write-lock reuse, task-registry fresh owner reads, docs, changelog, and regression coverage.
- Reproducibility: yes. with source and artifact evidence rather than a local rerun: current main completes me ... e task and one successful video task after the patch. I did not run tests because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: dedupe media completion delivery
- PR branch already contained follow-up commit before automerge: fix: avoid music provider lookup for explicit models
- PR branch already contained follow-up commit before automerge: fix: narrow detached media task handles
- PR branch already contained follow-up commit before automerge: fix: close media completion review gaps
- PR branch already contained follow-up commit before automerge: fix: tolerate media delivery mirrors during session lock
- PR branch already contained follow-up commit before automerge: Fix media completion duplicate delivery

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

Prepared head SHA: f83e3bf143
Review: https://github.com/openclaw/openclaw/pull/84006#issuecomment-4484835103

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@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-22 08:18:16 +00:00
Peter Steinberger
5ed8bbc694 fix(gateway): preserve stop reason for deferred agent aborts 2026-05-22 09:16:36 +01:00
Peter Steinberger
9ff3b9f4ef docs: update changelog for agent SIGTERM fix (#84381) 2026-05-22 09:16:36 +01:00
Kaspre
fd293bd2a7 test(gateway): use real dedupe map in abort helpers 2026-05-22 09:16:36 +01:00
Kaspre
01fce88082 fix(agent): abort accepted gateway runs on signal 2026-05-22 09:16:36 +01:00
Peter Steinberger
192a782b99 chore: add landable bug sweep skill 2026-05-22 09:08:48 +01:00
Vincent Koc
6981051682 fix(testing): ignore Crabbox metadata in changed lanes 2026-05-22 09:50:41 +02:00
Peter Steinberger
e201fbfbd2 fix: preserve xAI web search credential fallback (#85182) 2026-05-22 08:49:53 +01:00
fuller-stack-dev
ec8ed79646 fix: report oauth web search in onboarding summary 2026-05-22 08:49:53 +01:00
fuller-stack-dev
3b21a3f4c4 fix: pass active agent to web search execution 2026-05-22 08:49:53 +01:00
fuller-stack-dev
27088c6919 fix: use active agent auth for xai web search 2026-05-22 08:49:53 +01:00
fuller-stack-dev
65471a2da6 feat: add xai oauth web search and provider timeouts 2026-05-22 08:49:53 +01:00
samzong
014b527e23 fix: stop denied exec followups (#85194)
Stops denied exec approvals from feeding agent follow-up work, suppresses node `exec.denied` wakeups, adds Chinese stop phrases to abort handling, and documents terminal denial behavior.

Fixes #69386.

Co-authored-by: samzong <samzong.lu@gmail.com>
2026-05-22 08:48:19 +01:00
Peter Steinberger
b2a0bfab43 refactor(gateway): split connect assembly 2026-05-22 08:46:18 +01:00
samzong
0e47815e6e fix(gateway): surface connect assembly failures
Surface local post-challenge connect assembly failures immediately instead of waiting for the Gateway CLI wrapper timeout.\n\nCo-authored-by: samzong <samzong.lu@gmail.com>
2026-05-22 08:40:06 +01:00
Sarah Fortune
49e3f8c3ee fix(models) Discord model picker doesn't list all models (#85138)
* Add pagination to the discord model picker

* Ensure current model is shown as selected in the picker when its first loaded
2026-05-21 23:50:13 -07:00
WhatsSkiLL
170f72d5a1 fix(models): resolve set aliases from runtime config [AI-assisted] (#83262)
Summary:
- The branch passes runtime config into the model config write helper, updates `openclaw models set` to resolve aliases source-first then runtime-fallback, and adds regression tests plus a changelog entry.
- Reproducibility: yes. I did not execute the CLI in this read-only review, but the current-main source path a ... ing against source config while runtime defaults can be the only place the displayed `sonnet` alias exists.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(models): preserve authored aliases for set
- PR branch already contained follow-up commit before automerge: fix(models): resolve set aliases from runtime config [AI-assisted]

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

Prepared head SHA: 29138ac5d0
Review: https://github.com/openclaw/openclaw/pull/83262#issuecomment-4472495568

Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@users.noreply.github.com>
Co-authored-by: IWhatsskill <284122573+IWhatsskill@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-22 06:31:44 +00:00
clawsweeper[bot]
17e2ccf179 fix(exec): return approved WebChat gateway exec output inline (#85239)
Summary:
- The PR changes gateway exec approval handling so native WebChat approvals wait for the decision and return a ... al as the exec tool result, while preserving async follow-ups for diagnostics-direct and non-WebChat paths.
- Reproducibility: yes. Current-main source and tests show approval-required gateway exec returns approval-pen ... linked source PR provides live WebChat canary output showing the fixed inline result after native approval.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(exec): return approved WebChat gateway exec output inline

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

Prepared head SHA: 7182322015
Review: https://github.com/openclaw/openclaw/pull/85239#issuecomment-4515339946

Co-authored-by: Zac-W <wangzhifengzac@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-22 06:30:34 +00:00
Alex Knight
d0a74dbfbe fix codex memory flush tool surface (#85220)
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-22 16:23:25 +10:00
Sarah Fortune
b01a078d83 revert(models): drop auth-profiles.json fs watcher (#85244)
The watcher fired constantly because the gateway itself rewrites
auth-profiles.json frequently (cooldown ticks, usage stats, OAuth
refresh, markAuthProfileFailure). Each self-write triggered chokidar
which cleared the prepared auth map and scheduled an ~8 s rewarm,
a feedback loop that defeated the caching the rest of the PR added.

Drop the watcher entirely. Self-heal still covers the stale-TRUE
direction via the markAuthProfileFailure hook. Stale-FALSE (user adds
auth externally and the gateway hasn't observed any request through
that profile yet) reverts to the pre-PR behavior: reload config or
restart gateway to pick it up. Known limitation.
2026-05-21 23:11:50 -07:00
Andy Ye
03125c8e13 Validate Codex app-server command overrides (#84417)
Summary:
- The PR rejects Codex app-server command overrides that embed Node/package-manager inline arguments, adds matching doctor diagnostics, regression tests, and a changelog entry.
- Reproducibility: yes. for the scoped malformed override path: current main passes the combined command strin ... ix resolver/doctor live output. I did not establish a live Windows npm-global managed-startup reproduction.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Validate Codex app-server command overrides

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

Prepared head SHA: 966bcd6617
Review: https://github.com/openclaw/openclaw/pull/84417#issuecomment-4494295224

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-22 05:39:02 +00:00
Sarah Fortune
62a330e752 perf(models): make provider auth checks non-blocking
Make provider-auth checks asynchronous so catalog and model-listing sweeps yield between slow auth discovery calls.
2026-05-21 22:37:32 -07:00
Alex Knight
cc4e30b3d9 fix(codex): extend message tool timeout (#85216)
* fix(codex): extend message tool timeout

---------

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-22 15:36:25 +10:00
Kaspre
e32e0f3f7f fix(channels): pass allowBootstrap from channel-selection so in-agent message tool resolves channels in --local processes (#85022)
Summary:
- The branch passes `allowBootstrap: true` through outbound channel selection, preserves bundled-plugin resolution before bootstrap, adds focused regression tests, and documents the fix in the changelog.
- Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main omits `allow ... run the live current-main failure, but the supplied after-fix terminal proof exercises the implicated path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(channels): cover bootstrap channel selection
- PR branch already contained follow-up commit before automerge: fix(channels): avoid unnecessary bootstrap during message sends
- PR branch already contained follow-up commit before automerge: fix(channels): pass allowBootstrap from channel-selection so in-agent…

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

Prepared head SHA: 44099a80e8
Review: https://github.com/openclaw/openclaw/pull/85022#issuecomment-4510333662

Co-authored-by: Kaspre <kaspre@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.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-22 05:20:15 +00:00
Alex Knight
6a3377255d [codex] add color mode tooltips (#85227)
* fix(ui): add color mode tooltips

* docs: update changelog for color mode tooltips

* docs: credit changelog contributor

---------

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-22 15:15:20 +10:00
Alex Knight
8df350030d fix(ui): show all configured chat picker sessions
Remove the chat picker recency/current-agent filters while preserving the bounded configured-agent refresh, and add the changelog credit for @amknight.
2026-05-22 15:14:55 +10:00
Alex Knight
b7356e4e58 fix(ui): clarify inherited thinking off label (#85223)
Control UI thinking selectors now show inherited disabled reasoning as Inherited: Off while keeping explicit Off distinct.
2026-05-22 15:14:30 +10:00
Josh Avant
b010852dc6 fix(telegram): dedupe replayed message dispatches (#85208)
* Fix Telegram dispatch replay dedupe

* Add changelog for Telegram dispatch dedupe

* Persist Telegram replay dedupe at dispatch start
2026-05-21 22:14:16 -07:00
Jason (Json)
cd1cae5be9 fix(auto-reply): preserve sessions after compaction failures (#70479)
Summary:
- The PR removes the auto-reply compaction-failure session reset hook, adds preserved-session recovery guidance for overflow/compaction failure paths, and updates focused tests, docs, and the changelog.
- Reproducibility: yes. at source level with high confidence. Current main routes both embedded overflow paylo ... resetSessionAfterCompactionFailure, and the PR body includes before/after terminal proof of those branches.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(auto-reply): drop dead compaction reset hook
- PR branch already contained follow-up commit before automerge: fix(auto-reply): preserve sessions after compaction failures

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

Prepared head SHA: 193d3c0fdd
Review: https://github.com/openclaw/openclaw/pull/70479#issuecomment-4325128777

Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@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-22 05:04:41 +00:00
Galin Iliev
93c613cec4 fix: drop unsafe Copilot reasoning replay IDs 2026-05-22 05:02:27 +00:00
Sarah Fortune
55cfe00a3a fix(models): handle watcher errors, close on shutdown, rewarm after invalidate
Addresses three ClawSweeper findings on the fs-watcher commit:

- [P1] auth-profile watcher now handles chokidar 'error' events (logs +
  closes once) mirroring the gateway config-reload pattern. Without
  this, an unhandled error from chokidar can crash the gateway.

- [P2] auth-profile watcher handle is pushed into postReadySidecars so
  stopPostReadySidecarsAfterCloseStarted closes it on gateway shutdown.

- [P2] auth-failure and file-change invalidation paths now schedule a
  background rewarm (with a 'reason=' log line). Without this, the next
  /models call after an invalidation paid the slow per-provider path
  until the next reload. The warmer's existing generation counter
  handles concurrent rewarms safely.
2026-05-21 21:52:21 -07:00
Sarah Fortune
06a6d2b5c9 fix(models): watch auth-profiles.json so externally added creds become visible
Adds a chokidar watcher on every configured agent's auth-profiles.json.
Any change fires clearCurrentProviderAuthState so the next model-listing
call recomputes against the on-disk auth state. Closes the stale-FALSE
direction (user adds auth via codex login, hand-edit, etc.) that the
auth-failure hook can't catch on its own.
2026-05-21 21:52:21 -07:00
Sarah Fortune
a1bdffc212 test(auth-profiles): cover self-heal hook firing + survives hook errors 2026-05-21 21:52:21 -07:00
Sarah Fortune
ab265dbce9 fix(models): log auth-profile failure hook errors instead of swallowing them 2026-05-21 21:52:21 -07:00
Sarah Fortune
a483f70a8a fix(models): self-heal prepared auth on auth-profile failure
When markAuthProfileFailure observes an auth failure at request time
(token rotated, OAuth revoke, etc.), fire a hook that clears the
prepared provider-auth map so the next model-listing call recomputes
against the real auth state. Single mutable hook slot wired up at
gateway startup; no TTL or polling.

Addresses ClawSweeper's P1 freshness finding on #85125 without
reintroducing the TTL.
2026-05-21 21:52:21 -07:00
Sarah Fortune
95343affbb Remove ttl on auth config. Prewarm prepared config for each agent. Key by agent ID instead of agent dir 2026-05-21 21:52:21 -07:00
Kevin Lin
1008b8213b fix(slack): keep approvals in app conversation threads
* fix(slack): keep plugin approvals in app conversation threads

* fix(slack): preserve plugin approval routing

* fix(slack): keep suppression typing aligned

* fix(slack): suppress native dm approval fallback

* fix(slack): suppress stored native approval fallback
2026-05-21 21:51:34 -07:00
Pavan Kumar Gondhi
229490a489 fix: constrain Windows task script names [AI] (#85064)
* fix: validate windows task script file names

* addressing ci

* docs: add changelog entry for PR merge
2026-05-22 09:46:52 +05:30
Kaspre
5f0bec4479 fix(agent): await local agent_end hooks (#85007)
Summary:
- The PR adds an awaited `agent_end` helper, uses it for no-channel local CLI and Codex app-server terminal pa ... erves fire-and-forget behavior for channel-backed paths, and updates docs, changelog, and regression tests.
- Reproducibility: yes. by source inspection. Current main calls `runAgentHarnessAgentEndHook` without awaiting in local CLI and Codex terminal paths, and the PR's pending-hook tests encode the failure and desired split.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): await local agent_end hooks
- PR branch already contained follow-up commit before automerge: test: fix agent_end hook helper fixture
- PR branch already contained follow-up commit before automerge: ci: retry security checkout
- PR branch already contained follow-up commit before automerge: ci: allowlist qa-lab lifecycle fixtures
- PR branch already contained follow-up commit before automerge: fix CLI channel agent_end delivery
- PR branch already contained follow-up commit before automerge: ci: drop stale qa-lab deadcode entries

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

Prepared head SHA: 97b31379d7
Review: https://github.com/openclaw/openclaw/pull/85007#issuecomment-4509911851

Co-authored-by: Kaspre <kaspre@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-22 04:12:22 +00:00
WhatsSkiLL
7dc2e50ac3 fix(channels): bypass debounce for bare abort triggers [AI-assisted] (#83348)
Summary:
- The PR changes shared, Feishu, Mattermost, Microsoft Teams, and WhatsApp inbound debounce predicates so bare abort text bypasses debounce, then adds focused tests and a changelog entry.
- Reproducibility: yes. source-level. Current main sends bare `stop`, `abort`, and `wait` through a `hasContro ... ()` debounce gate, while the existing abort-aware detector and trigger set already recognize those phrases.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(channels): bypass debounce for bare abort triggers [AI-assisted]
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8334…

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

Prepared head SHA: c96bf84270
Review: https://github.com/openclaw/openclaw/pull/83348#issuecomment-4473176095

Co-authored-by: IWhatsskill <284122573+IWhatsskill@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-22 04:09:33 +00:00
吴杨帆
e399a92e6c fix(anthropic): preserve unsafe integer tool inputs (#83063)
* fix(anthropic): preserve unsafe integer tool inputs

Fixes #47229

* docs: add Anthropic unsafe integer changelog

* fix: narrow Anthropic partial JSON type

---------

Co-authored-by: Alex Knight <aknight@atlassian.com>
2026-05-22 13:48:38 +10:00
WhatsSkiLL
36e76ef424 fix(codex): block progress-only completions [AI-assisted] (#85110)
Summary:
- The PR adds shared required-completion classification for ACP/subagent finalization, marks missing, progress-only, and delivery-exhausted completions as blocked, and adds regression tests plus a changelog entry.
- Reproducibility: yes. source-reproducible. Current main finalizes the implicated ACP and subagent success pa ... he linked issue supplies production-shaped evidence; this read-only pass did not run a live provider repro.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(codex): preserve final completions after progress
- PR branch already contained follow-up commit before automerge: fix(codex): accept progress-prefixed final completions
- PR branch already contained follow-up commit before automerge: fix(codex): accept separator-delimited completions
- PR branch already contained follow-up commit before automerge: fix(codex): keep follow-up planning blocked
- PR branch already contained follow-up commit before automerge: fix(codex): block progress-only completions [AI-assisted]

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

Prepared head SHA: 21a1159165
Review: https://github.com/openclaw/openclaw/pull/85110#issuecomment-4513104331

Co-authored-by: IWhatsskill <284122573+IWhatsskill@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-22 03:43:28 +00:00
NVIDIAN
ddd3d69b86 fix(codex): unsubscribe app-server thread after runs (#84969)
Co-authored-by: ai-hpc <mail.speedy.hpc@hotmail.com>
2026-05-22 04:39:35 +01:00
Bob
ae4806ed9a feat(plugins): add embedding provider contract (#84947)
Summary:
- Merged feat(plugins): add embedding provider contract after ClawSweeper review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: chore(plugins): refresh embedding provider sdk baseline
- PR branch already contained follow-up commit before automerge: docs(plugins): document embedding provider contract
- PR branch already contained follow-up commit before automerge: fix(plugins): restore embedding providers after snapshot loads
- PR branch already contained follow-up commit before automerge: fix(plugins): resolve embedding providers from manifests
- PR branch already contained follow-up commit before automerge: fix(plugin-sdk): keep embedding provider registry mutators internal
- PR branch already contained follow-up commit before automerge: chore(plugin-sdk): refresh embedding provider API baseline

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

Prepared head SHA: 41ebd66ab4
Review: https://github.com/openclaw/openclaw/pull/84947#issuecomment-4514762026

Co-authored-by: Bob <dutifulbob@gmail.com>
Co-authored-by: Mariano Belinky <mbelinky@gmail.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-22 03:36:51 +00:00
WhatsSkiLL
0a4de3de57 [AI-assisted] fix(reply): wait for block replies before tools (#83722)
Summary:
- The branch adds an abort-aware dispatcher-idle wait after successful same-channel and direct ACP block replies, plus regression tests and a changelog entry.
- Reproducibility: yes. Current main source shows the same-channel block callback queues dispatcher delivery w ... spatcher idle, and the PR body supplies before/after diagnostic output for the tool-start ordering failure.

Automerge notes:
- PR branch already contained follow-up commit before automerge: [AI-assisted] fix(reply): wait for block replies before tools

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

Prepared head SHA: 32576209a2
Review: https://github.com/openclaw/openclaw/pull/83722#issuecomment-4480639845

Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@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-22 03:32:45 +00:00
Kaspre
eb7f3b7b50 fix(agent): support explicit CLI session keys (#85121)
Summary:
- The PR adds `openclaw agent --session-key`, normalizes explicit session keys through Gateway and embedded agent execution, and updates docs, tests, and changelog.
- Reproducibility: yes. Current main's `openclaw agent` registration and gateway CLI option type lack `--sessi ... Gateway agent protocol already accepts `sessionKey`; this is source-reproducible without executing the CLI.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agent): support explicit CLI session keys

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

Prepared head SHA: 2c76dd339f
Review: https://github.com/openclaw/openclaw/pull/85121#issuecomment-4513508932

Co-authored-by: Kaspre <kaspre@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-22 03:08:25 +00:00
Vincent Koc
a4c81c6f35 fix(codex): recover final text after prompt timeout (#84993) 2026-05-22 11:02:47 +08:00
Josh Avant
b8e9ab9385 fix(codex): surface native compaction failures (#85160)
* fix(codex): surface native compaction failures

* docs: add changelog for codex compaction fix

* test: align compaction failure fixtures
2026-05-21 19:41:54 -07:00
Dallin Romney
c8a35c4645 fix: coalesce repeated idle TUI abort notices (#85167) 2026-05-21 18:57:56 -07:00
Josh Avant
577e64db63 fix: require configured subagent allowlist targets (#85154)
* fix subagent allowlists to configured agents

* add changelog for subagent allowlist fix
2026-05-21 18:53:30 -07:00
Vincent Koc
60d200f797 fix(codex): make post-tool raw assistant timeout configurable (#84974)
* fix(codex): make post-tool raw assistant timeout configurable

* docs(codex): align post-tool assistant timeout docs

* docs(changelog): move codex timeout note to unreleased

---------

Co-authored-by: 0x505badc0de <32790662+rozmiarD@users.noreply.github.com>
2026-05-22 09:39:38 +08:00
clawsweeper[bot]
7f4bd454fe fix(agents): preserve accepted spawn terminal success (#85135)
Summary:
- The branch adds accepted `sessions_spawn` tracking through embedded Pi subscribe, runner, fallback, replay, lifecycle, tests, deadcode allowlist, and changelog surfaces.
- Reproducibility: yes. at source level. Current main documents accepted `sessions_spawn` results but the pre- ...  and classifier paths do not carry that accepted child-run fact into incomplete-turn or fallback decisions.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(qa-lab): allow codex fixtures in deadcode
- PR branch already contained follow-up commit before automerge: fix(agents): preserve accepted spawn terminal success

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

Prepared head SHA: 0f6d92b8cd
Review: https://github.com/openclaw/openclaw/pull/85135#issuecomment-4513861326

Co-authored-by: samzong <samzong.lu@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-22 01:16:41 +00:00
Josh Avant
221f5349b5 fix: redact denied exec failure params (#85140)
* fix exec failure log redaction

* docs: add exec redaction changelog

* test: satisfy redaction lint
2026-05-21 18:10:50 -07:00
Gio Della-Libera
ee9813f478 fix(cli): keep nodes json stdout clean (#84423)
Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-21 18:05:11 -07:00
Josh Avant
cbe68ba1a1 Fix inherited XDG env for exec subprocesses (#85139)
* fix exec xdg env inheritance

* chore changelog xdg env fix
2026-05-21 18:01:38 -07:00
Dallin Romney
d391434f4e perf: skip dts for local launcher builds (#85142) 2026-05-21 17:58:48 -07:00
Agustin Rivera
4faeb378ee fix(changelog): record provider setup trust fix (#81069) 2026-05-21 17:48:24 -07:00
Josh Avant
1f9ebb9dda Fix Matrix configured two-person room routing (#85137)
* Fix Matrix configured room DM routing

* Add Matrix room routing changelog
2026-05-21 17:40:17 -07:00
Michael Appel
0aabaebba1 fix: address issue (#81069) 2026-05-21 17:39:48 -07:00
Kevin Lin
6fe3088bc6 docs: refactor plugin bundle docs 2026-05-21 17:34:42 -07:00
Kevin Lin
7f499643b2 enhance(slack): deliver native plugin approvals (#85062)
* fix(slack): deliver native plugin approvals

* fix(slack): deliver plugin approvals with native UI

* docs: defer slack plugin approval docs
2026-05-21 17:31:06 -07:00
Kevin Lin
777a113973 fix(codex): await computer use elicitation bridge (#85117)
* fix(codex): bridge computer use elicitations

* fix(codex): preserve computer use approval boundary

* fix(codex): await app-server elicitation bridge
2026-05-21 17:17:46 -07:00
Gio Della-Libera
bc9e601491 fix: allow provider timeout overlays (#83990)
* fix: allow provider timeout overlays

* test: fix provider overlay fixture types
2026-05-21 17:10:32 -07:00
Firas Alswihry
0df9f297b6 fix(gateway): mirror source message sends into transcript (#84837)
Co-authored-by: Firas Alswihry <itzfiras@gmail.com>
2026-05-22 01:08:00 +01:00
Vincent Koc
f015c3ff52 test(qa-lab): tag live-only runtime sentinels 2026-05-22 07:42:09 +08:00
Vincent Koc
15a0156a8c fix(update): reject openclaw source package targets 2026-05-22 07:35:57 +08:00
Vincent Koc
fad1c8a071 test(qa-lab): add long-context watchdog scenario 2026-05-22 07:16:35 +08:00
Peter Steinberger
e2c92be90b chore(release): bump version to 2026.5.21 2026-05-22 00:09:45 +01:00
Josh Avant
ba06376c79 fix: harden codex sandbox execution
Harden the Codex app-server native execution bridge for OpenClaw sandboxed runs. The change keeps core sandbox policy in OpenClaw while exposing the process, filesystem, and HTTP relay behavior Codex needs inside a scoped exec server.

The large exec-server/test files were split into focused modules before landing, and the PR was rebased onto current main with focused tests, Testbox changed checks, CI, and Codex autoreview green.

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-21 23:47:32 +01:00
Andy Ye
c2004fe662 fix(agents): surface blocked subagent completions (#80886)
Summary:
- The PR adds shared blocked-liveness normalization, applies it to agent.wait, gateway dedupe, subagent registry, and announcement paths, and adds regression tests plus a changelog entry.
- Reproducibility: yes. from source inspection: current main accepts blocked lifecycle/wait metadata as ok thr ...  gateway wait and registry completion paths. I did not run a live provider overflow in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): normalize blocked wait completions
- PR branch already contained follow-up commit before automerge: fix(agents): surface blocked subagent completions

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

Prepared head SHA: 224785c8a6
Review: https://github.com/openclaw/openclaw/pull/80886#issuecomment-4427552621

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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>
2026-05-21 22:34:21 +00:00
Peter Steinberger
373e3fc719 fix: harden autoreview target handling 2026-05-21 23:26:49 +01:00
Peter Steinberger
08f66133ef ci(release): link durable release evidence 2026-05-21 23:24:35 +01:00
Dallin Romney
dca9cecaee perf(plugins): thread install records through plugin load options (#85026)
Adds installRecords to PluginLoadOptions and PluginRuntimeLoadContext so
callers that already hold a PluginMetadataSnapshot can pass the snapshot's
in-memory records instead of forcing each downstream loader to re-read
installs.json. resolvePluginRuntimeLoadContext extracts the records from
the snapshot via extractPluginInstallRecordsFromInstalledPluginIndex,
buildPluginRuntimeLoadOptionsFromValues forwards them, and the setup +
runtime provider load paths in providers.runtime.ts pass them through
from params.pluginMetadataSnapshot. resolvePluginLoadCacheContext uses
the threaded records (falling back to the sync read) and
loader-provenance now uses params.installRecords ?? sync-read instead of
always reading and overlaying.
2026-05-21 15:24:31 -07:00
Peter Steinberger
d4c6bdfeae docs: credit per-agent lean changelog entry 2026-05-21 23:20:02 +01:00
Andy Ye
6b5eba1f43 fix(cli): preserve numeric config set record keys (#83769)
Merged via squash.

Prepared head SHA: cb55b4a40d
Co-authored-by: TurboTheTurtle <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-22 01:09:15 +03:00
xiaotian
1b77145687 fix(agents): tolerate in-process session writes during prompt release (#84250)
Merged via squash.

Prepared head SHA: 33f88febc3
Co-authored-by: tianxiaochannel-oss88 <272340815+tianxiaochannel-oss88@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-21 18:06:12 -04:00
Josh Avant
7cda26aa6c Handle Codex turns missing completion (#85107)
* fix(codex): handle missing turn completion

* docs: add changelog for Codex completion fix
2026-05-21 15:02:17 -07:00
Peter Steinberger
61150870e2 test(parallels): allow npm smoke host ip override 2026-05-21 22:53:56 +01:00
Peter Steinberger
75a011977d fix(qa): accept Telegram no-reply timeout details 2026-05-21 22:45:25 +01:00
Peter Steinberger
8aabb79a83 test(release): respect stable ClawHub channel 2026-05-21 22:45:25 +01:00
Peter Steinberger
cabb55380f feat(plugin-sdk): add session entry workflow helpers
Co-authored-by: Eduardo Piva <efpiva@gmail.com>
2026-05-21 22:41:45 +01:00
Josh Avant
0ab1449215 Fix Discord session recovery abort ownership (#85100)
* fix auto-reply abort ownership

* add changelog for #85100
2026-05-21 14:34:18 -07:00
Peter Steinberger
c5e8bd08b8 fix(whatsapp): remove baileys logger patch 2026-05-21 22:33:13 +01:00
openclaw-release-bot
de5f1fa99a chore(release): update appcast for 2026.5.20 2026-05-21 21:25:03 +00:00
Peter Steinberger
26e64bda14 fix(whatsapp): update baileys dependency 2026-05-21 22:20:09 +01:00
100menotu001
f52db027a0 fix(discord): log component registry error details
Log structured details when Discord persistent component registry state falls back after a store failure.

- Format Error name, message, stack, and cause metadata at the Discord registry warning call site.
- Forward plugin runtime logger metadata to the underlying child logger.
- Add focused regression coverage for the Discord fallback warning and runtime logging adapter.
- Add changelog credit for @100menotu001.

Fixes #84185.

Co-authored-by: OpenClaw Contributor <100menotu001@users.noreply.github.com>
Co-authored-by: Craig <froelich@craigs.mac.studio.froho>
2026-05-21 22:13:14 +01:00
Thomas Krohnfuß
98af51748d fix(channels): hint at when bundled channel module is missing (#76974)
Summary:
- The PR adds a bundled-channel load-error formatter, wires it into the bundled-channel warning paths, adds focused tests, and updates the changelog.
- Reproducibility: yes. source-level: current main logs bundled-channel load failures with bare `formatErrorMe ... cause`. The contributor's terminal proof demonstrates the same wrapped-error shape before and after the PR.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(channels): walk error cause chain to detect missing bundled modules
- PR branch already contained follow-up commit before automerge: docs(changelog): add Unreleased Fixes entry
- PR branch already contained follow-up commit before automerge: Merge remote-tracking branch 'origin/main' into fix/bundled-channel-l…
- PR branch already contained follow-up commit before automerge: Merge branch 'main' into fix/bundled-channel-load-doctor-hint

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

Prepared head SHA: 416a8a2e77
Review: https://github.com/openclaw/openclaw/pull/76974#issuecomment-4367336485

Co-authored-by: BSG2000 <github@hsu.hamburg>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: BSG2000 <BSG2000@users.noreply.github.com>
Co-authored-by: BSG2000 <thomas.krohnfuss@stud.th-luebeck.de>
Co-authored-by: Thomas Krohnfuß <BSG2000@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-21 21:03:49 +00:00
100menotu001
0212188cb6 fix(discord): preserve reusable presentation buttons
Preserve `reusable` for portable message presentation buttons and carry it through Discord component registration so repeatable callbacks stay available after a successful interaction.

Also keeps `reusable` through legacy presentation-to-interactive conversion and documents the user-visible change in the changelog.

Verification:
- `pnpm test src/interactive/payload.test.ts extensions/discord/src/shared-interactive.test.ts extensions/discord/src/components.test.ts -- --reporter=verbose`
- `git diff --check`
- `AUTOREVIEW_AUTO_TESTS=0 .agents/skills/autoreview/scripts/autoreview --mode local`
- PR CI at `52f25221b3e01f3255d8df37df73d0357ab7410b`: all completed checks green/skipped/neutral except pending CodeQL `Security High (mcp-process-tool-boundary)` at time auto-merge was armed.

Co-authored-by: OpenClaw Contributor <100menotu001@users.noreply.github.com>
2026-05-21 22:02:16 +01:00
clawsweeper[bot]
5f5e3b4511 Fix Ollama cloud API key discovery (#85091)
Summary:
- The branch teaches Ollama discovery to use resolved `discoveryApiKey` values for non-local cloud providers, preserves local marker auth, and adds focused provider-discovery regressions plus a changelog entry.
- Reproducibility: yes. from source inspection: current main can return the `OLLAMA_API_KEY` marker instead of ... ential for documented Ollama Cloud config. I did not run executable tests because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: ci: allowlist qa lab fixtures
- PR branch already contained follow-up commit before automerge: Fix Ollama cloud API key discovery
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8503…

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

Prepared head SHA: cb6b658819
Review: https://github.com/openclaw/openclaw/pull/85091#issuecomment-4512647237

Co-authored-by: Anup Sharma <anupnewsmail@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-21 20:54:59 +00:00
Peter Steinberger
48bb3b0a74 fix(config): refresh Discord component TTL metadata (#84189) (thanks @100menotu001) 2026-05-21 21:54:30 +01:00
OpenClaw Contributor
4c6fe55d20 fix(discord): cap component ttl at one day 2026-05-21 21:54:30 +01:00
OpenClaw Contributor
814386a10b fix(discord): deep merge agent component config 2026-05-21 21:54:30 +01:00
OpenClaw Contributor
c17a48ccfd docs(discord): document component ttl config 2026-05-21 21:54:30 +01:00
OpenClaw Contributor
9a4fb3ed7e fix(config): refresh Discord component ttl metadata 2026-05-21 21:54:30 +01:00
OpenClaw Contributor
ee915cfede fix(discord): allow component registry ttl override 2026-05-21 21:54:30 +01:00
Gio Della-Libera
8961eae3f0 fix(cli): reject invalid node run port (#84307)
Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-21 13:47:28 -07:00
martingarramon
7f4462e5c0 fix(agents): classify auth HTML provider responses (#79900)
Merged via squash.

Prepared head SHA: b00513414d
Co-authored-by: martingarramon <263922628+martingarramon@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-21 23:28:53 +03:00
Super Zheng
01d95b9757 fix(gateway): allow bearer-auth session history reads (#81815)
Merged via squash.

Prepared head SHA: eb49667324
Co-authored-by: medns <1575008+medns@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-05-21 13:23:17 -07:00
Peter Steinberger
504f0dfa36 fix(installer): handle headless onboarding tty 2026-05-21 21:20:52 +01:00
Super Zheng
b77f36fb1c fix(exec): protect pathPrepend against posix login-shell RC overrides (#81403)
Merged via squash.

Prepared head SHA: 874fa90aa9
Co-authored-by: medns <1575008+medns@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-05-21 13:19:41 -07:00
Super Zheng
9b7e431b89 refactor(gateway): remove unused readLastMessagePreviewFromTranscript helper (#84427)
Merged via squash.

Prepared head SHA: 257aab2d18
Co-authored-by: medns <1575008+medns@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-05-21 13:17:05 -07:00
Super Zheng
faf96ff99b test: fix environment sensitivity in resolveNpmCommandInvocation test (#83405)
Merged via squash.

Prepared head SHA: b2c2e9a694
Co-authored-by: medns <1575008+medns@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-05-21 13:15:47 -07:00
Dallin Romney
4399eee6e0 fix(auth): load legacy Codex OAuth sidecars in embedded secrets-runtime loaders (#85074)
The auto-migration introduced in #83312 only fires when a credential is loaded
via a path that reads its sidecar tokens. The OAuth refresh manager's internal
loader does (so direct CLI inference works and self-heals on first refresh).

The embedded runner's secrets-runtime loaders did not:

  - loadAuthProfileStoreForSecretsRuntime
  - loadAuthProfileStoreWithoutExternalProfiles
  - ensureAuthProfileStoreWithoutExternalProfiles

All three opted out of sidecar resolution. So for an upgraded user with a
legacy oauthRef-backed openai-codex profile, the credential loaded with no
access/refresh material, evaluateStoredCredentialEligibility marked it
ineligible, resolveAuthProfileOrder filtered it out, and resolveApiKeyForProvider
threw "No API key found for provider 'openai-codex'" before the OAuth manager
(and its migration path) was ever consulted. CLI worked, Telegram/cron/embedded
turns broke — only doctor-or-bust would fix it.

Flip the three embedded loaders to default resolveLegacyOAuthSidecars to true
(matching loadStoredOAuthRefreshStore). The existing #83312 refresh-and-rewrite
then fires on the first embedded turn for these users and persists tokens
inline, removing the legacy sidecar from disk on the next doctor pass.

Cherry-picked and squashed from PR #84752 (commits 85f36e8d2b and
4624e34c06). Comments noting local-fork bookkeeping stripped per repo policy.

Co-authored-by: Will <totalsolutionspm@gmail.com>
2026-05-21 13:07:49 -07:00
litang9
016c34ff1d Fix/codex deactivated workspace failover (#55893)
Merged via squash.

Prepared head SHA: 3aa770fa84
Co-authored-by: litang9 <141409885+litang9@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-21 22:47:29 +03:00
Peter Steinberger
1d5b5db4d2 fix(codex): demote plugin thread eligibility log 2026-05-21 20:12:02 +01:00
Peter Steinberger
aef8d1771d fix(models): reset warmed provider auth on hot reload 2026-05-21 20:09:51 +01:00
Sarah Fortune
7ddcca6c77 address review v3: invalidate prepared map on auth-profile logout + defer plugin-reload rewarm
P1 (auth-profile logout): invalidateModelAuthStatusCache now also clears
the prepared provider-auth map, and the models.auth.logout handler fires
a rewarm against the current config. Without this, removing a provider's
auth profiles left the warmed 'true' answer in the map until restart,
so /models and pickers kept advertising the removed provider.

P1 (plugin-reload ordering): the previous version fired the rewarm
inline with the clear, before plan.reloadPlugins() ran. The warmer
reads plugin metadata and synthetic-auth hooks, so it published the
pre-reload runtime's answers. Moved the rewarm to fire after the
plugin-reload block completes, so it reads the new plugin runtime.

The early clear still happens upfront so callers don't keep seeing the
pre-reload answer during the reload window.
2026-05-21 20:09:51 +01:00
Sarah Fortune
c452a1e7e5 address review v2: workspace scope, warm generation guard, plugin reload trigger
ClawSweeper P1 + P2 findings on the prior review-fix commit.

- [P1] hasAuthForModelProvider now also checks workspaceDir against the
  warm's snapshot value. The warmer uses resolveDefaultAgentWorkspaceDir,
  but per-agent picker calls (buildModelsProviderData →
  resolveVisibleModelCatalog → createProviderAuthChecker) thread an
  agent-specific workspaceDir, and provider env/synthetic-auth
  resolution depends on it. Without this check the picker for agent B
  would silently reuse agent A's warmed answer.
- [P1] warmCurrentProviderAuthState now claims a generation counter at
  the start of its work and only publishes the new state if the
  generation hasn't been bumped (by a concurrent clear or another
  warm). Closes the race where a slow startup warm could overwrite a
  newer reload-driven rewarm with stale data.
- [P2] Reload handler now also clears and rewarms the prepared map
  when plugins.* config paths change. Provider auth can come from
  plugin env vars and plugin synthetic-auth wiring, so plugin hot
  reloads must invalidate the auth state too — not just model config
  paths.

Test: new case asserting a non-default workspaceDir caller bypasses the
prepared map and falls through to compute.
2026-05-21 20:09:51 +01:00
Sarah Fortune
01087cb936 address review: scope short-circuit by caller auth context + rewarm on reload
Two fixes flagged by ClawSweeper.

P1 — hasAuthForModelProvider now only short-circuits via the prepared
map when the caller's scope matches the warmer's (broad discovery, no
agentDir/env/store override). Read-only gateway model lists pass
runtimeAuthDiscovery: false, which the visibility helper maps to
discoverExternalCliAuth: false and allowPluginSyntheticAuth: false; the
prepared broad answer was previously masking that narrower intent. Now
those callers fall through to compute the narrow answer.

P2 — server-reload-handlers now also schedules a rewarm right after
clearing the prepared state on model-config reload, so long-lived
gateways don't regress to per-call discovery between reload and the
next restart.

Test: extends model-provider-auth.test.ts with a scope-narrowing case
asserting the prepared answer is bypassed when the caller passes
discoverExternalCliAuth: false / allowPluginSyntheticAuth: false.
2026-05-21 20:09:51 +01:00
Sarah Fortune
180cecda85 test(model-provider-auth): cover prepared-state short-circuit and clear
Asserts hasAuthForModelProvider returns the warmed answer for providers
in the prepared map and skips the compute path, and that
clearCurrentProviderAuthState restores fall-through to compute.
2026-05-21 20:09:51 +01:00
Sarah Fortune
4f80cc1943 perf(models): pre-warm provider auth state at gateway startup
Eliminates the per-call auth-filter loop that every /models invocation
(Discord/Telegram pickers, CLI, status commands) was paying — 30 unique
providers × ~600 ms each of plugin-runtime / external-CLI / auth-profile
discovery, done fresh on every call (~20 s per call).

warmCurrentProviderAuthState builds a provider->boolean map once at
gateway startup against a single AuthProfileStore scoped to every
candidate provider, and hasAuthForModelProvider consults the prepared
map first and short-circuits. The map is invalidated on config reload
alongside resetModelCatalogCache so the next read after a relevant
config change rewarms.

Per /models: ~20,569 ms → ~5 ms (~4,100×).
One-time startup warm cost: ~49 s (cold catalog + auth sweep), logged
via gateway log.info on completion.
2026-05-21 20:09:51 +01:00
Dallin Romney
ebd8b00cc3 fix(qa-lab): rename codex lifecycle fixtures to match knip ignore pattern (#85066)
knip's deadcode-unused-files check ignores fixtures matching **/*.fixture.ts
(dot before "fixture"). The codex lifecycle fixtures landed in bbf3eec786
as auth-profile-fixture.ts and codex-plugin-fixture.ts (hyphen), so knip
flagged them as unexpected unused files and CI's check-dependencies job
has been failing on main since then. Rename to auth-profile.fixture.ts
and codex-plugin.fixture.ts and update the lifecycle test, the fixture
cross-import, and the six qa/scenarios markdown files that reference
them by path and qaImport specifier.
2026-05-21 11:56:59 -07:00
Vincent Koc
b25a0d013b test(gateway): relax e2e node status waits 2026-05-22 02:25:30 +08:00
zhang-guiping
7d5afcbb3f fix #84745: scope Google preview model normalization to Google providers only (#84762)
Summary:
- The branch scopes config-time Google Gemini preview model normalization to Google providers or nested `google/` proxy suffixes, adds model-picker regression coverage, and adds a changelog entry.
- Reproducibility: yes. by source inspection. Current main sends every provider suffix through the Google prev ... i-3-flash` deterministically becomes `litellm/gemini-3-flash-preview`; I did not run a live cron preflight.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(config): scope Google preview model normalization to Google provi…
- PR branch already contained follow-up commit before automerge: fix #84745: scope Google preview model normalization to Google provid…
- PR branch already contained follow-up commit before automerge: fix #84745: preserve proxy Google model normalization

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

Prepared head SHA: c59163c809
Review: https://github.com/openclaw/openclaw/pull/84762#issuecomment-4504169062

Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: 张贵萍0668001030 <zhang.guiping@xydigit.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-21 17:45:57 +00:00
Vincent Koc
bbf3eec786 test(qa-lab): cover codex plugin lifecycle fixtures 2026-05-22 01:42:25 +08:00
Vincent Koc
ec0cf9af04 fix(tests): allow slower kitchen sink installs 2026-05-22 01:40:18 +08:00
Vincent Koc
46c8864048 revert(qa-lab): remove scenario github traceability metadata 2026-05-22 01:27:29 +08:00
Vincent Koc
23c58081d0 fix(docker): prune omitted plugin runtime deps 2026-05-22 01:08:48 +08:00
Dallin Romney
205c595b13 fix(auth): skip OAuth refresh adapter when credential has no refresh token (#85028)
OAuth credentials that loaded without their sidecar material (no access, no
refresh) would still enter the refresh path inside the per-profile lock,
where the adapter call is bounded by OAUTH_REFRESH_CALL_TIMEOUT_MS (120s).
That made the eventual "No API key found for provider" surface to the user
only after a long stall, even though the resolver had no usable material to
attempt with.

Short-circuit doRefreshOAuthTokenWithLock to return null when there is no
refresh token to use, after the in-lock main-store adoption and external
bootstrap-credential checks have already had a chance to recover.

Thanks @romneyda.
2026-05-21 10:00:29 -07:00
Vincent Koc
178e510aae test(qa-lab): cover update package sentinel 2026-05-22 00:59:02 +08:00
clawsweeper[bot]
7f943b5d8f fix(json): retry on transient File changed during read race condition (#85029)
Summary:
- The PR wraps the async JSON file readers in `src/infra/json-files.ts` with bounded retries for fs-safe `File changed during read` races, adds regression tests, and adds a changelog entry.
- Reproducibility: yes. Source inspection shows fs-safe throws `File changed during read`, current main re-exp ... R proof includes before/after gateway logs; I did not run a new live race harness in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(json): preserve strict reader types (Promise<T> for readJson/read…
- PR branch already contained follow-up commit before automerge: test(json): add retry-success and retry-exhaustion coverage
- PR branch already contained follow-up commit before automerge: fix(json): resolve lint warnings (prefer-exponentiation-operator, cur…
- PR branch already contained follow-up commit before automerge: fix(json): retry on transient File changed during read race condition

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

Prepared head SHA: 00602a1c03
Review: https://github.com/openclaw/openclaw/pull/85029#issuecomment-4510494668

Co-authored-by: samson1357924 <98934496+samson1357924@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-21 16:56:46 +00:00
clawsweeper[bot]
5955f354f7 fix(status): add gateway delivery health telemetry (#85016)
Summary:
- This replacement PR adds inbound delivery diagnostic events, gateway status counters and warnings, transport ... ut, Prometheus/OpenTelemetry metrics, docs, changelog, and regression coverage for gateway delivery health.
- Reproducibility: no. high-confidence live reproduction of the original Feishu failure was run here. Source i ... ch/turn telemetry, and the source PR supplies after-fix live output for the connected WebChat gateway path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(types): restore PR conflict resolution type checks

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

Prepared head SHA: 6ffe08a9c7
Review: https://github.com/openclaw/openclaw/pull/85016#issuecomment-4510224436

Co-authored-by: Andi Liao <liaoandi95@gmail.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-21 16:55:29 +00:00
Vincent Koc
efb7e4742f test(qa-lab): trace scenario issue evidence 2026-05-22 00:51:32 +08:00
clawsweeper[bot]
b33deb4159 fix(sessions): preserve compatible auth overrides (#85014)
Summary:
- This replacement branch preserves compatible session auth profile overrides during `sessions.patch` model ch ... d/cross-provider regression coverage, and updates related doctor/Mantis test assertions plus the changelog.
- Reproducibility: yes. by source inspection: current main’s `sessions.patch` model branch calls `applyModelOv ... d helper clears auth fields unless preservation is requested. I did not run tests in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(mantis): align telegram proof evidence comment
- PR branch already contained follow-up commit before automerge: fix(sessions): preserve provider auth aliases
- PR branch already contained follow-up commit before automerge: fix(sessions): guard unprefixed auth overrides
- PR branch already contained follow-up commit before automerge: fix(doctor): preserve params prototype semantics
- PR branch already contained follow-up commit before automerge: fix(sessions): preserve compatible auth overrides

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

Prepared head SHA: 64a07393d5
Review: https://github.com/openclaw/openclaw/pull/85014#issuecomment-4510194125

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
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-21 16:17:50 +00:00
Vincent Koc
652712e0ad ci(qa): publish soak parity artifacts 2026-05-22 00:08:51 +08:00
Vincent Koc
9f2c0a80b4 fix(qa): keep searchable tool coverage report-only 2026-05-21 23:55:35 +08:00
Vincent Koc
da1925cb67 test(e2e): isolate kitchen sink rpc gateway 2026-05-21 23:54:33 +08:00
clawsweeper[bot]
277a4b6952 fix(ollama): allow Orb host local auth (#84999)
Summary:
- The PR adds Docker/OrbStack host aliases to Ollama local-auth classification, keeps those aliases out of loopback-only discovery suppression, adds regression tests, and updates the changelog.
- Reproducibility: yes. The linked report gives a concrete v2026.5.19 config and error, and current main source shows host.orb.internal is not classified as local for ollama-local marker auth.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ollama): allow Orb host local auth

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

Prepared head SHA: cb82dcf522
Review: https://github.com/openclaw/openclaw/pull/84999#issuecomment-4509786332

Co-authored-by: Bob <dutifulbob@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: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-21 15:37:42 +00:00
Firas Alswihry
229323d37a test(qa-lab): add personal failure recovery scenario 2026-05-21 23:22:35 +08:00
Vincent Koc
0e6f314dbb ci: tune crabbox developer image config 2026-05-21 23:21:35 +08:00
Vincent Koc
cf0657852f feat(qa-lab): add jsonl replay harness 2026-05-21 23:03:51 +08:00
Neerav Makwana
66dcc4ee8f fix(codex): beta blocker - keep context engine on canonical session key (#84954)
Merged via squash.

Prepared head SHA: 6cdccaa007
Co-authored-by: neeravmakwana <261249544+neeravmakwana@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-21 11:01:35 -04:00
Peter Steinberger
1b1580cbc3 chore(release): refresh generated baselines 2026-05-21 15:52:04 +01:00
Sally O'Malley
e72f601925 fix(openshell): use NVIDIA CLI contract
Remove the unrelated npm openshell dependency and keep the OpenShell sandbox backend pointed at the NVIDIA CLI command contract.
2026-05-21 22:51:57 +08:00
Peter Steinberger
94b6d9f8b2 docs(release): prefer 1Password provider preflight 2026-05-21 15:46:34 +01:00
Gio Della-Libera
6dbd5bd446 Policy: add model, network, and MCP conformance checks (#80783)
* feat(policy): add model network and mcp conformance checks

* fix(policy): validate conformance rule shapes

* fix(policy): quote dynamic evidence paths

* fix(policy): scan per-agent model maps

* fix(policy): normalize model provider conformance
2026-05-21 07:27:16 -07:00
Vincent Koc
2bb00f6726 fix(agents): fence embedded session writes 2026-05-21 22:17:48 +08:00
Peter Steinberger
95eac52e92 test: update command auth expectations 2026-05-21 15:14:48 +01:00
Peter Steinberger
e0b53cae41 docs: remove stale owner tool wording 2026-05-21 15:14:48 +01:00
Peter Steinberger
02182d5a30 refactor: remove sender owner tool gating 2026-05-21 15:14:48 +01:00
Rubén Cuevas
159b3002e4 fix(xai): keep OAuth URL clickable (#84927) 2026-05-21 07:08:34 -07:00
Jesse Merhi
a901396ad1 Fix stale WebChat typing indicator after terminal session patch (#84565)
Summary:
- The branch clears WebChat local run and stream state when terminal session reconciliation completes the acti ...  session events, adjusts deferred history/queue flushing, adds regression tests, and updates the changelog.
- Reproducibility: yes. with high confidence from source inspection and PR evidence. Current main can apply a  ...  PR body, recording, and regression shape show the stale WebChat typing state being cleared by this branch.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: harden webchat session run reconciliation

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

Prepared head SHA: 89cca8dd01
Review: https://github.com/openclaw/openclaw/pull/84565#issuecomment-4498262223

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: jesse-merhi
2026-05-21 14:05:58 +00:00
Peter Steinberger
c49647ee23 docs: document rejected autoreview findings 2026-05-21 14:55:07 +01:00
Vincent Koc
db606a8475 docs(changelog): note VAPID subject fix
Add the missing changelog entry for the landed Web Push VAPID subject fix and refresh the config docs baseline hash to match the Node 24 check environment.
2026-05-21 21:54:47 +08:00
Shakker
6ccca4ae95 docs: add plugin registry reuse changelog 2026-05-21 13:41:29 +01:00
Shakker
b248b4816b test: cover dispatch registry reuse caller 2026-05-21 13:41:29 +01:00
ai-hpc
d2ad7d6b4c perf(plugins): reuse compatible gateway startup registry 2026-05-21 13:41:29 +01:00
Vincent Koc
bde07ddb15 fix(tests): wrap kitchen sink pnpm runner 2026-05-21 19:24:56 +08:00
Vincent Koc
04061bc801 fix(agents): cap heartbeat context hint fallback 2026-05-21 19:01:00 +08:00
Vincent Koc
88c49f9e68 chore(deadcode): dedupe repeated helpers 2026-05-21 18:47:09 +08:00
Frank Yang
f39f56a096 perf(cli): cache stable subcommand help (#84786)
Serve stable doctor, gateway, models, and plugins parent help from startup metadata while preserving strict argv validation and version precedence.

Verification:
- pnpm test src/cli/run-main.test.ts src/cli/run-main.exit.test.ts test/scripts/write-cli-startup-metadata.test.ts -- --reporter=default
- pnpm check:changed
- GitHub required checks passed
2026-05-21 18:01:32 +08:00
WhatsSkiLL
2000227e9e fix(ollama): preserve tool call ids [AI-assisted] (#84855)
Summary:
- The PR preserves native Ollama tool-call IDs through ingest and replay, opts native Ollama out of strict replay ID sanitization, and adds focused regression tests plus a changelog entry.
- Reproducibility: yes. Current main drops native Ollama tool-call IDs on ingest and replay and applies strict ...  PR discussion includes a maintainer-side before/after probe that reproduced the source-level failure path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ollama): keep native tool ids through replay

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

Prepared head SHA: bb9fef7d4c
Review: https://github.com/openclaw/openclaw/pull/84855#issuecomment-4505423891

Co-authored-by: IWhatsskill <whatsskilll@gmail.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-21 09:51:00 +00:00
Peter Steinberger
f43e83c937 fix: align remaining copyright notice 2026-05-21 10:47:54 +01:00
tanshanshan
8a8f9dc8cb fix(config): append numeric bound hints to ceiling/floor validation errors (#84852)
* fix(config): append numeric bound hints to ceiling/floor validation errors

When a config value exceeds a schema-enforced ceiling or falls below a
floor, the error message now includes the constraint explicitly:
  - Inclusive: `(maximum: 20)` / `(minimum: 0)`
  - Exclusive: `(must be less than 5)` / `(must be greater than 0)`

This matches the clarity that enum/union rejections already get via
`(allowed: …)` hints, and avoids the misleading "minimum: 0" wording
that previous attempts produced for `.positive()` / `.gt(0)` rejections.

Only numeric-origin `too_big`/`too_small` issues are enriched; string,
array, and file-size origins are left unchanged.

Fixes #52500

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(config): update maxFileBytes test for numeric bound hint

The test snapshot for `logging.maxFileBytes: 0` rejection now includes
the `(must be greater than 0)` hint appended by the numeric bound
enrichment added in the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(config): guard nullable record in appendNumericBoundHint call

ClawSweeper P1: `record` from `toIssueRecord()` can be null, but
`appendNumericBoundHint` expects a non-null `UnknownIssueRecord`.
Guard with a ternary so the original message is returned when record
is null (which only happens for malformed/empty issues that already
produce generic "Invalid input" messages).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: tanshanshan <tanshanshan@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:44:29 +08:00
Vincent Koc
0fb1de5f73 fix(qa): enable private self-check runtime 2026-05-21 17:42:42 +08:00
tanshanshan
b7f9bf5a5c fix(diffs): replace iconMarkup string with ToolbarIconName enum to el… (#83955)
* fix(diffs): replace iconMarkup string with ToolbarIconName enum to eliminate XSS sink

Replace createToolbarButton's iconMarkup: string parameter with icon: ToolbarIconName,
a union of known icon names. SVG generation moves into a sealed toolbarIconSvg map so
innerHTML only receives compile-time-known strings. The old splitIcon/unifiedIcon/
wrapIcon/backgroundIcon/themeIcon functions are removed; callers now pass icon name
literals instead of raw markup strings.

Closes #83918

* fix(diffs): remove jsdom dependency from viewer-client test

Use source file string analysis instead of jsdom to avoid missing
@types/jsdom declaration error in check-test-types CI job.

* fix(diffs): restore wrap icon arrow segment in ToolbarIconName map

The wrap-on and wrap-off SVG paths were missing the original wrap arrow
segment (M14 6h-4V5h4.5...). Restore the exact original path data and
rebuild the viewer runtime bundle.

* build(diffs): refresh viewer runtime after rebase

---------

Co-authored-by: tanshanshan <tanshanshan@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-21 17:39:01 +08:00
Peter Steinberger
3260da003d fix: update mac copyright owner 2026-05-21 10:33:49 +01:00
Vincent Koc
ec67290e0b fix(agents): normalize openapi tool schemas 2026-05-21 17:29:32 +08:00
NianJiu
c89632b647 fix(memory): stop recall tracking when dreaming is disabled
Fixes #84436.

- Gate memory search recall-tracking side effects on the memory-core dreaming master switch.
- Preserve normal search results and enabled-dreaming tracking behavior.
- Add CLI and tool regression coverage, plus the maintainer changelog entry.

Verification:
- 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 --shell -- "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" (tbx_01ks4watvb6apj9wtdx46a1r31)
- GitHub checks passed on 148fa6595e, including Real behavior proof and CI.
2026-05-21 17:13:49 +08:00
clawsweeper[bot]
5813fa4584 fix(diagnostics-otel): suppress exporter rejection crashes (#84881)
Summary:
- The PR adds a diagnostics-otel scoped unhandled-rejection handler for nested OTLPExporterError values, unregisters it on stop/restart, adds regression tests, and adds a changelog entry.
- Reproducibility: yes. The source path is high-confidence: current main has no OTLPExporterError-specific dia ... ror for non-retryable OTLP HTTP failures; I did not run a live collector shutdown in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(diagnostics-otel): avoid stale exporter handler
- PR branch already contained follow-up commit before automerge: fix(diagnostics-otel): suppress exporter rejection crashes

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

Prepared head SHA: e19c06c992
Review: https://github.com/openclaw/openclaw/pull/84881#issuecomment-4506249586

Co-authored-by: luoyanglang <hanwanlonga@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-21 09:01:32 +00:00
Frank Yang
233765b361 perf: speed up secrets and nodes help startup (#84818)
Merged via squash.

Prepared head SHA: d65ae1bd58
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-21 16:51:57 +08:00
Shakker
e3b77d6d2c docs: add PDF timeout changelog 2026-05-21 09:31:31 +01:00
luoyanglang
248169b646 fix(pdf): bound remote body reads 2026-05-21 09:31:31 +01:00
samzong
88fe39bc8b [Fix] Reject slow node event sends (#84387)
Merged via squash.

Prepared head SHA: b459f9ea57
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-21 16:22:16 +08:00
Vincent Koc
43c6c260de fix(doctor): detect Codex bwrap namespace denials
Fixes #83018.
2026-05-21 16:13:53 +08:00
Jason (Json)
4a360ac1cc fix(update): prune stale local bundled plugin shadows
Summary:\n- prune stale local bundled plugin path records during update/doctor repair\n- keep current, same-version, versionless, source-checkout, and arbitrary local path records preserved\n- add changelog and deterministic sort comparator cleanup\n\nVerification:\n- node scripts/run-vitest.mjs src/plugins/contracts/boundary-invariants.test.ts src/plugins/stale-local-bundled-plugin-install-records.test.ts src/cli/update-cli/post-core-plugin-convergence.test.ts src/commands/doctor-plugin-registry.test.ts\n- node scripts/run-oxlint-shards.mjs --threads=8\n- ./node_modules/.bin/oxfmt --check --threads=1 CHANGELOG.md src/plugins/stale-local-bundled-plugin-install-records.ts src/commands/doctor-plugin-registry.ts\n- git diff --check\n- GitHub exact-SHA: Real behavior proof, build-artifacts, checks-fast-contracts-plugins-a, check-prod-types, check-lint, check-test-types green on 8bcbf681ec
2026-05-21 00:49:19 -07:00
Peter Steinberger
3eb2d64392 ci: add live Codex plugin release check 2026-05-21 08:44:18 +01:00
Lucas Shadler
b05c6158c0 fix(slack): suppress reasoning reply payloads (#84322)
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-21 00:43:05 -07:00
Peter Steinberger
ec7495c993 chore: update vite 2026-05-21 08:33:45 +01:00
Peter Steinberger
ec10d12112 chore: update dependencies 2026-05-21 08:28:44 +01:00
Pavan Kumar Gondhi
3cc8b2a3d0 fix(config): validate browser sandbox bind sources [AI] (#84799)
* fix: validate browser sandbox bind sources

* docs: add changelog entry for PR merge
2026-05-21 12:54:48 +05:30
Pavan Kumar Gondhi
a2d0d6b0c2 doctor: constrain legacy plugin cleanup paths [AI] (#84801)
* fix: constrain legacy plugin dependency cleanup roots

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing codex review

* addressing ci

* docs: add changelog entry for PR merge
2026-05-21 12:54:03 +05:30
Josh Avant
40db92f609 Fix Telegram isolated polling stall watchdog (#84861)
* fix(telegram): watch isolated polling stalls

* docs(changelog): note telegram polling watchdog fix
2026-05-21 00:19:10 -07:00
Peter Steinberger
3faddfb506 ci(release): keep non-waiting clawhub publish best effort 2026-05-21 08:03:48 +01:00
Peter Steinberger
2fd02c2060 ci(release): require resolved target before child dispatch 2026-05-21 07:58:15 +01:00
Peter Steinberger
624d920351 ci(release): keep focused validation reruns independent 2026-05-21 07:58:15 +01:00
Peter Steinberger
0604d25101 ci(release): preserve direct repair publishes 2026-05-21 07:58:15 +01:00
Peter Steinberger
1e8d9666b0 fix(docker): keep prune store warmup before offline stage 2026-05-21 07:58:15 +01:00
Peter Steinberger
1c5fda115f ci(release): streamline beta publish verification 2026-05-21 07:58:15 +01:00
Peter Steinberger
a329b9e1ee fix(docker): keep runtime prune offline 2026-05-21 07:58:15 +01:00
clawsweeper[bot]
e427262044 [Fix] Keep node systemd tokens out of unit files (#84815)
Summary:
- This replacement PR marks the Linux node daemon gateway token as file-backed, writes it to `node.systemd.env`, sanitizes and migrates systemd env artifacts, adds regression tests, and updates the changelog.
- Reproducibility: yes. from source inspection: current `main` copies `OPENCLAW_GATEWAY_TOKEN` into the node s ... e-backed before systemd rendering. I did not run a local live systemd install during this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(systemd): scrub single-quoted env tokens
- PR branch already contained follow-up commit before automerge: [Fix] Keep node systemd tokens out of unit files

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

Prepared head SHA: f626b66c09
Review: https://github.com/openclaw/openclaw/pull/84815#issuecomment-4505012292

Co-authored-by: samzong <samzong.lu@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-21 06:48:15 +00:00
Peter Steinberger
9ec9fbf58d refactor(whatsapp): use async fs-safe credential checks 2026-05-21 07:38:51 +01:00
Marcus Castro
de743c5a54 fix(whatsapp): guard credential atomic writes 2026-05-21 07:38:51 +01:00
Marcus Castro
194f0786d4 fix: reject symlinked whatsapp creds 2026-05-21 07:38:51 +01:00
Gio Della-Libera
8284c035a0 fix(doctor): clear stale runtime override pins (#84221)
* fix(doctor): clear stale runtime override pins

* fix(doctor): register CLI runtime session owners
2026-05-20 23:00:03 -07:00
clawsweeper[bot]
ae80adbefb fix(agents): disable pi-coding-agent auto-retry to prevent tool call replay loops (#84798)
Summary:
- The PR disables pi-coding-agent auto-retry inside prepared embedded Pi settings, updates the focused settings test, and moves the changelog entry into Unreleased.
- Reproducibility: yes. source-reproducible: current main leaves embedded Pi retry enabled, while pi-coding-ag ... e assistant error before continuing. I did not run a live Feishu/Qwen replay loop in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): disable pi-coding-agent auto-retry to prevent tool call …

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

Prepared head SHA: ca745fd55d
Review: https://github.com/openclaw/openclaw/pull/84798#issuecomment-4504702875

Co-authored-by: yelog <yelogeek@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-21 05:56:41 +00:00
clawsweeper[bot]
c9b6a8b408 fix(trajectory): tolerate partial skill snapshot entries in support capture (#84797)
Summary:
- This PR filters partial skill snapshot entries in trajectory support metadata, accepts nullish support-redaction paths, adds regression tests, and records the fix in the changelog.
- Reproducibility: yes. Source inspection on current main shows undefined skill path/name values can reach str ... and the related source PR provides redacted live before/after gateway logs for the symlink-escape scenario.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(trajectory): tighten test types for partial skill entries
- PR branch already contained follow-up commit before automerge: fix(trajectory): tolerate partial skill snapshot entries in support c…

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

Prepared head SHA: ecb3df6c08
Review: https://github.com/openclaw/openclaw/pull/84797#issuecomment-4504703074

Co-authored-by: Luke Boyett <46942646+lukeboyett@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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-21 05:38:57 +00:00
Val Alexander
3156d94bca fix(ui): widen settings personal card
Widen the Control UI settings Personal quick-settings card to the intended 3/1 desktop split, keep Personal before Appearance/Automations at the narrower layout, and make the focused CSS assertions tolerant of harmless formatting changes.

Verification:
- pnpm --dir ui test src/styles/config-quick.test.ts
- pnpm exec oxfmt --check --threads=1 ui/src/styles/config-quick.test.ts
- git diff --check
- GitHub CI on 4c8f6d7f50
2026-05-21 00:32:30 -05:00
Gio Della-Libera
79be940130 fix(agents): log pre-prompt compaction fits decisions (#84676)
Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-20 21:53:02 -07:00
clawsweeper[bot]
0671a2a788 fix(memory-core): allow bounded dreaming session cleanup (#84802)
Summary:
- The PR changes memory-core dreaming narratives to use stable workspace-and-phase session keys, timestamped idempotency keys, serialized pre/final cleanup, focused tests, and a changelog entry.
- Reproducibility: yes. Source inspection of current main shows the session key includes nowMs and is reused a ... plains timestamp-scoped `dreaming-narrative-*` session accumulation without needing a new product decision.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory-core): allow bounded dreaming session cleanup
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-7046…

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

Prepared head SHA: d519bbecac
Review: https://github.com/openclaw/openclaw/pull/84802#issuecomment-4504756650

Co-authored-by: chiyouYCH <563318445@qq.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-21 04:45:19 +00:00
Frank Yang
168f8a758e perf(cli): lazy-load agents actions for help (#84483)
Lazy-load agents CLI action modules from command callbacks so agents --help avoids importing the full agents runtime.

Validated by GitHub required checks and local focused CLI gates.
2026-05-21 12:35:37 +08:00
Andy Ye
46030f5489 Skip empty sherpa structured transcripts (#84667)
Summary:
- The PR changes sherpa-onnx CLI audio parsing so structured JSON with an empty `text` field becomes no transcript, while preserving non-empty JSON extraction and adding direct plus auto-detect regression coverage.
- Reproducibility: yes. Source inspection on current main shows empty sherpa structured JSON misses extraction ... scord voice can skip empty transcripts; I did not run a live Discord reproduction in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Fix stale CI guardrails for sherpa transcript PR
- PR branch already contained follow-up commit before automerge: Skip empty sherpa structured transcripts

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

Prepared head SHA: ac03171cfc
Review: https://github.com/openclaw/openclaw/pull/84667#issuecomment-4501484167

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-21 04:23:59 +00:00
Patrick Erichsen
c0312748c4 feat: support git and local skill installs (#84793) 2026-05-20 21:12:03 -07:00
Gio Della-Libera
a30ac3f8d7 Policy: add tool metadata conformance (#80056)
* feat(policy): add tool metadata conformance checks

* Add policy trusted tool runtime gate

* Use requireMetadata for tool policy

Make tools.requireMetadata the canonical policy schema for risk, sensitivity, and owner requirements. Update runtime enforcement, doctor findings, evidence parsing, tests, and policy docs to use the new schema.

* fix(policy): persist approval metadata

* fix(policy): refresh approval metadata artifacts

* docs(policy): list all tool finding checks

* fix(policy): parse multiline tool metadata

* test(policy): cover unparseable policy check output

* fix(policy): resolve oc-path api in packaged dist

* fix(policy): clear post-rebase CI failures

* test(policy): clear post-rebase CI failures

* fix(policy): restore watch and align validation

* fix(policy): clear ci gate failures

* Simplify policy tool evidence parsing
2026-05-20 20:47:32 -07:00
clawsweeper[bot]
6745fe8e70 fix(doctor): warn when sandbox hides MCP tools (#84742)
Summary:
- This bot replacement PR adds an `openclaw doctor` warning, regression coverage, gateway docs, and a changelog entry for sandbox tool policies that hide configured MCP server tools.
- Reproducibility: yes. source-reproducible. Runtime policy inspection shows sandbox tool policy is a second g ... ed MCP tools, and the source PR supplies after-patch live `openclaw doctor` output showing the new warning.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(doctor): mirror sandbox policy fallback
- PR branch already contained follow-up commit before automerge: fix(doctor): preserve sandbox deny diagnostics
- PR branch already contained follow-up commit before automerge: fix(doctor): polish sandbox MCP warnings
- PR branch already contained follow-up commit before automerge: fix(doctor): warn when sandbox hides MCP tools
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8469…

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

Prepared head SHA: 79dfc3ebc8
Review: https://github.com/openclaw/openclaw/pull/84742#issuecomment-4503743579

Co-authored-by: David Huang <nxmxbbd@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-21 03:28:27 +00:00
Frank Yang
2c0c9c92f4 perf(cli): speed up onboarding help startup (#84488)
Merged via squash.

Prepared head SHA: b3b086e6d8
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-21 11:21:58 +08:00
Frank Yang
2585249737 perf: isolate doctor core check tests (#84493)
Merged via squash.

Prepared head SHA: 6229656ba1
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-21 10:47:43 +08:00
Eduardo Piva
3d3cf96dc9 feat(tasks): explain stale-running maintenance decisions (#84691)
Add JSON-only task maintenance diagnostics for stale running tasks and include the maintainer changelog entry.
2026-05-20 19:42:44 -07:00
clawsweeper[bot]
86ebceeb2e fix(minimax): stop advertising music duration control (#84765)
Summary:
- The PR removes MiniMax music duration support from provider capabilities and docs, stops prompt-injecting duration hints, updates the MiniMax provider test, and adds a changelog entry.
- Reproducibility: yes. by source inspection: current main advertises MiniMax duration support while the reque ... uage hint. I did not rerun a live pre-fix MiniMax request, but the code path and vendor contract are clear.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs(minimax): align music controls
- PR branch already contained follow-up commit before automerge: docs(music): remove minimax duration steering claim
- PR branch already contained follow-up commit before automerge: fix(minimax): stop advertising music duration control

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

Prepared head SHA: 1c616da45c
Review: https://github.com/openclaw/openclaw/pull/84765#issuecomment-4504176794

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@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-21 02:26:17 +00:00
WhatsSkiLL
c4f14a39a5 fix(codex): guard path-only bootstrap files [AI-assisted] (#84736)
Summary:
- The PR updates Codex app-server system-prompt reporting to tolerate bootstrap files with `path` and `content` but no `name`, adds a focused regression test, and records the fix in the changelog.
- Reproducibility: yes. The PR body supplies current-main before output with the `undefined.trim()` stack, and source inspection confirms hook-supplied path-only bootstrap files can reach the Codex report helper.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(codex): guard path-only bootstrap files [AI-assisted]

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

Prepared head SHA: 4667110899
Review: https://github.com/openclaw/openclaw/pull/84736#issuecomment-4503672362

Co-authored-by: JARVIS-Glasses <whatsskilll@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-21 01:55:29 +00:00
lukaIvanic
9cdf8a1e2f Warn on plaintext secret config in doctor (#84718)
Summary:
- Adds a `doctor` security warning for plaintext secret-bearing `openclaw.json` fields by reusing the secrets target registry and shared model-provider header sensitivity policy.
- Reproducibility: yes. for source-level behavior: current main has plaintext secret audit coverage but no doc ... llector for those config targets, and the PR body includes live patched CLI output showing the new warning.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Warn on plaintext secret config in doctor

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

Prepared head SHA: 31f83aae19
Review: https://github.com/openclaw/openclaw/pull/84718#issuecomment-4503210496

Co-authored-by: qingsenlab <qingsenlab@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-21 01:27:34 +00:00
Jesse Merhi
e964987cd2 Remove skill prelude exec allowlist (#84570)
Summary:
- The PR removes the legacy `cat SKILL.md && printf ... && <skill-wrapper>` exec-approval allowlist path, updates focused exec-approval tests, and adds a changelog entry.
- Reproducibility: yes. Current-main source and tests show the old `cat SKILL.md && printf ... && <wrapper>` c ... ed this by source and test inspection rather than executing tests because the checkout review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Remove skill prelude exec allowlist

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

Prepared head SHA: 0ca7f3e8ef
Review: https://github.com/openclaw/openclaw/pull/84570#issuecomment-4498357535

Co-authored-by: jesse-merhi <79823012+jesse-merhi@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: jesse-merhi
2026-05-21 01:03:35 +00:00
Dallin Romney
b79effefee perf(tui): defer EmbeddedTuiBackend import, drop dead warmup helpers (#84701)
* perf(tui): skip plugin-aware config validation on remote TUI startup

Cold `openclaw tui` against a remote gateway was synchronously calling
loadPluginMetadataSnapshot() via getRuntimeConfig() -> loadConfig() ->
validateConfigObjectWithPlugins(), pulling the full plugin metadata
snapshot (200k+ file reads) onto the TUI's event loop. The TUI itself
never consumes plugin metadata in remote mode; it queries the gateway
over RPC. The work was being done purely to validate the config and
then thrown away.

Thread an opt-in `skipPluginValidation` flag through getRuntimeConfig()
and loadConfig() (createConfigIO already supports pluginValidation: "skip";
it just wasn't reachable from the runtime entrypoints). The TUI passes
skipPluginValidation: !isLocalMode so:

- Remote-mode TUI: no plugin metadata load, no event-loop freeze after
  first render
- Embedded (--local) mode: unchanged; the in-process agent runtime
  still gets a fully validated config

* remove verbose comments

* perf(tui): move context cache warmup from module top-level to embedded backend

agents/context.ts fired ensureContextWindowCacheLoaded() unconditionally
at module-eval time for non-skip-listed CLI commands. The TUI transitively
imports this module, so the warmup ran on every TUI startup including
remote-mode, cascading into ensureOpenClawModelsJson -> resolveImplicitProviders
-> runProviderCatalog and dominating the cold-start freeze (CPU profile
showed ~55s of resolveProviderSyntheticAuthWithPlugin, lstat, open, etc.).

It also pre-emptively called getRuntimeConfig() without skipPluginValidation,
pinning the full snapshot and nullifying the skip flag added on this branch.

Remove the top-level side effect and trigger the warmup explicitly from
EmbeddedTuiBackend.start(), which only runs when an in-process agent
runtime actually needs the cache.

* perf(tui): defer EmbeddedTuiBackend import until local mode

* refactor(agents): remove dead context-cache warmup helpers
2026-05-20 17:43:52 -07:00
Dallin Romney
d91ef6bb17 perf(tui): skip plugin metadata + provider catalog on remote TUI startup (#84686)
* perf(tui): skip plugin-aware config validation on remote TUI startup

Cold `openclaw tui` against a remote gateway was synchronously calling
loadPluginMetadataSnapshot() via getRuntimeConfig() -> loadConfig() ->
validateConfigObjectWithPlugins(), pulling the full plugin metadata
snapshot (200k+ file reads) onto the TUI's event loop. The TUI itself
never consumes plugin metadata in remote mode; it queries the gateway
over RPC. The work was being done purely to validate the config and
then thrown away.

Thread an opt-in `skipPluginValidation` flag through getRuntimeConfig()
and loadConfig() (createConfigIO already supports pluginValidation: "skip";
it just wasn't reachable from the runtime entrypoints). The TUI passes
skipPluginValidation: !isLocalMode so:

- Remote-mode TUI: no plugin metadata load, no event-loop freeze after
  first render
- Embedded (--local) mode: unchanged; the in-process agent runtime
  still gets a fully validated config

* remove verbose comments

* perf(tui): move context cache warmup from module top-level to embedded backend

agents/context.ts fired ensureContextWindowCacheLoaded() unconditionally
at module-eval time for non-skip-listed CLI commands. The TUI transitively
imports this module, so the warmup ran on every TUI startup including
remote-mode, cascading into ensureOpenClawModelsJson -> resolveImplicitProviders
-> runProviderCatalog and dominating the cold-start freeze (CPU profile
showed ~55s of resolveProviderSyntheticAuthWithPlugin, lstat, open, etc.).

It also pre-emptively called getRuntimeConfig() without skipPluginValidation,
pinning the full snapshot and nullifying the skip flag added on this branch.

Remove the top-level side effect and trigger the warmup explicitly from
EmbeddedTuiBackend.start(), which only runs when an in-process agent
runtime actually needs the cache.
2026-05-20 17:43:24 -07:00
clawsweeper[bot]
b3ec4f08d1 Route JSON-mode plugin registration logs to stderr (#84741)
Summary:
- The PR extracts JSON-mode console-to-stderr routing into a shared CLI helper, wraps root and `nodes` lazy plugin registration, adds nodes registration coverage, and adds a changelog entry.
- Reproducibility: yes. for source-level reproduction: the linked report shows `openclaw nodes list --json 2>  ... ssing the existing JSON stderr guard. I did not run the live Helm/container repro in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Route JSON-mode plugin registration logs to stderr

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

Prepared head SHA: c9d0867db0
Review: https://github.com/openclaw/openclaw/pull/84741#issuecomment-4503741078

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-21 00:35:40 +00:00
Dallin Romney
cd019cfa41 build: suppress rolldown-plugin-dts CommonJS dts warnings from bundled zod locales (#84592)
* build: suppress rolldown-plugin-dts CommonJS dts warnings from bundled zod locales

After bumping rolldown-plugin-dts to 0.25.1 (94ac563399), every
`pnpm build` emits a 'CommonJS dts' warning per zod `v4/locales/*.d.cts`
file because zod is intentionally inlined for global pnpm install
resolution (#78515) and tsdown's external option cannot be scoped to the
dts pass only. Filter the warning in the existing onLog suppression list
(same pattern as PLUGIN_TIMINGS / UNRESOLVED_IMPORT / EVAL) so other
rolldown-plugin-dts warnings remain visible.

* docs(changelog): move rolldown-dts entry into 2026.5.20 fixes
2026-05-20 17:20:47 -07:00
clawsweeper[bot]
5c4c6a4207 [codex] Fix macOS app copyright year (#84729)
Summary:
- The PR updates the macOS About settings copyright text to 2026, adds a changelog entry, and adjusts changed-check planning so non-macOS hosts without SwiftLint emit an explicit app-lint skip with matching test coverage.
- Reproducibility: yes. from source inspection: current main still renders the 2025 copyright literal in the m ...  launch the app locally, but the source path and source PR proof make the observable issue high-confidence.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8438…

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

Prepared head SHA: 26816c18d6
Review: https://github.com/openclaw/openclaw/pull/84729#issuecomment-4503529931

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-20 23:45:47 +00:00
Kevin Lin
b58572e283 fix(approval): route /approve through approval resolver (#84678) 2026-05-20 16:00:37 -07:00
Dallin Romney
4d47f9a4c0 test(secret-file): cover NickServ + account-level symlinks, narrow inspect catch (#84713)
Followup nits from the #84711 review:

- Narrow the inspectTokenFile catch in
  extensions/telegram/src/account-inspect.ts to FsSafeError so only
  fs-safe validation throws map to configured_unavailable; any other
  throw (programmer error, unexpected I/O) is rethrown.
- Add a regression test for the IRC NickServ password file symlink
  rejection path (extensions/irc/src/accounts.ts:118), paralleling the
  existing top-level passwordFile test.
- Add a regression test for the Telegram account-level tokenFile
  symlink rejection path (extensions/telegram/src/token.ts:149),
  paralleling the existing channel-level tokenFile test.

Behavior was already correct after #84711; this just locks coverage and
tightens the catch.
2026-05-20 15:35:52 -07:00
Dallin Romney
90fd26b602 fix(infra): restore symlink rejection in tryReadSecretFileSync (#84711)
* fix(infra): restore symlink rejection in tryReadSecretFileSync

The local wrapper added in 9e4eca00ff swallowed all errors from
@openclaw/fs-safe@0.2.7's tryReadSecretFileSync via a bare try/catch,
silently downgrading every rejectSymlink: true caller (Telegram, LINE,
Zalo, IRC, Nextcloud Talk credential files) to accept symlinked
credential files. It also broke the infra-state CI shard's symlink
expectation that #84595 had just realigned with the new fail-closed
upstream contract.

Restore the direct re-export so the upstream contract surfaces:
undefined for blank/missing/not-found, FsSafeError for symlink,
oversize, non-regular file, and hardlink validation failures.

* test(plugins): align stale symlink tests with fail-closed contract

5 token/account resolver tests still asserted the pre-fs-safe-0.2.7
"silent skip" behavior (token: "", source: "none") on rejected symlinks;
they passed only because the swallow-all wrapper in secret-file.ts hid
the throw. Restoring the upstream fail-closed contract surfaces the
throw, so update the tests to expect FsSafeError.

inspectTelegramAccount reports credential status (its return type has an
explicit configured_unavailable state for "configured but unreadable"),
so its callsite is the right boundary to catch the FsSafeError and map
it to configured_unavailable rather than letting the throw bubble.

Affected:
- extensions/zalo/src/token.test.ts
- extensions/line/src/accounts.test.ts
- extensions/telegram/src/token.test.ts
- extensions/irc/src/accounts.test.ts
- extensions/nextcloud-talk/src/setup.test.ts
- extensions/telegram/src/account-inspect.ts (catch + report status)
2026-05-20 15:21:13 -07:00
Peter Steinberger
3844513431 test: align release timeout budget expectations
(cherry picked from commit a185ca283a)
2026-05-20 22:38:43 +01:00
Peter Steinberger
6b52105b23 ci: extend stable release validation monitors
(cherry picked from commit ca3c3fca43)
2026-05-20 22:38:43 +01:00
Peter Steinberger
d786b4eb55 ci: preserve node path across setup action steps
(cherry picked from commit a6172a7d0e)
2026-05-20 22:38:43 +01:00
Peter Steinberger
1fdeee380e fix: preserve update compatibility host during release upgrades
(cherry picked from commit 2823725134)
2026-05-20 22:38:43 +01:00
Peter Steinberger
2e389b6a46 fix(update): prefer npm during post-core repair
(cherry picked from commit eab57ad8ad)
2026-05-20 22:38:43 +01:00
Peter Steinberger
f4dc9b1232 fix(update): defer legacy parent plugin repair
(cherry picked from commit 93c2d1ea99)
2026-05-20 22:38:43 +01:00
Peter Steinberger
aa687a08cd fix(update): adopt post-core plugin payloads
(cherry picked from commit 29faac2f9c)
2026-05-20 22:38:43 +01:00
Peter Steinberger
e57fa51412 fix(update): preserve post-core host version
(cherry picked from commit e8d8c5dd6f)
2026-05-20 22:38:42 +01:00
Peter Steinberger
3c3ef6067e fix(update): prefer existing npm plugins during repair
(cherry picked from commit 3743d6bdeb)
2026-05-20 22:38:42 +01:00
openclaw-release-bot
ec8e7003a6 chore(release): update appcast for 2026.5.19 2026-05-20 21:35:27 +00:00
Peter Steinberger
6c7fe58468 chore(release): refresh generated baselines 2026-05-20 21:59:52 +01:00
Peter Steinberger
7b9066120a chore(release): bump version to 2026.5.20 2026-05-20 21:58:56 +01:00
Gio Della-Libera
6e9d47bd12 fix(doctor): migrate invalid thinking formats (#84626) 2026-05-20 13:58:01 -07:00
Kevin Lin
9e4eca00ff fix(slack): normalize approval user ids (#84671)
* fix(slack): normalize approval user ids

* chore(openrouter): satisfy spread fallback lint

* fix(ci): unblock status and secret-file checks
2026-05-20 13:40:14 -07:00
Kevin Lin
404fd6d9ab fix(codex): bridge computer use elicitations 2026-05-20 13:39:11 -07:00
Peter Steinberger
6e7bd551f2 chore(deps): update whatsapp baileys 2026-05-20 21:36:39 +01:00
Zhaocun Sun
ca0fe884ff fix(cli): gate exported subcli descriptors (#84519)
Summary:
- This PR filters exported sub-CLI descriptors through the private-QA gate, centralizes that filter, adds regr ... ge, and carries small validation repairs in workspace glob and tunnel-timeout tests plus a changelog entry.
- Reproducibility: yes. Current-main source shows the raw SUB_CLI_DESCRIPTORS export can include qa while the helper surfaces filter it, and src/cli/argv.ts consumes that export for root command policy.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): gate exported subcli descriptors
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8451…

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

Prepared head SHA: ba197a6f30
Review: https://github.com/openclaw/openclaw/pull/84519#issuecomment-4496549642

Co-authored-by: Zhaocun <zhaocunsun@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-20 20:32:01 +00:00
Xu Xiang
d5cc0d53b7 fix(browser): honor image sanitization config for screenshots (#84595)
Summary:
- The branch threads `agents.defaults.imageMaxDimensionPx` into browser screenshot and labeled snapshot image results, adds regression coverage and a changelog entry, and includes small repair-pass type/lint cleanup.
- Reproducibility: yes. source-level reproduction is high confidence: current `main` calls `imageResultFromFil ...  both browser image-returning paths, while the shared sanitizer falls back to `1200px` without an override.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(browser): honor image sanitization config for screenshots
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8459…

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

Prepared head SHA: c01fde7990
Review: https://github.com/openclaw/openclaw/pull/84595#issuecomment-4499178477

Co-authored-by: Xu Xiang <xx205@outlook.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-20 20:09:32 +00:00
Peter Steinberger
1a7669bc63 fix: update fs-safe fallback dependency 2026-05-20 19:35:08 +01:00
Dallin Romney
447a3643c6 fix(errors): dedupe identical messages when traversing error .cause chain (#84556)
Merged via squash.

Prepared head SHA: 46aa27fa12
Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-20 21:26:14 +03:00
Neerav Makwana
950e5c8c50 fix(agents): credit delivered subagent completions (#84383) 2026-05-20 14:19:30 -04:00
Aayush Pratap Singh
0af55f971d fix: check billing errors before surfacing rate-limit message (#79489)
Merged via squash.

Prepared head SHA: 2ea757ce8c
Co-authored-by: aayushprsingh <172073271+aayushprsingh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-20 20:55:58 +03:00
Peter Steinberger
a13468320c fix: clarify pinned session model status 2026-05-20 15:59:24 +01:00
Peter Steinberger
c8a953af93 fix: keep cron final output over tool warnings 2026-05-20 14:50:50 +01:00
Alex Knight
ac69776330 Add OpenRouter provider routing params (#84579)
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-20 23:27:34 +10:00
Jason (Json)
48a14e41e2 feat(discord): add realtime voice bootstrap context
Add bounded realtime profile context for Discord realtime voice sessions.
Default to `IDENTITY.md`, `USER.md`, and `SOUL.md`; `voice.realtime.bootstrapContextFiles: []` disables the extra context.
Document the config/SDK surface and refresh generated metadata.

Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-20 14:13:59 +01:00
googlerest
32fbb9ff01 test(cli): cover parsePort edge cases (#84518)
Summary:
- The PR updates `src/cli/shared/parse-port.test.ts` to cover numeric strings, whitespace-padded strings, fractional strings, invalid suffixes, and safe-integer overflow for `parsePort`.
- Reproducibility: not applicable. This PR adds test coverage rather than reporting a failing runtime behavior. Source inspection confirms the current parser contract and the exact baseline coverage gap on main.

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

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

Prepared head SHA: 14213cc8f4
Review: https://github.com/openclaw/openclaw/pull/84518#issuecomment-4496552268

Co-authored-by: googlerest <127843198+googlerest@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-20 13:06:25 +00:00
Moeed Ahmed
9c00268914 fix: honour tool error suppression for mutating tools (#81561)
Merged via squash.

Prepared head SHA: 7462a862be
Co-authored-by: moeedahmed <5780040+moeedahmed@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-20 05:53:15 -07:00
Chunyue Wang
5d775122c1 fix(codex/command-account): respect explicit auth order over lastGood (#84412)
Fixes openclaw#84386. resolveActiveProfileId in extensions/codex/src/command-account.ts returned store.lastGood whenever that profile was still in the resolved order, ignoring rank, so /codex account marked the stale openai-codex:default profile as active after models auth login + models auth order set. Tracks whether the order came from an explicit operator source (store.order / config.auth.order, including the openai alias key), picks the first usable explicit-order profile, and returns undefined when no candidate is eligible so the display surfaces "no working credential" instead of marking a lower-ranked profile active. Runtime selection via resolveCodexAppServerAuthProfileId is unchanged.
2026-05-20 20:02:28 +08:00
Peter Steinberger
99c88629c3 fix(macos): update embedded Peekaboo bridge 2026-05-20 12:58:56 +01:00
Dallin Romney
9a6744baba perf(plugins): scan-scoped package.json cache in discovery (#84302)
* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to #75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
2026-05-20 04:57:45 -07:00
Jason (Json)
befb0f3d39 feat(discord): follow configured users in voice
Summary:
- Adds Discord voice followUsers/followUsersEnabled config, metadata, docs, and changelog coverage.
- Makes Discord voice follow configured users across joins, moves, disconnects, admin moves, handoff, bounded reconciliation, transient REST failures, destroy cleanup, and DAVE recovery.
- Adds focused Discord voice/config regression tests and refreshes generated config docs metadata.

Verification:
- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/discord/src/voice/manager.e2e.test.ts
- node scripts/run-vitest.mjs run --config test/vitest/vitest.extension-discord.config.ts extensions/discord/src/config-schema.test.ts
- pnpm config:channels:check
- pnpm config:docs:check
- pnpm config:schema:check
- pnpm exec oxfmt --check --threads=1 docs/channels/discord.md extensions/discord/src/voice/manager.ts extensions/discord/src/voice/manager.e2e.test.ts src/config/bundled-channel-config-metadata.generated.ts CHANGELOG.md
- git diff --check
- pnpm build
- pnpm check:test-types
- Mac Studio config validate + gateway:watch proof on cf67023fdf; Discord provider started and gateway ready
- Autoreview passed after two actionable findings were fixed

CI notes:
- PR-specific proof is green: check-docs, config-boundary, real behavior proof, check-test-types, OpenGrep, CodeQL, no-tabs, security-fast.
- Remaining broad CI reds match current main failures/noise on unrelated fs-safe Python helper, Windows ACL locale, managed media staging, and dependency guardrail surfaces.

Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-20 12:49:15 +01:00
Peter Steinberger
d1470360c4 fix: stabilize mac app packaging 2026-05-20 07:35:11 -04:00
Peter Steinberger
94ac563399 build: update dependencies 2026-05-20 12:08:17 +01:00
Gio Della-Libera
cbf72e5e26 feat(policy): add channel conformance checks (#80407)
Summary:
- Add the bundled Policy plugin with policy-backed doctor checks for channel conformance.
- Add `openclaw policy check` attestations, accepted-attestation drift checks, and opt-in doctor repair.
- Add policy CLI docs, generated plugin inventory/reference docs, and changelog credit.

Verification:
- node --import tsx scripts/sync-plugin-versions.ts --check
- pnpm plugins:inventory:check
- pnpm docs:list
- git diff --check origin/main..HEAD
- node scripts/run-vitest.mjs extensions/policy/src/policy-state.test.ts extensions/policy/src/cli.test.ts extensions/policy/src/doctor/register.test.ts src/flows/bundled-health-checks.test.ts src/cli/program/register.maintenance.test.ts
- codex review --uncommitted; accepted finding fixed, reran clean
- codex review --commit HEAD
- GitHub CI for 4e09b067f4: CI, Workflow Sanity, CodeQL, CodeQL Critical Quality, OpenGrep PR Diff, Real behavior proof, Dependency Change Awareness all green; reran failed Windows Node setup job successfully

Co-authored-by: Gio Della-Libera <giodl73@gmail.com>
Co-authored-by: Gio Della-Libera <giodl@microsoft.com>
2026-05-20 11:50:21 +01:00
Peter Steinberger
9c5e8eb495 docs: note GitHub paste preflight 2026-05-20 11:45:19 +01:00
Peter Steinberger
3c8050c44c docs: keep developer tooling out of release tweets 2026-05-20 11:39:46 +01:00
Peter Steinberger
45930457ca docs: keep qa proof out of release tweets 2026-05-20 11:37:51 +01:00
Peter Steinberger
167e73cd5f build: bump bundled Codex harness to 0.132.0 2026-05-20 10:38:35 +01:00
yaoyi1222
110042d840 fix(cron-cli): bound loadCronJobForShow pagination (#83856) (#83989)
Summary:
- Adds a 50-page and advancing-`nextOffset` guard to `loadCronJobForShow`, exports that helper for regression tests, and adds an unreleased changelog entry.
- Reproducibility: yes. Current main is source-reproducible because `loadCronJobForShow` loops while `hasMore` ... ed numeric `nextOffset`; the PR discussion also includes terminal before/after proof for the same CLI path.

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

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

Prepared head SHA: 7828b4bdae
Review: https://github.com/openclaw/openclaw/pull/83989#issuecomment-4484474655

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-20 07:19:38 +00:00
Sarah Fortune
ea8f4ebb4d fix(config): accept execApprovals.enabled="auto" in zod schema 2026-05-20 00:16:41 -07:00
Pavan Kumar Gondhi
0c67dc7f82 fix(mattermost): fail closed on missing channel type [AI] (#84091)
* fix: fail closed on missing Mattermost channel type

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-20 12:33:55 +05:30
Pavan Kumar Gondhi
e98760a1bf Recheck rebuilt system.run argv [AI] (#84090)
* fix: recheck rebuilt system run argv

* docs: add changelog entry for PR merge
2026-05-20 12:30:26 +05:30
Gio Della-Libera
67c12e0368 fix(cli): use active node for startup bench scripts (#84451) 2026-05-19 23:21:26 -07:00
Ayaan Zaidi
989e53c20d fix(android): address overhaul review findings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
bbcac0019b refactor(android): make overhaul UI canonical 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
64b6cafcaa test(android): update gateway hello callback fixtures 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
98f2e568b3 refactor(android): centralize v2 separated list rows 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
c289e3ea87 fix(android): expand v2 settings toggle hit areas 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
c0ac4564f7 fix(android): clarify v2 voice settings action 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
07b28a6dd6 fix(android): gate v2 cron job save action 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
85ef8fb975 fix(android): request v2 capability permissions 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
c885a1c243 feat(android): wire v2 chat image attachments 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
dd772307a3 fix(android): tighten v2 navigation affordances 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
43b03b7621 fix(android): expand v2 model catalog groups 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
9868f4cf29 fix(android): align v2 control affordances 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
d3cf65eb14 fix(android): remove dead v2 chat controls 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
3aefd355c4 fix(android): wire v2 onboarding actions 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
8d492637af fix(android): wire v2 navigation controls 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
5de8f8e8a9 feat(android): polish v2 voice surfaces 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
338a0062c4 feat(android): add v2 chat starters 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
69e646f680 fix(android): prevent provider setup button overlap 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
d41f595c75 feat(android): polish v2 provider setup 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
a9669c0f9f feat(android): polish v2 overview navigation 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
2294c28355 style(android): refine v2 touch rhythm 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
499ccd1522 feat(android): add v2 cron job editor 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
de195645f9 refactor(android): reuse v2 list primitives 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
357e3ecc65 feat(android): add v2 about update status 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
f359299df4 feat(android): add v2 health logs 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
3d5be4c5a9 feat(android): add v2 dreaming settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
6db000630c feat(android): add v2 channels settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
fd05179d0a feat(android): add v2 canvas settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
e067203b22 feat(android): add v2 nodes devices settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
817ca4bf65 feat(android): add v2 skills settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
41175edd98 feat(android): add v2 usage settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
b6e04fa6a2 feat(android): add v2 cron jobs settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
efe7393064 feat(android): add v2 approvals settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
d7a90ebea6 feat(android): add v2 agents settings 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
e5cd050e51 refactor(android): split v2 shell screens 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
aca22366f2 feat(android): add v2 settings detail screens 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
e8a90a03df feat(android): add v2 command palette 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
c842f542cd feat(android): restore readable v2 typography 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
415a338dc6 feat(android): tighten voice transcript cards 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
ceb7e04108 feat(android): keep overview modules honest 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
ee6c42945a feat(android): tighten dictation fidelity 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
abf70ac04e feat(android): tighten talk session fidelity 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
2ce12552bb feat(android): tighten settings fidelity 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
289eea04d0 feat(android): tighten provider model fidelity 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
ac28341ebf feat(android): tighten voice mockup fidelity 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
ca4264202e feat(android): tighten chat mockup fidelity 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
af5e0b26ef feat(android): tighten overview density 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
f4cc4655ef feat(android): tighten sessions density 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
5a82e4aa19 feat(android): tighten voice hub density 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
eff8b41fb0 feat(android): tighten chat density 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
d593f5b062 feat(android): add providers models surface 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
6af2fa4ec3 feat(android): tighten settings screen 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
6db48f70e8 feat(android): tighten chat chrome 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
818aa36f7c feat(android): add focused dictation 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
7d0bb236f2 feat(android): add focused talk session 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
1882984380 feat(android): tighten voice hub 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
9342deeae3 feat(android): tighten sessions experience 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
26352f5a13 feat(android): tighten overview experience 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
ff50cdf396 feat(android): rebuild chat experience 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
7e0584579c feat(android): overhaul gateway onboarding 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
5200e8a436 feat(android): add v2 app shell 2026-05-20 10:54:08 +05:30
Ayaan Zaidi
ac43f47820 feat(android): add v2 design system 2026-05-20 10:54:08 +05:30
pashpashpash
448eb36f75 Revert "fix: prompt Codex to send visible channel replies (#84397)" (#84442)
This reverts commit 47eb4ca14f.
2026-05-20 14:20:56 +09:00
clawsweeper[bot]
65030f3164 fix(pi): keep message-tool delivery in session lock (#84437)
Summary:
- The replacement branch adds an owned transcript write context around Pi prompt-time delivery mirror appends and a message-tool-only terminal hook, with focused tests and a changelog entry.
- Reproducibility: yes. the source PR includes before/after redacted live Discord logs for a message-tool-only ... ession-lock and transcript append code. I did not rerun the live Discord scenario in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(pi): keep message-tool delivery in session lock

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

Prepared head SHA: f16678175c
Review: https://github.com/openclaw/openclaw/pull/84437#issuecomment-4494545360

Co-authored-by: Andrew Meyer <andrewmeyer@andrews-air.lan>
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-20 05:20:03 +00:00
clawsweeper[bot]
7811e313b3 fix(channels): suppress verbose failed-tool dumps (#84354)
Summary:
- The branch suppresses regular verbose failed-tool raw output after final replies across shared dispatch, Codex, Telegram, and Discord paths, keeps raw detail under `/verbose full`, and updates tests, docs, and changelog.
- Reproducibility: yes. The current-main source path and supplied before screenshot show failed text-only tool ... ping after a final reply; I did not rerun a live Telegram or Discord reproduction in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix message-tool delivery gating
- PR branch already contained follow-up commit before automerge: fix(channels): keep verbose tool failures compact
- PR branch already contained follow-up commit before automerge: fix(channels): suppress in-flight final progress
- PR branch already contained follow-up commit before automerge: fix(replies): suppress failed tool dumps in message-only mode
- PR branch already contained follow-up commit before automerge: fix(replies): avoid duplicate exec failure warnings
- PR branch already contained follow-up commit before automerge: Revert "fix(replies): avoid duplicate exec failure warnings"

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

Prepared head SHA: d15ae6951b
Review: https://github.com/openclaw/openclaw/pull/84354#issuecomment-4493007024

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-20 05:09:38 +00:00
Galin Iliev
ddf9fbed34 fix(gateway): expose runtime version in gateway status
Closes #56222
2026-05-19 22:09:14 -07:00
WhatsSkiLL
29f8715f05 [AI-assisted] fix(cron): preserve legacy array stores (#84433)
Summary:
- The PR changes cron store loading to normalize legacy top-level array `jobs.json` files into the versioned store shape and adds store, service, doctor, gateway tests plus a changelog entry.
- Reproducibility: yes. Current `main` clearly maps a top-level parsed array to `{}` before reading `.jobs`, and the PR body supplies before/after runtime output for the load/add/save path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: [AI-assisted] fix(cron): preserve legacy array stores

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

Prepared head SHA: 446014b4c1
Review: https://github.com/openclaw/openclaw/pull/84433#issuecomment-4494478724

Co-authored-by: JARVIS-Glasses <284122573+JARVIS-Glasses@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-20 05:01:24 +00:00
Peter Steinberger
5c39e0019d ci: harden pnpm setup node selection 2026-05-20 05:34:20 +01:00
Josh Avant
47eb4ca14f fix: prompt Codex to send visible channel replies (#84397)
* fix: prompt codex to send visible channel replies

* chore: add codex reply changelog entry

* test: refresh codex prompt snapshots
2026-05-19 23:29:41 -05:00
Galin Iliev
9eee202a69 fix(cron): isolate main-session cron wake lanes (#82767)
* fix(cron): isolate main-session cron wake lanes

* test(cron): expect dedicated main cron lanes

* fix(cron): route global main cron wakes

* docs(changelog): note cron main-session lane fix

---------

Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
Co-authored-by: Galin Iliev <5711535+galiniliev@users.noreply.github.com>
2026-05-19 21:12:28 -07:00
Josh Avant
a54c73687f fix(agents): provenance-bound Codex reasoning replay (#84367)
* fix(agents): recover stale Codex encrypted reasoning replay

* docs(changelog): note Codex encrypted replay recovery

* fix(agents): bind Codex reasoning replay provenance

* fix(agents): pin codex reasoning replay provenance
2026-05-19 23:05:19 -05:00
clawsweeper[bot]
a57ab2448f docs(imessage): warn that cliPath wrappers must stream JSON-RPC stdio (#84330) (#84420)
Summary:
- The PR adds a Warning block to `docs/channels/imessage.md` explaining that iMessage `cliPath` wrappers and SSH proxies must stream long-lived JSON-RPC stdin/stdout incrementally.
- Reproducibility: not applicable. for this docs-only PR. Source inspection verifies the runtime uses long-lived line-framed stdio, and current main lacks the operator warning being added.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs(imessage): warn that cliPath wrappers must stream JSON-RPC stdio…

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

Prepared head SHA: a371ee998e
Review: https://github.com/openclaw/openclaw/pull/84420#issuecomment-4494313781

Co-authored-by: HCL <chenglunhu@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-20 03:51:32 +00:00
Galin Iliev
c982358753 fix: dedupe OpenAI strict schema downgrade diagnostics (#82933)
* fix: dedupe openai strict schema downgrade logs

* test: align openai transport helper export

* test: cover openai downgrade log behavior

* docs: note openai downgrade diagnostic dedupe

---------

Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
2026-05-19 20:48:26 -07:00
Dave Morin
18a514e39e docs: align xai code execution auth docs (#84416) 2026-05-19 20:39:04 -07:00
Andy Ye
33fc2375f8 fix(anthropic): preserve configured Claude image capability (#84180)
Summary:
- The PR routes model-list row construction through provider-owned resolved-model normalization for configured ...  rows, adds Anthropic regression coverage, updates focused test mocks/fixtures, and adds a changelog entry.
- Reproducibility: yes. at source level: current main renders configured/default list rows without calling the ... ty is restored. The PR body also supplies terminal output showing the fixed configured row as `text+image`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(models): update forward compat agent-scope mock
- PR branch already contained follow-up commit before automerge: test(models): isolate provider catalog row tests
- PR branch already contained follow-up commit before automerge: test(models): complete provider catalog fixtures
- PR branch already contained follow-up commit before automerge: Merge remote-tracking branch 'upstream/main' into fix/anthropic-confi…
- PR branch already contained follow-up commit before automerge: test(workflows): match alpha concurrency rules

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

Prepared head SHA: 7a1caa7dff
Review: https://github.com/openclaw/openclaw/pull/84180#issuecomment-4489015944

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-20 03:32:12 +00:00
Galin Iliev
ad925bd43b Preserve AGENTS.md policy during bootstrap truncation (#82921)
Fixes #82920
2026-05-19 20:25:27 -07:00
Andy Ye
9108ae0114 Include delivery errors in subagent announce give-up logs (#84281)
* Include delivery error in subagent announce give-up logs

* test(agents): type announce delivery error response
2026-05-19 23:22:48 -04:00
Jason (Json)
2ab3a4e422 Filter heartbeat response-tool transcript artifacts (#83477)
Summary:
- This PR replaces pair-only heartbeat filtering with span-based filtering before embedded-runner prompt assem ... ession coverage and a changelog entry, and updates the LINE command type to use the SDK command definition.
- Reproducibility: yes. from source and report evidence: current main only removes immediate heartbeat prompt/ ...  body supplies same-session terminal proof and a commenter supplied a matching Discord gateway observation.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: filter heartbeat transcript artifacts
- PR branch already contained follow-up commit before automerge: fix: clean up heartbeat filter lint
- PR branch already contained follow-up commit before automerge: fix: keep line entry on channel SDK
- PR branch already contained follow-up commit before automerge: fix: filter heartbeat response text transcript shapes
- PR branch already contained follow-up commit before automerge: Filter heartbeat response-tool transcript artifacts

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

Prepared head SHA: e019c74bb5
Review: https://github.com/openclaw/openclaw/pull/83477#issuecomment-4475062400

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@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-20 03:19:35 +00:00
Galin Iliev
5d799c2d20 fix: yield diagnostic event drains (#82937)
Summary:
- The branch caps async diagnostic drains at 100 events per turn, adds pending/full-drain diagnostic helpers,  ... rminal diagnostics to inspect pending events, and adds regression coverage plus changelog/baseline updates.
- Reproducibility: yes. from source inspection. Current main drains the entire async diagnostic queue in one s ... ck, and the PR body supplies a focused 250-event after-fix probe showing 100/200/250 delivery across turns.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: yield diagnostic event drains

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

Prepared head SHA: 95610934cd
Review: https://github.com/openclaw/openclaw/pull/82937#issuecomment-4469498220

Co-authored-by: Galin Iliev <galini@microsoft.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-20 02:55:17 +00:00
Jiaming Guo
125f0c31dd fix(msteams): mark external system events as non-owner
Marks skipped and supplemental Microsoft Teams system events as non-owner/untrusted while preserving active primary message dispatch behavior.

Verified before merge:
- PR was open, not draft, mergeable, and clean against main
- Matched head: 4f79f46205
- GitHub checks passed, including Real behavior proof, auto-response, build artifacts, type/lint checks, channel/runtime critical quality checks, and security-fast
- ClawSweeper marked proof sufficient with no concrete contributor-facing blocker remaining

Co-authored-by: GuoJiaming <804436395@qq.com>
2026-05-19 21:48:17 -05:00
Josh Avant
e1c1c57242 Fix node approval scope requests (#84392)
* fix(cli): request node approval scopes

* docs(changelog): note node approval scope fix
2026-05-19 21:47:10 -05:00
Peter Steinberger
0556ac0291 fix(update): repair plugins for legacy updater doctors 2026-05-20 03:41:45 +01:00
clawsweeper[bot]
eb814b0216 Fix Codex image generation tool timeout (#84369)
Summary:
- The branch gives Codex `image_generate` dynamic-tool calls a 120s default watchdog in main and side-thread paths and updates docs, tests, and changelog.
- Reproducibility: yes. Source inspection on current main shows unconfigured Codex `image_generate` calls fall ... -tool default, and the linked source PR includes live Gateway before/after output for the timeout behavior.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8425…
- PR branch already contained follow-up commit before automerge: Fix Codex image generation tool timeout

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

Prepared head SHA: 10c7f87023
Review: https://github.com/openclaw/openclaw/pull/84369#issuecomment-4493288493

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: moritzmmayerhofer <254141390+moritzmmayerhofer@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-20 02:29:49 +00:00
Peter Steinberger
a002c416c7 fix(update): omit compatibility host env when package version is missing 2026-05-20 02:46:27 +01:00
Peter Steinberger
fd790e2977 chore(release): refresh generated release baselines 2026-05-20 02:46:27 +01:00
Peter Steinberger
6b82eaa2cd fix(update): carry candidate plugin API version through doctor 2026-05-20 02:36:52 +01:00
Gio Della-Libera
70e51b81cf fix(doctor): preserve unknown web search records (#83315)
* fix(doctor): preserve unknown web search records

* fix(doctor): filter dangerous web search keys

* fix(config): preserve extensible web search settings

* fix(config): keep legacy web search validation strict

* fix(config): reject blocked web search keys
2026-05-19 18:35:44 -07:00
clawsweeper[bot]
0e2a06ae10 fix(code-mode): sharpen exec tool description so models stop wasting turns rediscovering constraints (#84368)
Summary:
- The PR updates the code-mode exec tool description, adds regression coverage for the model-visible constraints, and records the fix in the changelog.
- Reproducibility: yes. at source level: current main's exec schema omits constraints that the current code-mo ...  also includes a live before/after recitation path showing the model receives the changed tool description.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(code-mode): cover exec tool guidance
- PR branch already contained follow-up commit before automerge: fix(code-mode): sharpen exec tool description so models stop wasting …

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

Prepared head SHA: 8ff85071ce
Review: https://github.com/openclaw/openclaw/pull/84368#issuecomment-4493273853

Co-authored-by: Kaspre <kaspre@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-20 01:25:04 +00:00
clawsweeper[bot]
6048cd43a5 fix(cron): keep recovered tool warnings diagnostic (#84308)
Summary:
- The PR threads middleware tool-error metadata into reply payloads, teaches cron outcome and diagnostics code to keep marked recovered warnings non-fatal, and adds focused regression coverage plus a changelog entry.
- Reproducibility: yes. Source inspection shows current main lacks a non-terminal recovered-warning path in cr ... fication, and the linked source PR includes a terminal runtime probe for the affected cron payload outcome.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cron): keep recovered tool warnings diagnostic

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

Prepared head SHA: 8b8a36e912
Review: https://github.com/openclaw/openclaw/pull/84308#issuecomment-4491925358

Co-authored-by: abnershang <abner.shang@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-20 01:19:52 +00:00
Peter Steinberger
d7896ed4c9 ci: retry release artifact downloads 2026-05-20 01:59:34 +01:00
Josh Avant
f6de2b3885 Fix Anthropic CLI auth routing for shorthand refs (#84374)
* Fix Anthropic CLI auth routing

* Add changelog for Anthropic CLI routing
2026-05-19 19:58:07 -05:00
Peter Steinberger
2a01fbb56c ci: keep ClawHub advisory for alpha publish 2026-05-20 01:57:00 +01:00
clawsweeper[bot]
7f8141ead9 fix(cron): use structured denial signals (#84311)
Summary:
- The PR changes isolated cron denial handling to use structured embedded tool-error metadata, preserves node-host denial wrappers, and updates cron docs, changelog, and focused regression tests.
- Reproducibility: yes. for source-level reproduction: current main scans cron summary, output, synthesized te ... denial tokens and promotes matches into fatal cron state. I did not execute tests in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cron): normalize node denial wrappers
- PR branch already contained follow-up commit before automerge: fix(cron): use structured denial signals
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8406…

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

Prepared head SHA: 047622fe8d
Review: https://github.com/openclaw/openclaw/pull/84311#issuecomment-4491946986

Co-authored-by: abnershang <abner.shang@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: Abner Shang <75654486+abnershang@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-20 00:49:49 +00:00
clawsweeper[bot]
ab7aa88ef2 gateway: use identity.name in agent summaries when name is unset (#84355)
Summary:
- The PR updates Gateway agent summary builders to use `agents.list[].identity.name` when explicit `agents.list[].name` is absent, adds focused gateway regression tests, and records a changelog fix.
- Reproducibility: yes. Current main can be source-reproduced: both gateway summary builders set top-level `na ... list[].name`, so identity-only configured agents have no summary name for consumers that read `agent.name`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(gateway): cover missing agent summary names
- PR branch already contained follow-up commit before automerge: fix(gateway): remove stale name fallback import
- PR branch already contained follow-up commit before automerge: gateway: use identity.name in agent summaries when name is unset

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

Prepared head SHA: 9f7024f55c
Review: https://github.com/openclaw/openclaw/pull/84355#issuecomment-4493008710

Co-authored-by: luoyanglang <hanwanlonga@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-20 00:45:08 +00:00
Peter Steinberger
4408e60c31 test(codex): avoid provider normalization in sandbox tool test 2026-05-20 01:38:37 +01:00
clawsweeper[bot]
165cc581cd fix(discord): preserve streamed replies after tool warnings (#84169)
* fix(discord): preserve previews after tool warnings

* fix(discord): preserve streamed replies after tool warnings

* test(discord): cover progress warning finalization

---------

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-19 19:36:28 -05:00
clawsweeper[bot]
ff5354ee4f fix(twitch): export clearRegistryForTest for cross-test isolation (#83887) (#84309)
Summary:
- The PR adds an async test-only Twitch client-manager registry reset helper, a focused registry isolation test, and an Unreleased changelog entry.
- Reproducibility: yes. Source inspection shows getOrCreateClientManager() returns the cached module-level manager for the same account id, and the repo’s Vitest configuration is explicitly non-isolated.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(twitch): export clearRegistryForTest for cross-test isolation (#8…

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

Prepared head SHA: 38c3fadc91
Review: https://github.com/openclaw/openclaw/pull/84309#issuecomment-4491930986

Co-authored-by: HCL <chenglunhu@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-20 00:26:21 +00:00
Josh Avant
00da318350 fix: constrain wildcard subagent targets (#84357)
* fix subagent wildcard targets

* add changelog for subagent wildcard fix
2026-05-19 19:21:13 -05:00
Peter Steinberger
eea71708ac test(release): update workflow concurrency expectations 2026-05-20 01:16:43 +01:00
Peter Steinberger
79197b3196 ci(release): stabilize beta validation gates 2026-05-20 00:53:32 +01:00
Peter Steinberger
d0bc520de8 test(plugins): update prerelease shard expectations 2026-05-20 00:53:32 +01:00
pash-openai
e0d1a2a9b9 Move Codex soul context to developer instructions (#84331)
* Move Codex soul context to developer instructions

* Route Codex workspace context by lifetime

* Refresh Codex prompt snapshots

* Update prompt snapshot expectations

* Fix Codex workspace context diagnostics
2026-05-19 16:47:32 -07:00
Gio Della-Libera
68c5a892d0 fix(config): dedupe missing official plugin warnings (#84227) 2026-05-19 16:44:21 -07:00
Peter Steinberger
375afbad2d ci: cancel duplicate Tideclaw alpha release runs 2026-05-20 00:42:39 +01:00
Dave Morin
a00e7d3898 docs: clarify xai oauth setup (#84350) 2026-05-19 16:33:18 -07:00
100menotu001
1bb0ebab0b Expose messageId in message CLI JSON output (#84191)
Summary:
- The PR promotes direct or nested send receipt IDs into `openclaw message send --json`, adds a focused command test, and adds a changelog entry.
- Reproducibility: yes. at source level. Current main serializes only the raw payload while send receipts can carry `payload.result.messageId`; I did not execute the CLI in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(cli): fix message json payload type
- PR branch already contained follow-up commit before automerge: chore: retrigger PR checks
- PR branch already contained follow-up commit before automerge: Expose messageId in message CLI JSON output

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

Prepared head SHA: 9eba815fcc
Review: https://github.com/openclaw/openclaw/pull/84191#issuecomment-4489100591

Co-authored-by: OpenClaw Contributor <100menotu001@users.noreply.github.com>
Co-authored-by: Craig <froelich@craigs.mac.studio.froho>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-19 23:30:24 +00:00
clawsweeper[bot]
97aa0c8c01 Preserve disabled Discord presentation buttons (#84312)
Summary:
- Adds `disabled` to the message presentation button schema, advertises Discord disabled-button support, prese ... through Discord component mapping and link serialization, and adds regression tests plus a changelog entry.
- Reproducibility: yes. Source inspection on current main shows `disabled` exists in the runtime type but is a ... rtised in Discord capabilities, dropped by adaptation, and omitted from Discord mapping/link serialization.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(discord): advertise disabled presentation support
- PR branch already contained follow-up commit before automerge: fix(discord): preserve disabled link buttons
- PR branch already contained follow-up commit before automerge: Preserve disabled Discord presentation buttons

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

Prepared head SHA: 9bb60d8cbf
Review: https://github.com/openclaw/openclaw/pull/84312#issuecomment-4491983845

Co-authored-by: OpenClaw Contributor <100menotu001@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-19 23:29:48 +00:00
clawsweeper[bot]
e61fe1c539 feat(ui): tool name style in usage panel (#84310)
Summary:
- This PR adds scoped truncation and hover titles to usage-panel context-breakdown names and adds a changelog entry crediting the source PR.
- Reproducibility: yes. at source/proof level: current main renders long context names without truncation or t ... he overflow before and ellipsis/tooltip after. I did not run a live browser session in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: feat(ui): tool name style in usage panel

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

Prepared head SHA: 396e405b3b
Review: https://github.com/openclaw/openclaw/pull/84310#issuecomment-4491942108

Co-authored-by: Rain120 <1085131904@qq.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-19 23:25:00 +00:00
Dallin Romney
88d8d6af93 perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts (#84283)
* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to #75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
2026-05-19 16:22:30 -07:00
Thiago Costa
b9a2c11521 fix(clawhub): preserve base URL path prefix [AI-assisted] (#83982)
Summary:
- The PR updates `src/infra/clawhub.ts` URL joining, adds a path-prefix regression test in `src/infra/clawhub.test.ts`, and adds a changelog bullet.
- Reproducibility: yes. Source inspection plus a direct Node URL check show current main drops `/clawhub` when resolving a leading-slash API path against a prefixed base URL.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(clawhub): preserve base URL path prefix [AI-assisted]

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

Prepared head SHA: 7bb2cb8764
Review: https://github.com/openclaw/openclaw/pull/83982#issuecomment-4484348274

Co-authored-by: Thiago Costa <thiago12_fera@hotmail.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-19 23:22:01 +00:00
Kevin Lin
ecb6da9289 docs: move codex native plugins nav (#84341) 2026-05-19 15:27:51 -07:00
Eva
5c9a8f33b3 fix(plugins): add default timeout for before_compaction/after_compaction hooks (#84153)
Merged via squash.

Prepared head SHA: 41fa5fed37
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-19 15:14:15 -07:00
Extra Small
d7b23d5bca fix(cli): honor --no-prefix-cwd in acp
Fixes #83901. Honors Commander negated option handling for ACP prompt-prefix forwarding and adds focused CLI regression coverage. Verified with Crabbox AWS cbx_1689d0ad78e9 run run_a406418db6fe and Real behavior proof run 26127392365.
2026-05-19 15:10:17 -07:00
Eva
a059309a9f fix(agents): bound plugin-owned context-engine compaction with a safety timeout (#84083)
Merged via squash.

Prepared head SHA: 9121a1a5ea
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-19 14:49:00 -07:00
Andy Ye
3bc728eaa9 fix(twitch): register chat intent for refreshing auth (#83750)
Summary:
- The PR registers Twitch refreshing-token users with Twurple's chat intent and adds regression coverage for that contract.
- Reproducibility: yes. by source and dependency contract. Current main does not register the chat intent, and ...  RefreshingAuthProvider only resolves getAccessTokenForIntent('chat') when that intent is mapped to a user.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(twitch): register chat intent for refreshing auth

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

Prepared head SHA: 1fdadcff04
Review: https://github.com/openclaw/openclaw/pull/83750#issuecomment-4481748086

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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>
2026-05-19 21:20:25 +00:00
Alex Knight
c81271ee6e Fix managed Gateway updates across CLI and service Node skew (#84043)
Summary:
- The PR pins managed Gateway package updates, runtime preflight, post-install doctor, post-core update, service refresh, and restart follow-ups to the Node binary and package root baked into the Gateway service.
- Reproducibility: yes. source-level. Current main validates and follows up with the shell process Node in the ...  body provides a concrete two-Node Docker reproduction, though I did not execute it in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(update): detect service node mismatch even when package roots match
- PR branch already contained follow-up commit before automerge: fix(update): pin package install to service root when nodes differ wi…

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

Prepared head SHA: 5607e441f6
Review: https://github.com/openclaw/openclaw/pull/84043#issuecomment-4485613931

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
Co-authored-by: Alex Knight <aknight@atlassian.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: amknight
Co-authored-by: amknight <15041791+amknight@users.noreply.github.com>
2026-05-19 20:44:29 +00:00
Dallin Romney
3d96111a5a Revert "perf(plugins): extend discovery threading to loader, manifest registr…" (#84278)
This reverts commit f5f0b2c7c9.
2026-05-19 12:35:27 -07:00
Dallin Romney
f5f0b2c7c9 perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts (#84258)
Follow-up to #75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
2026-05-19 12:35:01 -07:00
Sebastien Tardif
28beea9e88 perf(plugins): thread explicit discovery to avoid redundant filesystem walks (#75451)
Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes #82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-19 12:15:33 -07:00
Nimrod Gutman
edd7c8e4a1 [codex] fix iOS TestFlight release archive (#84255)
Merged via squash.

Prepared head SHA: c59a81a4bf
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-19 22:04:33 +03:00
Kevin Lin
9b97e1ef2f feat(codex): add plugin list enable disable commands (#83293)
* feat(codex): add plugin enable disable list commands

* fix(codex): escape plugin management output

* test(codex): narrow plugin command coverage

* fix(codex): gate plugin management writes

* test(codex): type command plugin context

* docs(codex): document plugin management commands
2026-05-19 11:39:50 -07:00
Nimrod Gutman
94d8391c03 [codex] restore QR bootstrap operator handoff (#83684)
Merged via squash.

Prepared head SHA: 2dc955cfb7
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-19 20:59:09 +03:00
Md. Al-Mosabbir Rakib
e00cb664ad docs: clarify /new vs /reset semantics in slash-commands (#81073)
Summary:
- The PR changes one bullet in `docs/tools/slash-commands.md` to distinguish `/new` from `/reset` and remove the misleading alias wording.
- Reproducibility: yes. Reading current main reproduces the misleading docs line at `docs/tools/slash-commands.md:127`, and adjacent source/tests show `/new` and `/reset` take different paths in the Control UI.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs/slash-commands: drop inaccurate Control UI/ACP cross-reference (…
- PR branch already contained follow-up commit before automerge: Merge branch 'main' into docs/fix-reset-alias-misleading

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

Prepared head SHA: bb92b6050a
Review: https://github.com/openclaw/openclaw/pull/81073#issuecomment-4432165259

Co-authored-by: Md. Al-Mosabbir Rakib <mrakib50.cse@gmail.com>
Co-authored-by: Md. Al-Mosabbir Rakib <34891461+mosabbirrakib@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-19 17:51:48 +00:00
samzong
323c9760d3 [Docs] Document gateway benchmark probes (#83866)
Summary:
- The PR updates `docs/cli/gateway.md` and `docs/reference/test.md` to document Gateway startup/restart benchmark prerequisites, commands, case IDs, probes, output semantics, and platform limits.
- Reproducibility: not applicable. as a runtime bug; docs correctness is source-checkable against the benchmar ... ipts, and readiness source. The current PR head corrected the earlier startup-hook readiness wording issue.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs(gateway): correct benchmark readiness wording

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

Prepared head SHA: 5bd0f6c463
Review: https://github.com/openclaw/openclaw/pull/83866#issuecomment-4483820005

Co-authored-by: samzong <samzong.lu@gmail.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-19 17:50:36 +00:00
Ayaan Zaidi
edcf862da5 fix(mantis): finish interrupted telegram proof sessions 2026-05-19 22:59:50 +05:30
Shakker
78d226bb3b docs: add provider timeout changelog entry 2026-05-19 17:16:48 +01:00
Shakker
6899eff155 test: cover provider timeout bare hostnames 2026-05-19 17:16:48 +01:00
yujiawei
9e9feb52f4 fix(llm-idle-timeout): honor models.providers.<id>.timeoutSeconds for cloud providers
The schema.help text for `models.providers.*.timeoutSeconds` documents the
key as the user-facing knob for "slow local or self-hosted model servers".
In practice the option is also the only configurable lever for the LLM
idle/first-token watchdog. However `resolveLlmIdleTimeoutMs` was still
running the explicit provider timeout through `clampImplicitTimeoutMs`,
clamping it back down to the implicit ~120s `DEFAULT_LLM_IDLE_TIMEOUT_MS`
ceiling for any non-cron, non-local provider.

Consequence (matches #77744 and #78361):
- User sets `models.providers.llamacpp.timeoutSeconds: 14400` (or 600 for
  a slow Gemini/Opus turn with a large tool payload).
- Hot reload accepts the value, runtime resolves
  `modelRequestTimeoutMs = 14_400_000`.
- Idle watchdog still trips at ~120s with
  "LLM idle timeout (120s): no response from model", aborting an
  otherwise-healthy upstream that is mid-prefill or buffering thinking
  tokens.

Fix: when the caller passes an explicit `modelRequestTimeoutMs`
(sourced from `models.providers.<id>.timeoutSeconds` /
`model.requestTimeoutMs`), treat it as a deliberate ceiling for cloud
providers too. The run-timeout / agent-timeout bounds still apply via
`timeoutBounds`, so a shorter explicit run timeout always wins. The
implicit default watchdog still kicks in when the user has not set a
provider timeout, preserving the network-silence-as-hang guard for
default configs.

Updated the two corresponding test cases that asserted the old
clamp-on-cloud behavior; all 71 tests in `llm-idle-timeout.test.ts`
and the wider 430-test `src/agents/pi-embedded-runner/run/` lane pass.
Schema help text refreshed to call out that the same knob raises the
idle watchdog ceiling.

Refs: #77744, #78361
2026-05-19 17:16:48 +01:00
Pavan Kumar Gondhi
48acdd3d85 harden update restart script creation [AI] (#84088)
* fix: harden update restart script creation

* docs: add changelog entry for PR merge
2026-05-19 21:05:37 +05:30
YuanHanzhong
d0f7c8fa28 fix(docker): keep codex plugin in release images 2026-05-19 10:36:50 -04:00
hcl
5d19beb547 fix(cli): format acp client errors with formatErrorMessage (#83904) (#84080)
Summary:
- The PR changes `openclaw acp client` error handling to use `formatErrorMessage`, adds a plain-object rejection regression test, and adds a changelog entry.
- Reproducibility: yes. Current main visibly sends `openclaw acp client` caught errors through `String(err)`,  ...  catch already uses `formatErrorMessage`; I did not run a live failing ACP server in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): format acp client errors with formatErrorMessage (#83904)

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

Prepared head SHA: 69ef0e7270
Review: https://github.com/openclaw/openclaw/pull/84080#issuecomment-4486666922

Co-authored-by: HCL <chenglunhu@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-19 14:34:51 +00:00
Bob
13c97c5a8d feat(agents): support per-agent local model lean mode (#84073)
Summary:
- The branch adds per-agent `agents.list[].experimental.localModelLean` config and applies lean tool filtering through agent, session, and default-agent resolution.
- Reproducibility: not applicable. this is a feature/config PR rather than a current-main bug report. The chan ... or is supported by source review, focused tests in the branch, and the PR body's redacted live runtime log.

Automerge notes:
- PR branch already contained follow-up commit before automerge: feat(agents): support per-agent local model lean mode
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8407…

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

Prepared head SHA: 1f9a9554da
Review: https://github.com/openclaw/openclaw/pull/84073#issuecomment-4486397570

Co-authored-by: Bob <dutifulbob@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: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-19 14:11:38 +00:00
Thomas Krohnfuß
b7ba7c3f2a fix(cli): preserve first line of channels logs at window boundary (#84106)
Summary:
- The PR updates `openclaw channels logs` tail-window reading to keep a complete first line when the 1 MB window starts on a newline boundary, adds a regression test, and adds a changelog entry.
- Reproducibility: yes. Source inspection on current main shows the unconditional first-line drop, and the PR  ... s provide terminal before/after CLI output for a 2 MB log whose tail window starts exactly after a newline.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Merge remote-tracking branch 'origin/main' into fix/channels-logs-dro…
- PR branch already contained follow-up commit before automerge: fix(cli): preserve first line of channels logs at window boundary

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

Prepared head SHA: 284b312b31
Review: https://github.com/openclaw/openclaw/pull/84106#issuecomment-4487313048

Co-authored-by: BSG2000 <bsg2000@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@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-19 13:27:43 +00:00
clawsweeper[bot]
f07c87405c Fix config queue overrides for Matrix (#84104)
Summary:
- The branch updates queue-by-channel config schema/types for Matrix, Google Chat, and Mattermost, refreshes config baseline hashes, adds config/schema regression tests, and records the user-visible fix in the changelog.
- Reproducibility: yes. Source inspection gives a high-confidence path: current main's strict `messages.queue. ... matrix`, and the linked source PR records the same config failing before the patch and validating after it.

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

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

Prepared head SHA: 3865178550
Review: https://github.com/openclaw/openclaw/pull/84104#issuecomment-4487285061

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-19 12:05:46 +00:00
clawsweeper[bot]
03d774d6d8 [codex] Fix Control UI terminal run status recovery (#84112)
Summary:
- Adds shared Control UI session-run active-state handling, applies terminal-status precedence in chat/session rendering and lifecycle recovery, and adds focused regressions plus a changelog entry.
- Reproducibility: yes. Current main has a source-visible path where `status: "done"` plus stale `hasActiveRun ... eeps abort/in-progress UI alive, and the linked proof exercises the fixed stale-terminal state in Chromium.

Automerge notes:
- PR branch already contained follow-up commit before automerge: [codex] Fix Control UI terminal run status recovery

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

Prepared head SHA: f9f503add0
Review: https://github.com/openclaw/openclaw/pull/84112#issuecomment-4487409085

Co-authored-by: NianJiuZst <3235467914@qq.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-19 11:56:09 +00:00
clawsweeper[bot]
4e60ad7212 fix(media): decode remote URL fallback filenames (#84108)
Summary:
- This replacement PR decodes valid percent escapes in remote media URL fallback basenames, replaces decoded s ... scores, preserves malformed escapes, adds `saveRemoteMedia` regression coverage, and updates the changelog.
- Reproducibility: yes. Source inspection plus a Node check on current main show the URL path basename remains `My%20Report.pdf`, and the linked source PR supplies after-fix runtime proof through `saveRemoteMedia`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(media): decode remote URL fallback filenames

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

Prepared head SHA: 8cbac43f9b
Review: https://github.com/openclaw/openclaw/pull/84108#issuecomment-4487334097

Co-authored-by: Jayesh Betala <jayesh.betala7@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-19 11:42:16 +00:00
clawsweeper[bot]
d916f176e1 fix(cli): preserve equals in root option values [AI-assisted] (#84107)
Summary:
- This PR updates CLI root option parsing to preserve embedded equals signs, adds focused Vitest coverage for inline and space-separated values, and records the fix in the changelog.
- Reproducibility: yes. by source inspection: current main uses `raw.split("=", 2)`, so `--token=abc=def` returns only `abc`; the PR body also supplies after-fix live output for the same path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): preserve equals in root option values [AI-assisted]

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

Prepared head SHA: 8a15801e79
Review: https://github.com/openclaw/openclaw/pull/84107#issuecomment-4487314163

Co-authored-by: Thiago Costa <thiago12_fera@hotmail.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-19 11:37:35 +00:00
hcl
e2c8e7c8ae fix(cli): reject out-of-range port numbers in parsePort (#83900) (#84008)
Summary:
- The PR adds a 65,535 upper-bound check to the shared CLI `parsePort` helper, a colocated regression test, and a changelog entry for the linked port-range bug.
- Reproducibility: yes. Source inspection on current main shows `parsePort('99999')` delegates to `parseStrict ... sitive safe integer, so the return would be `99999`; I did not execute it because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): reject out-of-range port numbers in parsePort (#83900)

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

Prepared head SHA: 9ad0705c44
Review: https://github.com/openclaw/openclaw/pull/84008#issuecomment-4484883200

Co-authored-by: HCL <chenglunhu@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-19 11:36:12 +00:00
clawsweeper[bot]
d7083bab4c chore: move Motivation section above Change Type in PR template (#84098)
Summary:
- This PR moves the existing Motivation section in `.github/pull_request_template.md` from below Linked Issue/PR to immediately after Summary without changing the section text.
- Reproducibility: not applicable. this is a PR-template ordering cleanup, not a runtime bug. Source inspection of current main and the PR head verifies the before/after section order.

Automerge notes:
- PR branch already contained follow-up commit before automerge: chore: move Motivation section above Change Type in PR template

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

Prepared head SHA: 6c68583fac
Review: https://github.com/openclaw/openclaw/pull/84098#issuecomment-4487082864

Co-authored-by: Huan Jiang <seraphjiang@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-19 11:05:57 +00:00
clawsweeper[bot]
5e0850fc54 fix(ollama): default unknown capabilities to tools (#84075)
Summary:
- The branch makes unknown-capabilities Ollama model definitions explicitly tool-capable, adds regression assertions and changelog text, and guards the issue-labeler job to run only on issue events.
- Reproducibility: yes. for the metadata gap: current main builds unknown-capabilities Ollama models without a ... er-fix live provider output with `supportsTools: true`. I did not run local tests in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ollama): default unknown capabilities to tools

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

Prepared head SHA: 27527716c0
Review: https://github.com/openclaw/openclaw/pull/84075#issuecomment-4486492661

Co-authored-by: Bob <dutifulbob@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: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-19 09:51:24 +00:00
clawsweeper[bot]
1c1c75df72 fix(memory): close local embedding providers on timeout (#84048)
Summary:
- The branch adds a close lifecycle for local memory embedding providers, scoped memory search/index teardown for one agent, Active Memory timeout cleanup, focused tests, and a changelog entry.
- Reproducibility: yes. The linked issue gives a concrete OpenClaw 2026.5.18 Telegram Active Memory timeout pa ... current-main source inspection confirms there is no timeout cleanup for that local embedding provider path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory): close local embedding providers on timeout

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

Prepared head SHA: 8e2e369b5c
Review: https://github.com/openclaw/openclaw/pull/84048#issuecomment-4485705481

Co-authored-by: brokemac79 <martin_cleary@yahoo.co.uk>
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: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-05-19 09:19:09 +00:00
Sarah Fortune
aef93881af fix(installer): preserve windows onboarding tui (#84044)
Co-authored-by: sjf-oa <sjf-oa@users.noreply.github.com>
2026-05-19 01:12:26 -07:00
Ayaan Zaidi
1d77170a30 fix(xai): use public plugin test runtime (#84005) (thanks @fuller-stack-dev) 2026-05-19 12:35:54 +05:30
Ayaan Zaidi
6ee60fcfe2 fix(xai): add device code changelog (#84005) (thanks @fuller-stack-dev) 2026-05-19 12:35:54 +05:30
Ayaan Zaidi
b66e91ba77 fix(xai): decouple device code discovery 2026-05-19 12:35:54 +05:30
FullerStackDev
896fd13b1c feat(xai): add device code oauth login 2026-05-19 12:35:54 +05:30
Galin Iliev
ddeaebfc68 fix(agents): add trajectory flush timeout diagnostics
Adds bounded queued-writer diagnostics to pi-trajectory-flush cleanup timeout warnings so operators can see pending write count, queued bytes, active operation, and append size without exposing paths or payloads.

Closes #82961
2026-05-18 23:29:37 -07:00
Galin Iliev
04eac15f43 fix: recover stale subagent completion announces
Recover stale subagent completion delivery by retrying unsupported transcript-wait wakes without transcript waiting and forcing the existing message-tool handoff when the requester run is stale and direct completion is invisible.\n\nAdds regression coverage for the stale wake sequence and records the maintainer changelog entry.\n\nFixes #83699.
2026-05-18 23:09:20 -07:00
Galin Iliev
754b4234cb fix(agents): ignore duplicate embedded run clears
* fix(agents): ignore duplicate embedded run clears

* test(agents): fix embedded run clear lint

* docs(changelog): note embedded run clear fix

---------

Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
2026-05-18 23:05:37 -07:00
Ayaan Zaidi
cf235b209f chore(android): bump play upload version code 2026-05-19 11:06:12 +05:30
Ayaan Zaidi
ac07701833 fix(android): remove photo library access from play build 2026-05-19 11:06:12 +05:30
Gio Della-Libera
ff871e162a fix(config): allow bundled provider timeout overlays (#83267)
* fix config provider timeout overlays

Allow bundled model provider config entries to act as overlays so fields like timeoutSeconds can be configured without redeclaring baseUrl and models. Keep unknown custom provider declarations strict, and guard configured-provider fallback against overlay entries without models.

* fix(config): include provider aliases in model overlays

* fix(config): guard Foundry timeout overlays

* fix(config): normalize bundled provider overlays

* fix(models): reject overlay-only fallback models
2026-05-18 21:50:10 -07:00
Ayaan Zaidi
f0a86450b1 fix(mantis): release interrupted telegram proof leases 2026-05-19 09:58:34 +05:30
Patrick Erichsen
d60ab48511 Add Telegram progress preview flows (#83847)
* feat(telegram): add progress preview flow tooling

* docs: add channel flow preview skill

* test(telegram): exercise native draft flow fixture

* fix(telegram): remove progress label ellipsis animation

* fix(telegram): address progress preview review
2026-05-18 21:23:55 -07:00
clawsweeper[bot]
b86435f0b5 fix: forward-port Tideclaw alpha release fixes
Forward-port Tideclaw alpha stabilization fixes from the 2026-05-19 nightly release branch.
2026-05-19 04:13:38 +00:00
clawsweeper[bot]
6fcfeed5dc fix: include gateway plugin commands in TUI autocomplete (#83941)
Summary:
- The PR adds TUI-side Gateway `commands.list` fetching, dynamic slash-command merging, backend typing/tests, and a changelog entry so Gateway-connected TUI sessions suggest plugin-owned slash commands.
- Reproducibility: yes. Source inspection shows current main builds TUI autocomplete without any `commands.lis ... y exposes text-scope plugin commands, and the source PR supplies after-fix command output plus screenshots.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: include gateway plugin commands in TUI autocomplete
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8364…

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

Prepared head SHA: 2eba76a42d
Review: https://github.com/openclaw/openclaw/pull/83941#issuecomment-4484023526

Co-authored-by: Se7en <se7en-agent@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-19 03:55:26 +00:00
clawsweeper[bot]
b2f9f197a5 fix(whatsapp): clarify inbound group diagnostics (#83969)
Summary:
- The PR updates WhatsApp inbound listener and group-drop diagnostics, adds focused tests, and documents that observed but unregistered groups must be admitted through `channels.whatsapp.groups`.
- Reproducibility: yes. from source inspection: current main still emits the DM-only startup log and vague gro ... sions/whatsapp/src/auto-reply/monitor.ts` and `extensions/whatsapp/src/auto-reply/monitor/group-gating.ts`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(whatsapp): clarify group drop guidance
- PR branch already contained follow-up commit before automerge: fix(whatsapp): make inbound diagnostics policy-aware
- PR branch already contained follow-up commit before automerge: fix(whatsapp): clarify inbound group diagnostics

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

Prepared head SHA: 0da24e3bbb
Review: https://github.com/openclaw/openclaw/pull/83969#issuecomment-4484218945

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@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-19 03:53:56 +00:00
Ayaan Zaidi
6da73ac90f fix(mantis): wait for telegram desktop bootstrap 2026-05-19 09:15:15 +05:30
Josh Avant
cc835b6d72 fix(agents): preserve bare Telegram reply context (#83953)
* fix(agents): preserve bare reply context

* docs: add Telegram reply context changelog
2026-05-18 22:33:48 -05:00
clawsweeper[bot]
cbaf858227 fix: retry config snapshot after rejection (#83944)
Summary:
- This PR clears the cached CLI config snapshot promise when a read rejects, adds a reject-retry-cache regression test, and adds an Unreleased changelog entry.
- Reproducibility: yes. Current main clearly caches the first snapshot-read promise, and the source PR supplied a focused reject, recover, cached-success probe; I did not rerun it in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: retry config snapshot after rejection

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

Prepared head SHA: a46b5ec5c7
Review: https://github.com/openclaw/openclaw/pull/83944#issuecomment-4484051060

Co-authored-by: honor2030 <19909783+honor2030@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-19 03:28:26 +00:00
Ayaan Zaidi
1f794d2816 fix(mantis): skip zombie telegram queue runs 2026-05-19 08:45:31 +05:30
Josh Avant
ba7ce3c6b9 Fix stuck Codex-native subagent tasks after blocked spawn (#83945)
* fix codex native subagent terminal mirror

* add changelog for codex subagent terminal mirror
2026-05-18 22:12:43 -05:00
Galin Iliev
57ec361682 fix(agents): skip dormant completion wake probes
Skip embedded-run wake/steer attempts for dormant completion requesters and keep late subagent completions on the requester-agent/direct handoff path.\n\nAlso records the missing regression assertion that dormant completion requesters do not call queueEmbeddedPiMessageWithOutcome and adds the maintainer changelog entry.\n\nVerification:\n- node scripts/run-vitest.mjs src/agents/subagent-announce-delivery.test.ts\n- git diff --check\n- Codex autoreview via local Copilot endpoint: no actionable regressions\n- CI on 0108ebb2b3: clean
2026-05-18 20:07:36 -07:00
Ayaan Zaidi
d75e16a1b9 fix(mantis): ignore stale telegram queue blockers 2026-05-19 08:32:26 +05:30
Ayaan Zaidi
131577a4dc fix(mantis): preserve telegram account queue 2026-05-19 08:15:52 +05:30
Josh Avant
e996159738 Guard final delivery session refresh (#83928)
* guard final delivery session refresh

* add changelog for final delivery refresh guard
2026-05-18 21:44:17 -05:00
clawsweeper[bot]
8bd24ad6d4 fix(codex): preserve plugin tool auth profiles (#83845)
Summary:
- This PR threads a Codex-only `toolAuthProfileStore` through embedded runner attempt params, uses it for Code ... struction, forwards auth profiles into plugin-only tools, and adds regression tests plus a changelog entry.
- Reproducibility: yes. The linked source PR includes a concrete before-fix negative control and after-fix gat ... urrent-main source inspection shows Codex dynamic tools still receive only the scoped transport auth store.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(codex): align dynamic tool auth test helper
- PR branch already contained follow-up commit before automerge: fix(codex): expose tool auth to installed harnesses
- PR branch already contained follow-up commit before automerge: test(codex): narrow auth store assertions
- PR branch already contained follow-up commit before automerge: fix(codex): preserve plugin tool auth profiles

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

Prepared head SHA: c226f54be0
Review: https://github.com/openclaw/openclaw/pull/83845#issuecomment-4483631210

Co-authored-by: Rubén Cuevas <hi@rubencu.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-19 02:40:40 +00:00
Josh Avant
3ee0342061 fix(codex): honor Docker bind write policy (#83849)
* fix(codex): honor docker bind write policy

* docs: note docker bind sandbox fix

* fix(codex): expose docker sandbox fallback tools
2026-05-18 21:39:18 -05:00
Bek
10b313d628 Allow trusted plugin keyed state (#83775) 2026-05-18 22:38:54 -04:00
Oviemudi.eth
e9989f3a92 fix(whatsapp): periodic delivery-queue drain so enqueued items don't wait for next reconnect (#79083)
Merged via squash.

Prepared head SHA: 9a619bb9d9
Co-authored-by: Oviemudiaga <49584793+Oviemudiaga@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-05-18 23:29:15 -03:00
clawsweeper[bot]
ff4bf0c367 docker: support optional pip packages in local builds (#83850)
Summary:
- Adds `OPENCLAW_IMAGE_PIP_PACKAGES` as an opt-in Dockerfile build arg, passes it through Docker and Podman local setup, and documents/tests the new local image-build option.
- Reproducibility: not applicable. this is an additive Docker/Podman build capability, not a bug report. The s ... image importing requested Python packages, and the branch diff wires the renamed arg through Docker/Podman.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docker: support optional pip packages in local builds

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

Prepared head SHA: 0ccec19206
Review: https://github.com/openclaw/openclaw/pull/83850#issuecomment-4483676614

Co-authored-by: Stephen Redmond <stephen.redmond@straiteis.ie>
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-19 02:23:13 +00:00
VACInc
a559ccc084 Preserve queued Telegram topic followups (#83827)
Summary:
- This PR changes queued reply followups so user_request items no longer carry or inherit a source abort signal, preserves room_event abort signals, adds focused regression coverage, and updates CHANGELOG.md.
- Reproducibility: yes. at source level. Current main attaches and later falls back to opts.abortSignal for qu ... ore-fix regression failures for the two implicated paths; I did not execute tests in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Preserve queued Telegram topic followups

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

Prepared head SHA: 96fa0f69ba
Review: https://github.com/openclaw/openclaw/pull/83827#issuecomment-4483451436

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-19 02:13:04 +00:00
Kevin Lin
f169e0aafd fix(codex): guard against stale codex app snapshots leading to plugin invocation failure (#83807)
* feat(codex): add plugin enable disable list commands

* fix(codex): escape plugin management output

* test(codex): narrow plugin command coverage

* fix(codex): gate plugin management writes

* test(codex): type command plugin context

* fix(codex): recover plugin app bindings

* fix(codex): fail closed on missing app inventory

* fix(codex): restore plugin thread config log signal

* revert(codex): drop plugin management commands

* fix(codex): warn on missing plugin app inventory

* fix(codex): trim plugin binding debug logs

* fix(codex): restore thread lifecycle json import

* chore(codex): remove plugin app debug logs

* fix(codex): redact plugin thread config logs
2026-05-18 18:57:48 -07:00
clawsweeper[bot]
6f7d9736e2 fix(deepseek): normalize mcp union tool schemas (#83848)
Summary:
- The PR adds DeepSeek provider-owned `anyOf`/`oneOf` tool-schema normalization, normalizes late materialized bundled tools, and updates focused tests, docs, and changelog.
- Reproducibility: yes. Source inspection shows current main appends materialized bundled MCP tools after prov ... aw/issues/83361 provides the concrete DeepSeek `400 Invalid schema` failure for an MCP `anyOf` tool schema.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(deepseek): normalize mcp union tool schemas

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

Prepared head SHA: 1bbbb44d2b
Review: https://github.com/openclaw/openclaw/pull/83848#issuecomment-4483638498

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-19 01:54:29 +00:00
Gio Della-Libera
8eb0a1777f fix(migrate): use resolved provider for options (#83323)
* fix(migrate): use resolved provider for options

* test(migrate): cover resolved provider apply options
2026-05-18 18:54:26 -07:00
VACInc
f526d96c98 Fix Telegram forum topic parallel flow (#83829)
Summary:
- The branch fixes Telegram forum-topic session routing, per-topic text/media buffering, media-group scoping, and outbound group send fairness, with focused Telegram regression tests and a changelog entry.
- Reproducibility: yes. source inspection of current main plus the PR body's before-proof give a high-confiden ... s_forum can collapse to the base group route, and global text/media buffer chains serialize sibling topics.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Fix Telegram forum topic parallel flow

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

Prepared head SHA: b0f78fa275
Review: https://github.com/openclaw/openclaw/pull/83829#issuecomment-4483486851

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-19 01:48:56 +00:00
Gio Della-Libera
19065e4a2f Improve Telegram groups config shape diagnostics (#83260)
* Improve Telegram groups config diagnostics

Add targeted guidance when channels.telegram.groups uses a non-object shape so startup/config validation and doctor explain the required group-id object map and topic nesting.

* fix(config): keep channel validation hints generic
2026-05-18 18:47:28 -07:00
Gio Della-Libera
88585da2e8 test(config): keep agent model overrides selection-only (#83319)
* fix(config): keep subagent model overrides selection-only

* fix(config): reuse agent model schema for subagents
2026-05-18 18:46:40 -07:00
Josh Avant
eb6dd2c65d Fix memory plugin CLI help dispatch (#83841)
* fix cli help for active memory plugin

* docs add changelog for memory cli help

* test fix root help mock type
2026-05-18 20:35:55 -05:00
Peter Steinberger
0b4fc26d4a codex: surface deferred dynamic tool names (#83813)
* codex: surface deferred dynamic tool names

* codex: keep prompt snapshots source-backed

* style: wrap mac voice settings help text

* style: satisfy swiftformat for voice wake help text

* style: apply swiftformat to voice wake help text

* test: load codex prompt snapshots through plugin aliases

* test: type codex source surface loader

* test: avoid extra codex loader suppression

---------

Co-authored-by: pashpashpash <nik@vault77.ai>
2026-05-19 10:32:36 +09:00
clawsweeper[bot]
48d9966aa1 fix(cli): include loopback tools in cli prompts (#83828)
Summary:
- The PR feeds loopback-scoped MCP tools into CLI system prompts and reports, persists a prompt tool-name hash for CLI session reuse, adds regression tests, and adds a changelog entry.
- Reproducibility: yes. from source inspection: current main builds the CLI prompt and report with `tools: []` ... execute a live CLI turn in this read-only review, but the source path and source PR terminal proof line up.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): gate prompt loopback tools on active runtime
- PR branch already contained follow-up commit before automerge: fix(cli): include loopback tools in cli prompts

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

Prepared head SHA: d196564d4d
Review: https://github.com/openclaw/openclaw/pull/83828#issuecomment-4483469332

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-19 01:30:06 +00:00
clawsweeper[bot]
d160342b55 fix(ui): keep delete confirm in viewport (#83825)
Summary:
- This PR changes the Control UI chat delete confirmation popover from absolute above-trigger positioning to fixed viewport-clamped placement with focused geometry tests and a changelog entry.
- Reproducibility: yes. The related delete-click report maps directly to current main code that appends an abo ... able chat thread without viewport measurement; I did not run a live browser repro in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ui): keep delete confirm in viewport

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

Prepared head SHA: bc000c5b64
Review: https://github.com/openclaw/openclaw/pull/83825#issuecomment-4483439624

Co-authored-by: Thiago Costa <71539514+ThiagoCAltoe@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-19 01:28:54 +00:00
clawsweeper[bot]
1a242cd4f5 fix(memory-wiki): preserve fs-safe write diagnostics (#83839)
Summary:
- The branch narrows Memory Wiki imported-source `FsSafeError` wrapping, adds directory-collision bridge regressions, and adds a changelog entry crediting the source PR.
- Reproducibility: yes. Source inspection shows current main catches all imported-source `FsSafeError`s with symlink wording, and the linked source PR includes live bridge-sync output for the directory-collision path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory-wiki): normalize source page stat guard
- PR branch already contained follow-up commit before automerge: fix(memory-wiki): preserve fs-safe write diagnostics

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

Prepared head SHA: e38ae3b998
Review: https://github.com/openclaw/openclaw/pull/83839#issuecomment-4483591199

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-19 01:24:54 +00:00
Josh Avant
2c8f78e723 Harden final delivery routing refresh (#83835)
* harden final delivery routing refresh

* add changelog for final delivery hardening
2026-05-18 20:16:58 -05:00
Firas Alswihry
a9eaf0c993 test(qa-lab): add personal no-fake-progress scenario (#83824)
Summary:
- The PR adds a personal-agent QA-Lab no-fake-progress scenario, registers it in the personal-agent pack, teaches mock-openai the scripted path, and updates focused tests, docs, and changelog.
- Reproducibility: not applicable. This PR adds QA coverage rather than reporting a current-main bug; the branch supplies concrete after-patch QA-Lab/mock-openai commands and copied pass output.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(qa-lab): add personal no-fake-progress scenario

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

Prepared head SHA: 95d2e46288
Review: https://github.com/openclaw/openclaw/pull/83824#issuecomment-4483439200

Co-authored-by: Firas Alswihry <itzfiras@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-19 01:16:00 +00:00
Eduardo Piva
6f18decb7a fix: add Copilot IDE headers to resolved models (#82275)
* fix: add copilot headers to resolved models

* fix copilot header imports

* fix prod typecheck
2026-05-18 17:59:02 -07:00
clawsweeper[bot]
f1a55cbd52 fix(skills): refresh snapshots when watch roots change (#83823)
Summary:
- The replacement PR adds a `watch-targets` skills snapshot invalidation when `ensureSkillsWatcher` rebuilds f ... root set, reads the snapshot version after watcher setup, adds regression tests, and updates the changelog.
- Reproducibility: yes. Source inspection shows current main rebuilds the skills watcher on changed root targe ... the version before watcher setup; I did not run a live Gateway mount reproduction in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(skills): refresh snapshots when watch roots change

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

Prepared head SHA: 2677dcc35a
Review: https://github.com/openclaw/openclaw/pull/83823#issuecomment-4483425019

Co-authored-by: hclsys <hclsys@openclaw.ai>
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-19 00:36:27 +00:00
WhatsSkiLL
9b517b50cb fix(push): use valid default VAPID subject (#83317) 2026-05-18 17:27:11 -07:00
Peter Steinberger
8aff1807fa style: refine mac voice settings layout 2026-05-19 02:18:22 +02:00
Peter Steinberger
b4fdd1470b fix(codex): expose sandbox shell tools for ssh backends 2026-05-19 02:15:53 +02:00
Peter Steinberger
1c3ff34d75 ci(release): stabilize beta validation assertions 2026-05-19 01:05:52 +01:00
Peter Steinberger
59defa3e71 ci(release): fix beta validation gates 2026-05-19 01:05:52 +01:00
Peter Steinberger
ab398ae86d docs(release): add mac release recovery skill 2026-05-19 01:05:52 +01:00
Gio Della-Libera
87aa319568 fix(export): preserve explicit trajectory session keys (#83308) 2026-05-18 16:48:23 -07:00
Gio Della-Libera
567fe2957d fix(doctor): include channel model provider repairs (#83328)
* fix(doctor): include channel model provider repairs

* fix(doctor): include provider-keyed channel model repairs
2026-05-18 16:46:10 -07:00
Peter Steinberger
df8505b09d test: cover installer npm freshness policy 2026-05-19 00:15:49 +01:00
Peter Steinberger
0903fa61d0 docs: fix update recovery verification (#83757) 2026-05-19 01:13:51 +02:00
Peter Steinberger
bcdfbb8b84 docs: update changelog for EACCES recovery (#83757) (thanks @brokemac79) 2026-05-19 01:13:51 +02:00
brokemac79
26bcc95665 fix(update): guide EACCES manual recovery 2026-05-19 01:13:51 +02:00
Peter Steinberger
583eb711ec ci(release): disable notarytool s3 acceleration 2026-05-18 23:53:29 +01:00
openclaw-release-bot
46d53d3b59 chore(release): update appcast for 2026.5.18 2026-05-18 22:50:35 +00:00
Peter Steinberger
b77444ee48 docs(refactor): remove completed channel route plan 2026-05-19 00:49:25 +02:00
Peter Steinberger
cde6d60c18 fix(channels): compare normalized routes without serialization 2026-05-19 00:49:25 +02:00
Peter Steinberger
17eab1ed4d fix(channels): preserve route metadata on agent updates 2026-05-19 00:49:25 +02:00
Peter Steinberger
02f8fb7147 fix(channels): clear canonical stale routes 2026-05-19 00:49:25 +02:00
Peter Steinberger
8477a67faf refactor(channels): unify session route projection 2026-05-19 00:49:25 +02:00
Josh Lehman
85a3d5312f fix: bypass npm freshness for managed installs (#83761)
* fix: bypass npm freshness for managed installs

* test: tolerate npm config json differences

* test: align npm freshness bypass expectation

* fix: resolve npm config path expansions

* test: tolerate npm zero config encoding

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-19 00:31:02 +02:00
nitinjwadhawan
d761b98adc fix(memory-core): yield event loop during fallback vector search (#81172) (#83758)
Summary:
- The branch changes memory-core fallback vector search to scan chunks in 256-row rowid batches with `setImmediate` yields, updates regression tests, and adds a changelog entry.
- Reproducibility: yes. from source and supplied live output. Current main synchronously scans fallback vector ...  and the PR body shows the before/after heartbeat behavior through the actual `searchVector` fallback path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(memory-core): add boundary, parity, and concurrent-insert covera…
- PR branch already contained follow-up commit before automerge: fix(memory-core): yield event loop during fallback vector search (#81…

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

Prepared head SHA: 0ede3d7168
Review: https://github.com/openclaw/openclaw/pull/83758#issuecomment-4482137790

Co-authored-by: NW <nitinwadhawan66@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-18 22:18:14 +00:00
Andy Ye
98cc6df7ff fix(anthropic): preserve Claude image capability (#83756)
Summary:
- The PR adds Anthropic Claude 4.x image-capability normalization for stale text-only resolved model rows, regression tests for provider and fallback model resolution, and a changelog entry.
- Reproducibility: yes. for source-level reproduction: current main gates native images on model.input includi ... s text-only. I did not run the command locally because this review was constrained to read-only inspection.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(anthropic): preserve Claude image capability

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

Prepared head SHA: 06dd378ea3
Review: https://github.com/openclaw/openclaw/pull/83756#issuecomment-4482116499

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-18 22:16:01 +00:00
Peter Steinberger
83c225b243 chore: refresh release generated baselines 2026-05-18 23:11:42 +01:00
Peter Steinberger
c1579b7727 chore: bump release version to 2026.5.19 2026-05-18 23:11:42 +01:00
Tak Hoffman
9968db65db fix(github): preserve clawsweeper proof labels (#83781) 2026-05-18 17:10:35 -05:00
Super Zheng
d124c5aa20 fix(cli): fix flaky config set help text test caused by env var leakage and word wrapping (#83423)
Merged via squash.

Prepared head SHA: 7ba1bac70c
Co-authored-by: medns <1575008+medns@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-19 00:11:52 +03:00
Patrick Erichsen
721ad1587a fix: keep inter-session provenance out of transcripts (#83755) 2026-05-18 14:02:25 -07:00
Andy Ye
b2c5ba6d4c fix(outbound): resolve send-capable channel registry (#83733)
Summary:
- The PR changes outbound channel registry loading and bootstrap to fall back from pinned setup-only channel entries to the active runtime registry, with regression tests and a changelog entry.
- Reproducibility: yes. at source level. Current main can select a pinned setup-only channel entry and skip th ... module live output showing delivery after the fallback; I did not run local tests in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(outbound): resolve send-capable channel registry

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

Prepared head SHA: 67c20aa72b
Review: https://github.com/openclaw/openclaw/pull/83733#issuecomment-4481084888

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-18 20:30:51 +00:00
clawsweeper[bot]
424c6d0a5f fix(auto-reply): honor webchat textChunkLimit/chunkMode config overrides [AI-assisted] (#83742)
Summary:
- This PR removes the WebChat special-case from auto-reply chunk limit/mode resolution, adds WebChat override regression tests, and records the fix in the changelog.
- Reproducibility: yes. from source inspection rather than runtime execution: current main returns the fallbac ... bchat` before reading `cfg.channels`, so a configured `channels.webchat.textChunkLimit` cannot take effect.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(auto-reply): honor webchat textChunkLimit/chunkMode config overri…

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

Prepared head SHA: cd9ac01a36
Review: https://github.com/openclaw/openclaw/pull/83742#issuecomment-4481570742

Co-authored-by: luyao618 <364939526@qq.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-18 20:09:40 +00:00
Andy Ye
583a60f8b5 fix(ui): render session-scoped tool events (#83734)
Summary:
- The PR routes `session.tool` Gateway frames through the Control UI tool-stream handler, adds a regression test, and adds a changelog entry.
- Reproducibility: yes. Current main emits `session.tool` frames for session subscribers, but the Control UI d ...  to the tool-stream handler, so the failure path is source-reproducible without needing a live browser run.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ui): render session-scoped tool events

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

Prepared head SHA: 58be438acb
Review: https://github.com/openclaw/openclaw/pull/83734#issuecomment-4481086608

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-18 19:59:09 +00:00
Gio Della-Libera
94abfa76e2 Doctor: convert read-only health checks (#83198)
* feat(doctor): convert read-only health checks

* fix(doctor): keep read-only conversion gates green

* fix(doctor): preserve health repair preview contract

* fix(doctor): defer session snapshot lint target

* fix(doctor): avoid false-clean lint placeholders

* test(doctor): type conversion target registry check
2026-05-18 12:49:20 -07:00
Tak Hoffman
c92ebd6a41 fix(ci): preserve Barnacle proof labels (#83735)
* fix(ci): preserve sufficient proof override

* fix(ci): keep sufficient proof on label churn
2026-05-18 14:37:20 -05:00
Gio Della-Libera
1fb09069c3 fix(doctor): anchor WhatsApp TUI process matching (#83313) 2026-05-18 12:04:32 -07:00
clawsweeper[bot]
70f580041f test(qa-lab): add personal share-safe diagnostics scenario (#83717)
Summary:
- Adds a personal-agent QA-Lab share-safe diagnostics scenario with mock-openai support, pack registration/tests, docs, and changelog coverage.
- Reproducibility: not applicable. This PR adds a new QA-Lab scenario rather than fixing a current-main bug. T ... ce PR provides a clear after-patch validation path using qa-channel, a real gateway child, and mock-openai.

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

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

Prepared head SHA: 46eb0af9e4
Review: https://github.com/openclaw/openclaw/pull/83717#issuecomment-4480393933

Co-authored-by: Firas Alswihry <itzfiras@gmail.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-18 18:56:25 +00:00
nitinjwadhawan
9995e1b4d5 fix(nextcloud-talk): dispatch react action so agents can send reactions (#70110) (#72348)
Summary:
- This PR adds and registers a Nextcloud Talk message action adapter for add-only reactions, updates reaction docs, and adds adapter plus sender tests.
- Reproducibility: yes. Source inspection on current main shows Nextcloud Talk advertises reactions and has a  ... ion sender, but the plugin lacks `actions.handleAction`, so shared `react` dispatch has no channel handler.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(nextcloud-talk): cover reaction sender request path
- PR branch already contained follow-up commit before automerge: fix(nextcloud-talk): harden react null-guard; fix disabled-account te…
- PR branch already contained follow-up commit before automerge: fix(nextcloud-talk): reject react remove requests instead of silently…
- PR branch already contained follow-up commit before automerge: fix(nextcloud-talk): inline listEnabledAccounts helper after main cle…
- PR branch already contained follow-up commit before automerge: docs(nextcloud-talk): note add-only react support in reactions and me…

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

Prepared head SHA: 9817fed842
Review: https://github.com/openclaw/openclaw/pull/72348#issuecomment-4323046928

Co-authored-by: NW <nitinwadhawan66@gmail.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-18 18:54:44 +00:00
clawsweeper[bot]
bf9329486b fix(models): label picker auth via effective provider order (#83726)
Summary:
- The PR passes the effective OpenAI/Codex auth provider set into `/models` provider-header labeling, adds focused regression tests, and records the user-facing fix in the changelog.
- Reproducibility: yes. Current main lacks `acceptedProviderIds` in the shared picker header path, and the source PR's Mantis baseline/candidate proof shows the visible Telegram header mismatch and after-fix OAuth label.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(models): label picker auth via effective provider order

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

Prepared head SHA: 8ca2924adc
Review: https://github.com/openclaw/openclaw/pull/83726#issuecomment-4480805713

Co-authored-by: Stellar鱼 <2182712990@qq.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-18 18:48:58 +00:00
Andy Ye
44c6ad7dce fix(subagents): collect unresolved announce batches (#83701)
Summary:
- The PR changes collect-mode follow-up queue routing so unresolved-origin items can batch with a single resolved route and later compatible items can resume batching after a true cross-channel drain.
- Reproducibility: yes. at source level: current main treats unkeyed-plus-same-keyed queue items as cross-chan ... failing path is directly visible in `src/utils/queue-helpers.ts` and `src/auto-reply/reply/queue/drain.ts`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Merge remote-tracking branch 'origin/main' into maint-83701-20260518

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

Prepared head SHA: e6ad029e23
Review: https://github.com/openclaw/openclaw/pull/83701#issuecomment-4479943100

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@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-18 18:34:58 +00:00
clawsweeper[bot]
3e6f7494af fix(browser): preserve explicit cdpPort when cdpUrl omits port (#83707)
Summary:
- The PR adds raw explicit-port detection for browser CDP URLs, updates profile resolution precedence, adds regression tests, and records the browser fix in the changelog.
- Reproducibility: yes. Source inspection shows current main resolves a portless profile `cdpUrl` through `par ...  443, and overwrites the configured `cdpPort`; the source PR also provides live before/after Chrome output.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(browser): encapsulate explicit-port detection in parseBrowserHttpUrl
- PR branch already contained follow-up commit before automerge: fix(browser): preserve explicit cdpPort when cdpUrl omits port

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

Prepared head SHA: 070c31cdcf
Review: https://github.com/openclaw/openclaw/pull/83707#issuecomment-4480058057

Co-authored-by: Hongwei Ma <marvae24@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-18 18:20:55 +00:00
Agustin Rivera
78f3985c60 fix(browser): guard current tab act routes (#78523)
* fix(browser): guard current tab act routes

* fix(browser): document current-tab route guard
2026-05-18 11:19:30 -07:00
Tak Hoffman
06a39015f2 fix(ci): authenticate proof verdict markers (#83692)
Summary:
- The branch restricts exact-head ClawSweeper proof markers to GitHub App-authored comments, adds read-only issue-comment token fallback for the proof workflow, and adds focused regression tests plus a changelog entry.
- Reproducibility: yes. Source inspection of current main shows any issue comment body with a matching `clawsw ...  SHA is accepted without author/App authentication; the PR adds focused negative tests for forged comments.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ci): authenticate proof verdict markers

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

Prepared head SHA: f4c375eaa7
Review: https://github.com/openclaw/openclaw/pull/83692#issuecomment-4479843682

Co-authored-by: Tak Hoffman <781889+Takhoffman@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-18 17:42:10 +00:00
Tak Hoffman
0901801238 docs: clarify pull request motivation 2026-05-18 12:39:54 -05:00
Vincent Koc
cb408bb06b fix(release): repair broad gate regressions 2026-05-19 01:31:25 +08:00
clawsweeper[bot]
fa814eb9ed feat(browser): add evaluate timeout CLI option (#83696)
Summary:
- The branch adds `openclaw browser evaluate --timeout-ms`, forwards it to the evaluate body and request timeo ... ents and tests it, adds a changelog entry, and includes a config.patch no-op shortcut from the repair pass.
- Reproducibility: not applicable. this is a feature PR rather than a bug report. Source inspection shows current main lacks the CLI flag while the branch wires it into an already-supported evaluate `timeoutMs` payload.

Automerge notes:
- PR branch already contained follow-up commit before automerge: feat(browser): add evaluate timeout CLI option

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

Prepared head SHA: 0d81d3d93e
Review: https://github.com/openclaw/openclaw/pull/83696#issuecomment-4479900502

Co-authored-by: fred <fengruifree@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-18 17:30:33 +00:00
clawsweeper[bot]
a4f80f905d fix(ui): prevent reading indicator from sticking after assistant response (#83711)
Summary:
- The PR removes the Control UI chat fallback that converts a null stream into an empty stream for abortable runs, adds null-vs-empty stream regression tests, and updates the changelog.
- Reproducibility: yes. source-level reproduction is high confidence: current main converts null stream plus c ... ading indicator. The linked source PR also reports live Control UI verification after the equivalent patch.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ui): prevent reading indicator from sticking after assistant resp…

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

Prepared head SHA: 44bea55110
Review: https://github.com/openclaw/openclaw/pull/83711#issuecomment-4480128171

Co-authored-by: 二狗子 <njuboy11@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-18 17:28:26 +00:00
clawsweeper[bot]
5702858553 feat(cli): support installing skills to shared global directory via --global (#83705)
Summary:
- Adds `--global` to `openclaw skills install` and `openclaw skills update`, routing ClawHub installs and updates to the shared managed skills root with docs, changelog, and CLI command tests.
- Reproducibility: not applicable. as a bug reproduction; this is a new CLI feature request. Source inspection confirms current `main` lacks `--global`, and the source PR includes after-fix terminal proof for the new path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cli): address skills global review
- PR branch already contained follow-up commit before automerge: feat(cli): support installing skills to shared global directory via -…

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

Prepared head SHA: 6eb7187fc1
Review: https://github.com/openclaw/openclaw/pull/83705#issuecomment-4480023577

Co-authored-by: Hongwei Ma <marvae24@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-18 17:22:00 +00:00
clawsweeper[bot]
3631af8107 fix(skill-creator): reject empty name and description in skill valida… (#83704)
Summary:
- The PR makes skill-creator quick validation reject empty or whitespace-only `name` and `description` fields, adds regression tests, and records the fix in the changelog.
- Reproducibility: yes. Source inspection on current main shows empty or whitespace-only values skip validation after `.strip()`, and the source PR includes before/after terminal output for the same path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(skill-creator): reject empty name and description in skill valida…

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

Prepared head SHA: 0fb4555cb2
Review: https://github.com/openclaw/openclaw/pull/83704#issuecomment-4479984760

Co-authored-by: jay <a1@ponys-Mac.local>
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-18 17:10:19 +00:00
Tak Hoffman
57854219d0 docs(changelog): add Telegram delivery log entry 2026-05-18 12:08:59 -05:00
Yuval Dinodia
324a95db8b docs(gateway): troubleshoot group @mention silent suppression (#77052)
Summary:
- Adds a symptom-keyed troubleshooting block to `docs/gateway/config-channels.md` for group/channel @mentions that log `queuedFinal=false, replies=0` and explains the `visibleReplies` remedies.
- Reproducibility: yes. for the docs gap and source behavior: current main lacks the exact symptom-keyed troubleshooting entry, and the resolver/tests show when message-tool mode suppresses automatic final delivery.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs(gateway): make group reply fix restart conditional
- PR branch already contained follow-up commit before automerge: docs(gateway): qualify direct-chat reply default in troubleshooting
- PR branch already contained follow-up commit before automerge: docs(gateway): align group reply troubleshooting with current automat…
- PR branch already contained follow-up commit before automerge: docs(gateway): scope group reply suppression cause to group config

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

Prepared head SHA: e60ae89b20
Review: https://github.com/openclaw/openclaw/pull/77052#issuecomment-4367898048

Co-authored-by: yetval <yetvald@gmail.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-18 17:03:53 +00:00
Yufeng He
f4e17a4b54 fix: stop swallowing mkdir errors in memory ensureDir (#41259)
Summary:
- The PR removes the empty `mkdirSync({ recursive: true })` catch in the memory host SDK `ensureDir()`, adds a regression test for surfaced mkdir failures, and adds a changelog entry.
- Reproducibility: yes. from source inspection rather than a locally executed repro. Current main swallows eve ... kdir failure in `ensureDir()`, and the active memory database path calls that helper before opening SQLite.

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

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

Prepared head SHA: 0f82f185cc
Review: https://github.com/openclaw/openclaw/pull/41259#issuecomment-4326310101

Co-authored-by: Yufeng He <40085740+he-yufeng@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-18 17:01:52 +00:00
Rohin
9cbe28d75e fix(skills): resolve skills info name mismatches (#38713)
Summary:
- The PR updates the skills CLI formatter, tests, and changelog so `skills info` resolves case-insensitive and ... ator-normalized skill name variants only when non-exact matches are unique, and sanitizes not-found output.
- Reproducibility: yes. by source inspection. The documented `openclaw skills info <name>` command passes the  ... ormatter lookup on current main, while skill status entries can have distinct `name` and `skillKey` values.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(skills): exercise case-insensitive lookup branch
- PR branch already contained follow-up commit before automerge: style(skills): format lookup resolver signature
- PR branch already contained follow-up commit before automerge: fix(skills): sanitize not-found output and avoid ambiguous lookup mat…
- PR branch already contained follow-up commit before automerge: fix(skills): require unique case-insensitive info matches

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

Prepared head SHA: 01f3e2d468
Review: https://github.com/openclaw/openclaw/pull/38713#issuecomment-4321021300

Co-authored-by: NewdlDewdl <rohin.agrawal@gmail.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-18 17:00:57 +00:00
zhengzuo0-ai
1fbb4e4e6a ui: highlight WebChat code blocks (#83569)
Summary:
- The PR adds highlight.js-backed WebChat code-block highlighting, scoped token CSS, regression tests, a type shim, and a direct UI dependency.
- Reproducibility: not applicable. as a bug reproduction; this is a feature addition. The feature gap is source-evident because current main renders code blocks as escaped plaintext without hljs token markup.

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

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

Prepared head SHA: 7bb95c47ed
Review: https://github.com/openclaw/openclaw/pull/83569#issuecomment-4476990135

Co-authored-by: zhengzuo0-ai <zheng.zuo0@gmail.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-18 16:53:24 +00:00
Firas Alswihry
46c622aa3b test(qa-lab): add dreaming shadow trial report scenario 2026-05-19 00:44:39 +08:00
Ayaan Zaidi
3fb5b4bec9 docs(changelog): note telegram native progress drafts (#83622) (thanks @akrimm702) 2026-05-18 22:14:30 +05:30
Ayaan Zaidi
890139f998 refactor(telegram): simplify native draft progress path 2026-05-18 22:14:30 +05:30
Alexander Krimm
0802a10273 fix(config): scope native telegram preview config 2026-05-18 22:14:30 +05:30
Alexander Krimm
f199cec885 docs(telegram): clarify native draft progress config 2026-05-18 22:14:30 +05:30
Alexander Krimm
a433cef05f fix(telegram): gate native tool progress drafts 2026-05-18 22:14:30 +05:30
Alexander Krimm
7cc4258dd5 feat(telegram): use native DM drafts for tool progress 2026-05-18 22:14:30 +05:30
Tak Hoffman
e4fba78d81 fix(ci): honor exact-head proof verdicts (#83688) 2026-05-18 11:39:30 -05:00
clawsweeper[bot]
9dc7bd4d05 fix(memory-wiki): make wiki_lint tool output path-safe (#83687)
Summary:
- The PR updates the memory-wiki `wiki_lint` tool to show vault-relative lint report paths in tool text and details, keeps the core linter/CLI result absolute, adds regression coverage, and adds a changelog entry.
- Reproducibility: yes. there is a high-confidence source reproduction path: current main returns the linter's ... tPath` in `wiki_lint` text and raw details. I did not execute the harness because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory-wiki): make wiki_lint tool output path-safe

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

Prepared head SHA: df5c7db151
Review: https://github.com/openclaw/openclaw/pull/83687#issuecomment-4479682214

Co-authored-by: LLagoon3 <choonarm3@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-18 16:34:11 +00:00
nv-kasikritc
38f11a0844 feat(nvidia): tag NIM request origin (#81524) 2026-05-18 09:25:24 -07:00
Dallin Romney
cf194419c3 ci(proof): skip real-behavior-proof gate for private maintainers (#83418)
* ci(proof): trust maintainer label for private org members

Private organization memberships report author_association=CONTRIBUTOR
on PRs, so the real-behavior-proof gate currently demands proof from
maintainers whose membership is private. The labeler workflow already
applies the 'maintainer' label via the team-membership API (which sees
private members), so treat that label as an equivalent privileged
signal in evaluateRealBehaviorProof.

* ci(proof): drop noisy comments

* ci(proof): check maintainer team membership via GitHub App token

Replace the label-based private-maintainer skip with a direct
getMembershipForUserInOrg call using a minted GitHub App token, mirroring
the pattern labeler.yml already uses for the same lookup. Removes the
race against the labeler workflow and the implicit dependency on the
'maintainer' label having landed first.

The App-token steps are continue-on-error so the gate still runs (using
the existing author_association path) when the App key secrets are
absent or both mints fail.

* ci(proof): narrow App token to members:read

ClawSweeper review #83418: actions/create-github-app-token defaults to
the full installation permission set, but the proof gate only needs the
org-members read scope used by teams.getMembershipForUserInOrg. Set
permission-members: read on both the primary and fallback mint steps.

* docs(changelog): private maintainers skip the real-behavior-proof gate
2026-05-18 09:22:59 -07:00
Elarwei
9657b8e8ce fix(image-generate): allow distinct active image requests (#83614)
Summary:
- This PR prompt-scopes `image_generate` duplicate detection, adds same-prompt and distinct-prompt regression tests, and updates task guardrail docs and changelog.
- Reproducibility: yes. Current-main source shows the duplicate guard runs before prompt parsing and active lookup ignores prompt identity, matching the linked distinct-second-image failure mode.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs(tasks): clarify image generation guardrail
- PR branch already contained follow-up commit before automerge: fix(image-generate): allow distinct active image requests

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

Prepared head SHA: 9f19a96427
Review: https://github.com/openclaw/openclaw/pull/83614#issuecomment-4478236891

Co-authored-by: Elarwei <elarweis@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>
2026-05-18 16:01:12 +00:00
Ted Li
fffb8c9e2c fix(lmstudio): resolve env-template API keys (#80568)
Merged via squash.

Prepared head SHA: 03224c8c27
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-18 23:46:10 +08:00
yaoyi1222
023e33cb07 fix(transcript): skip trailing custom entries in tail assistant reader (#83427) (#83635)
Summary:
- The branch updates the transcript tail assistant reader to skip trailing non-message rows, adds cache-ttl gap-fill regression tests, and adds a changelog entry.
- Reproducibility: yes. Source inspection shows cache-ttl custom rows can sit after the canonical assistant me ... r stops on that row; the PR body also supplies a concrete live three-turn CLI reproduction after the patch.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(transcript): skip trailing custom entries in tail assistant reade…

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

Prepared head SHA: 866aa27ca8
Review: https://github.com/openclaw/openclaw/pull/83635#issuecomment-4478637780

Co-authored-by: yaoyi1222 <yaoyi_1222@163.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-18 15:43:09 +00:00
Ayaan Zaidi
98256b192b fix(mantis): suppress auto no-proof comments 2026-05-18 21:11:21 +05:30
Jai Govindani
8c2a390fbc fix(cron): link isolated task runs to cron session (#83606)
Summary:
- The PR updates cron timer task-run creation to derive `childSessionKey` for isolated agent-turn jobs from the stable cron session key, adds focused timer coverage, and records the fix in the changelog.
- Reproducibility: yes. Current main's timer task creation copies only `job.sessionKey`, while isolated cron e ... Id>:cron:<jobId>` later; the supplied before-test output matches that source path by receiving `undefined`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cron): link isolated task runs to cron session

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

Prepared head SHA: 748998b018
Review: https://github.com/openclaw/openclaw/pull/83606#issuecomment-4478039217

Co-authored-by: Jai Govindani <jai.g@ewa-services.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-18 15:39:56 +00:00
Ayaan Zaidi
4af590a5f8 docs(changelog): note Telegram transcript mirror fix (#83631) (thanks @kurplunkin) 2026-05-18 20:46:58 +05:30
Tyler Bea
03c303d953 fix(telegram): avoid progress transcript mirrors 2026-05-18 20:46:58 +05:30
Arulprashath
27c7e1e07b Fix sidebar tree collapse not hiding child items (#42223)
Merged via squash.

Prepared head SHA: a6bf8f4511
Co-authored-by: Aroool <90670606+Aroool@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-18 18:08:47 +03:00
Coy Geek
516356835d fix: Admin HTTP RPC can execute against another live gateway instance (#83487)
* fix(ar-gdn-cross-gateway-admin-rpc-context-confusion): apply security fix

Generated by staged fix workflow.

* fix(ar-gdn-cross-gateway-admin-rpc-context-confusion): apply security fix

Generated by staged fix workflow.

* fix(gateway): bind plugin HTTP dispatch to server context

* fix(gateway): scope dynamic plugin HTTP routes

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-18 15:59:28 +01:00
Peter Steinberger
cce00498cd fix(doctor): preserve legacy Claude CLI runtime intent 2026-05-18 15:58:55 +01:00
Peter Steinberger
ae29d14abf test: speed up slow test fixtures 2026-05-18 15:55:40 +01:00
Peter Steinberger
13deea2a9d fix(macos): normalize settings pane margins 2026-05-18 15:37:36 +01:00
Krzysztof Probola
1912be8619 fix(codex): complete dynamic tool diagnostics
fix(codex): complete dynamic tool diagnostics

Co-authored-by: 0x505badc0de <32790662+rozmiarD@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-18 15:37:24 +01:00
Peter Steinberger
c49d909b60 fix(slack): persist inbound delivery dedupe 2026-05-18 15:28:07 +01:00
Peter Steinberger
f0b43bfd34 fix(ci): restore release e2e checks 2026-05-18 15:20:27 +01:00
yetval
1b82c0e3d9 fix(followup,reply): stop model-fallback retries duplicating session entries
Follow-up and main reply paths re-entered each embedded fallback candidate
with the same queued transcript prompt. After the first candidate persisted
that queued user message, later candidates appended it again. Failed
embedded candidates could also persist an assistant error stub on each
retry, leaving same-role transcript runs that downstream providers reject.

The fallback callers now keep two persistence latches for one fallback run:
queuedUserMessagePersistedAcrossFallback flips from onUserMessagePersisted,
and assistantErrorPersistedAcrossFallback flips only after the session guard
actually persists an assistant stopReason="error" message. Later candidates
suppress only the entries that were already written, so CLI or otherwise
non-persisting failures do not hide the first embedded error separator.

Plumb the assistant-error persistence callback through the embedded runner,
attempt params, and session guard wrapper. Add guard and runner regression
tests for all-embedded fallback retries and CLI-to-embedded fallback.

Closes #83404
2026-05-18 15:01:46 +01:00
Peter Steinberger
4f4d108639 chore(lint): remove underscore-dangle allow list (#83542)
* chore(lint): reduce underscore-dangle exceptions

* chore(lint): reduce more underscore exceptions

* chore(lint): remove underscore-dangle allow list

* fix(lint): repair underscore cleanup regressions

* test(lint): track version define suppression
2026-05-18 14:56:06 +01:00
jasonyliu
5613f5fd05 fix(gateway): clear CLI bindings on session reset
Clear stale CLI provider resume bindings when a normal gateway session is reset, while preserving spawned subagent bindings.

Also isolate target normalization in the outbound source-delivery unit test so the CI shard does not load provider/plugin runtime state for a pure matcher case.

Co-authored-by: psyphix-claw <262498103+psyphix-claw@users.noreply.github.com>
2026-05-18 14:51:05 +01:00
LLagoon3
35cd2af159 Expose reload kind in config schema lookup (#81612)
Merged via squash.

Prepared head SHA: 9517cfa718
Co-authored-by: LLagoon3 <115124830+LLagoon3@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-05-18 16:39:12 +03:00
Yao
6a5a1353c7 fix(agents): skip fallback for session coordination errors
Preserve provider fallback metadata when session coordination errors are nested under provider failures.

Co-authored-by: luyao618 <364939526@qq.com>
2026-05-18 14:30:58 +01:00
Peter Steinberger
220d3ec26f docs: clarify formatter-friendly code shape 2026-05-18 14:26:00 +01:00
LLagoon3
bf95f762b5 fix(gateway): rotate failed sessions with missing transcripts 2026-05-18 14:19:40 +01:00
tanshanshan
40a5942091 fix(memory): keep qmd archived session hits visible
Keep QMD-exported archived session transcript hits visible by resolving QMD `.md` archive stems back to their live session ids before applying session visibility policy. Preserve normal markdown session ids that only resemble archive names, reject ambiguous slug fallback matches, and keep deleted same-agent QMD archives readable when the live store entry is gone.

Fixes #83506.

Co-authored-by: tanshanshan <tanshanshan@users.noreply.github.com>
2026-05-18 14:15:30 +01:00
Nimrod Gutman
b823a5a266 fix(ios): improve live activity lifecycle (#83597)
Merged via squash.

Prepared head SHA: 6bd991dafb
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-18 16:11:54 +03:00
Nimrod Gutman
29f39db857 fix(whatsapp): lower upload-file media sends (#81883)
Merged via squash.

Prepared head SHA: 3b2ae9c80d
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-18 16:09:24 +03:00
Ayaan Zaidi
651ec2027d fix(android): isolate timed out permission requests 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
4f5e817782 fix(android): escape call log like filters 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
d204ec0cc9 style(android): fix voice ktlint formatting 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
4712931e71 fix(android): filter unsafe markdown links 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
ce039eb103 fix(android): bound inline chat image payloads 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
d43f2f73f7 fix(android): reject unsupported gateway schemes 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
db2858cec7 fix(android): shorten talk mode final wait 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
ae25afdb62 fix(android): reset wake command dedupe per cycle 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
022a422755 fix(android): try final scaled jpeg size 2026-05-18 18:32:58 +05:30
Ayaan Zaidi
f1f92b8656 fix(android): restart gateway session on reconnect 2026-05-18 18:32:57 +05:30
Ayaan Zaidi
5fb9c0c937 fix(mantis): crop telegram proof chat pane 2026-05-18 18:30:36 +05:30
Peter Steinberger
880b39f061 refactor(messages): clarify Codex source delivery defaults (#83602) 2026-05-18 13:59:05 +01:00
Peter Steinberger
d29f77bece docs(agents): prefer cleaner code shape 2026-05-18 13:51:21 +01:00
Peter Steinberger
c32878d1b7 fix(messages): keep Codex source replies tool-gated 2026-05-18 13:51:21 +01:00
Peter Steinberger
4b35003051 fix(messages): keep Codex direct replies automatic 2026-05-18 13:51:21 +01:00
Peter Steinberger
0ed24da686 test: update gateway config write expectation 2026-05-18 13:47:49 +01:00
Peter Steinberger
e973aa278f test: add codex media path docker e2e 2026-05-18 13:45:35 +01:00
Peter Steinberger
2bb448908d fix: keep config writes independent of auth profile refs 2026-05-18 13:34:49 +01:00
Ayaan Zaidi
125ebd0987 fix(mantis): load telegram credential validator 2026-05-18 18:01:03 +05:30
Nimrod Gutman
a7ab09fa4e fix(gateway): allow mobile OS metadata refresh (#83490)
Merged via squash.

Prepared head SHA: 5fae3757e9
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-18 15:23:55 +03:00
Peter Steinberger
384ddae86f fix(codex): keep dynamic tools available in code mode (#83583) 2026-05-18 13:13:30 +01:00
Peter Steinberger
508945965a docs: record ci snapshot closeout notes 2026-05-18 13:08:51 +01:00
Peter Steinberger
67f8683ca3 fix: reduce strict-agentic activation logging 2026-05-18 13:07:41 +01:00
Ayaan Zaidi
2fa86c6a42 fix(mantis): point telegram proof skill at workflow command 2026-05-18 17:36:12 +05:30
Peter Steinberger
86885f31c1 docs(changelog): note Discord subagent thread fix 2026-05-18 13:02:39 +01:00
Craig
70c326f2be test(discord): accept delivery origin on spawn 2026-05-18 13:02:39 +01:00
Craig
61d583d59d fix(discord): return subagent thread delivery origin 2026-05-18 13:02:39 +01:00
Eva
2a0350b5b4 Separate prompt surfaces by selected harness (#83454)
* fix: scope agent prompt surfaces

* fix(codex): preserve lightweight project doc suppression

* fix(codex): demote openclaw context for native turns

* fix(codex): report demoted prompt context

* fix(codex): align demoted prompt observability

* docs: format codex runtime table

* docs: align codex prompt overlay docs

* test: align codex prompt snapshots

* test: update prompt snapshot contract

---------

Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-18 13:00:53 +01:00
Peter Steinberger
3132969c68 fix: fall back from official ClawHub artifact blocks (#83566)
* fix: fall back from official ClawHub artifact blocks

* test: refresh codex prompt snapshots

* test: refresh code mode prompt snapshots

* test: refresh linux prompt snapshots
2026-05-18 13:00:05 +01:00
Peter Steinberger
d1fa0f9628 fix(macos): keep settings sidebar visible 2026-05-18 12:53:18 +01:00
Peter Steinberger
edd97365f2 docs: add Discord realtime voice changelog (#80505) 2026-05-18 12:50:16 +01:00
Colin
6e80294079 Fix Discord realtime voice playback stability 2026-05-18 12:50:16 +01:00
Ayaan Zaidi
1f01ab3a30 chore(release): bump Android version to 2026.5.18 2026-05-18 17:13:09 +05:30
Peter Steinberger
a2d67959d7 fix(telegram): avoid reply fence spread allocation 2026-05-18 12:41:42 +01:00
Peter Steinberger
83b525bc1f fix(telegram): keep diagnostics on turn lane 2026-05-18 12:41:42 +01:00
Peter Steinberger
25aa72edbd fix(telegram): stop noninterrupting reply fences 2026-05-18 12:41:42 +01:00
Peter Steinberger
1ba9f5ded3 fix(telegram): isolate noninterrupting reply fences 2026-05-18 12:41:42 +01:00
Peter Steinberger
3bf518e518 fix(telegram): keep trajectory exports on turn lane 2026-05-18 12:41:42 +01:00
Peter Steinberger
e3d802a10b fix(telegram): harden spool timeout recovery 2026-05-18 12:41:42 +01:00
Peter Steinberger
9fa8b86891 fix: harden image metadata fallback (#83579) 2026-05-18 12:35:26 +01:00
Kaspre
fd8877b5fd fix(code-mode): honor agent scoped code mode
Fixes #83388.

- Honor per-agent `tools.codeMode` in config schema, runtime code-mode resolution, and model payload filtering.
- Preserve grouped OpenAI tool declarations when code-mode filtering keeps only `exec` and `wait`.
- Sync generated config/prompt baselines and carry a narrow media CI unblocker from current `main` fallout.

Co-authored-by: Kaspre <kaspre@gmail.com>
2026-05-18 12:26:46 +01:00
Lior Balmas
81f20d8464 feat(admin-http-rpc): allow web QR login methods (#83259)
* feat(admin-http-rpc): allow web QR login methods

* docs(changelog): note admin HTTP RPC web login methods

* test(codex): refresh prompt snapshots for code mode config

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-18 12:24:27 +01:00
Peter Steinberger
57c952f679 fix: add resilient media processing fallbacks (#83568) 2026-05-18 11:59:12 +01:00
Ayaan Zaidi
53d14d0561 fix: route Telegram topic media completions (#83556) (thanks @fuller-stack-dev) 2026-05-18 16:22:01 +05:30
fuller-stack-dev
ff47c51608 fix: route Telegram topic media completions 2026-05-18 16:22:01 +05:30
Ayaan Zaidi
6940a01e74 feat(android): show realtime talk transcripts 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
55f4b66a52 fix(android): stream realtime mic continuously 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
c86b89a5fe test(android): cover turn mic transcription lifecycle 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
22d98b4d52 fix(android): stream turn mic through gateway transcription 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
172eadbb6f fix(gateway): align transcription relay audio contract 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
ade3a6a3ad fix(android): gate realtime talk responses 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
3a2502de92 fix(android): surface voice recognition failures 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
5c01418442 fix(android): connect operator session without gateway auth 2026-05-18 16:19:33 +05:30
Ayaan Zaidi
ea3749872a fix(android): make realtime talk mode opt-in 2026-05-18 16:19:33 +05:30
Steven Liekens
0c125be717 fix(android): use realtime relay for talk mode 2026-05-18 16:19:33 +05:30
Peter Steinberger
419eea2462 fix(codex): stop forcing code-mode-only turns (#83561) 2026-05-18 11:39:31 +01:00
joshavant
9536d66a35 add changelog for empty cli response fix 2026-05-18 11:33:29 +01:00
joshavant
76ce72cbe5 fix empty cli response handling 2026-05-18 11:33:29 +01:00
Peter Steinberger
3a5627d911 fix(macos): avoid duplicate channel config heading 2026-05-18 11:16:55 +01:00
Peter Steinberger
253b24445e docs: update changelog for gateway update check (#83520) 2026-05-18 11:16:02 +01:00
samzong
5e33bb6458 fix(gateway): defer update check startup 2026-05-18 11:16:02 +01:00
Vincent Koc
d831b8e7bd chore: dedupe non-interactive setup prompters 2026-05-18 18:02:56 +08:00
Peter Steinberger
8e9d5c43d2 fix(messages): apply tts before message-tool sends (#83543) 2026-05-18 10:51:46 +01:00
Peter Steinberger
957d50ad49 fix: hide unsupported video audio refs 2026-05-18 10:48:39 +01:00
Sliverp
e6e1696c28 fix(qqbot): shorten typing keepalive window (#83469)
* fix(qqbot): shorten typing keepalive window

* docs: note qqbot typing keepalive fix

* fix(qqbot): shorten typing keepalive window

---------

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-18 17:48:02 +08:00
Peter Steinberger
6c6bc7fff5 ci: update performance artifact action 2026-05-18 10:46:26 +01:00
Peter Steinberger
018a6db132 ci: use node24 artifact action tags 2026-05-18 10:45:59 +01:00
Peter Steinberger
8bfdffad32 fix: keep autoreview pnpm checks local 2026-05-18 10:43:30 +01:00
Peter Steinberger
f14a0ed6cf fix: run autoreview checks from repo root 2026-05-18 10:43:30 +01:00
Peter Steinberger
b4cf06b0d7 fix: preserve autoreview prompt review status 2026-05-18 10:43:30 +01:00
Peter Steinberger
3015eeca94 fix: preserve autoreview maintainer policy 2026-05-18 10:43:30 +01:00
Peter Steinberger
53773ccee4 fix: keep autoreview checks offline 2026-05-18 10:43:30 +01:00
Peter Steinberger
f85534fc52 chore: auto-run offline package checks in autoreview 2026-05-18 10:43:30 +01:00
Peter Steinberger
0f4eccefd4 ci: use node24 artifact actions 2026-05-18 10:39:31 +01:00
Peter Steinberger
9eda9d1114 docs: clarify autoreview base prompt fallback 2026-05-18 10:34:36 +01:00
Vincent Koc
25a4a620d1 chore: dedupe legacy OAuth sidecar helpers 2026-05-18 17:33:40 +08:00
Peter Steinberger
4e3e659a20 fix: echo xai oauth pkce token fields (#83499)
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-18 10:28:44 +01:00
Ayaan Zaidi
826eb7c552 fix: harden release stability recovery (#83503) (thanks @100yenadmin) 2026-05-18 14:58:10 +05:30
Eva (agent)
08ecc518ec fix: close adversarial release stability gaps 2026-05-18 14:58:10 +05:30
Eva (agent)
54f87184f0 fix: recover stale release-stability stalls and auth loops 2026-05-18 14:58:10 +05:30
Ayaan Zaidi
6062f90d8b refactor(models): simplify codex auth route handling 2026-05-18 14:58:10 +05:30
Eva (agent)
a792068d9d fix: harden release stability diagnostics 2026-05-18 14:58:10 +05:30
tanshanshan
a51ee5b02d chore(lint): enable no-underscore-dangle 2026-05-18 10:26:24 +01:00
Peter Steinberger
8725364cf0 fix(codex): hydrate queued inbound images 2026-05-18 10:16:37 +01:00
Jason (Json)
3553aa3763 fix(tui): bound standalone exit
Fix standalone TUI shutdown so `/exit` cannot leave onboarding-launched TUI child processes alive after `runTui` returns.

Adds the maintainer changelog entry and keeps embedded `runTui` callers unaffected by making the post-return process-exit guard CLI-only.

Verification:
- `git diff --check origin/main...HEAD`
- `node scripts/run-vitest.mjs src/tui/tui.test.ts src/tui/tui-command-handlers.test.ts src/cli/program.smoke.test.ts src/tui/tui-launch.test.ts`
- `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main --parallel-tests "node scripts/run-vitest.mjs src/tui/tui.test.ts src/tui/tui-command-handlers.test.ts src/cli/program.smoke.test.ts src/tui/tui-launch.test.ts"`
- GitHub exact-head checks passed for `2413f48fc89b4deb9c1923ff63085c3cc2a69522`: CI `26023555738`, CodeQL `26023555736`, CodeQL Critical Quality `26023555785`, Real behavior proof `26023571192`, OpenGrep PR Diff `26023555745`, Workflow Sanity `26023555925`.
- `checks-node-core-fast` initially timed out in unrelated `src/infra/outbound/source-delivery-plan.test.ts`; rerunning failed jobs passed on the same head SHA.
2026-05-18 17:07:32 +08:00
Peter Steinberger
4b4f71a2cc fix(ui): polish reasoning labels and settings margins 2026-05-18 10:05:23 +01:00
Peter Steinberger
1e5450f23e fix(messages): keep group visible replies automatic by default (#83498)
* fix(messages): keep group visible replies automatic by default

* fix(messages): keep unauthorized slash turns quiet

* fix(messages): return boolean from slash guard

* test(messages): narrow visible reply fixtures

* test(messages): align completion delivery default
2026-05-18 09:48:58 +01:00
Josh Avant
5a7d31108e Load provider owner for Codex harness runtime (#83519)
* fix: load codex provider owner for harness runtime

* docs: add changelog for codex harness provider owner

* test: update codex harness owner expectation
2026-05-18 03:36:07 -05:00
Josh Avant
491ce8b753 fix(native-pi): pass Telegram images to Ollama (#83516)
* fix(native-pi): pass Telegram images to Ollama

* chore(changelog): note Telegram Ollama image fix
2026-05-18 03:22:10 -05:00
Vincent Koc
3a58621e72 fix(qa): use supported telegram streaming config in rtt 2026-05-18 16:22:07 +08:00
Vincent Koc
ac1b48efbc fix(macos): satisfy channel config swiftformat 2026-05-18 16:20:27 +08:00
Peter Steinberger
46bad8676c fix(macos): polish settings channel config 2026-05-18 09:16:36 +01:00
Peter Steinberger
bd69510662 feat(macos): add Dock menu shortcuts 2026-05-18 09:16:35 +01:00
Vincent Koc
4a1745281e fix(qa): decode OTLP smoke traces without generated internals 2026-05-18 16:06:43 +08:00
Vincent Koc
856a1692ff fix(qa): use final telegram replies for rtt runs 2026-05-18 16:06:10 +08:00
Josh Avant
b7735f88fa fix(telegram): recover stalled isolated spool handlers (#83505)
* fix(telegram): recover stalled isolated spool handlers

* chore(changelog): note telegram spool recovery fix

* test(telegram): satisfy spool timeout lint
2026-05-18 03:03:12 -05:00
Peter Steinberger
adc37670e8 fix(codex): preserve sandbox egress for app-server turns
Fixes #83347.
2026-05-18 09:00:51 +01:00
Peter Steinberger
3c36ea0dd7 docs: clarify lean refactor guidance 2026-05-18 08:53:27 +01:00
Peter Steinberger
4c613fbfe0 refactor(cron): centralize source delivery plan 2026-05-18 08:31:59 +01:00
Vincent Koc
81b9058cc3 chore(autoreview): scope Testbox policy to maintainers 2026-05-18 15:27:06 +08:00
computment
a9407d2f65 [codex] Fix Discord progress mode dropping final replies (#83443)
* fix(discord): deliver finals in progress mode

* docs(changelog): note Discord progress final delivery fix

---------

Co-authored-by: compoodment <compoodment@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-18 08:24:02 +01:00
Peter Steinberger
9e00234d2d ci: split cron runtime shard 2026-05-18 08:21:35 +01:00
Vincent Koc
3b5d30b5fd chore(autoreview): route OpenClaw validation to Testbox 2026-05-18 15:14:28 +08:00
samzong
27adbf9a1f [Test] Add gateway restart benchmark tooling (#83299)
* test(gateway): add repeated restart benchmark

Signed-off-by: samzong <samzong.lu@gmail.com>

* test(gateway): harden restart benchmark probes

Signed-off-by: samzong <samzong.lu@gmail.com>

* fix(gateway): count restart benchmark sample failures

* fix(gateway): harden restart benchmark portability

* fix(gateway): tighten restart benchmark attribution

* fix(gateway): preserve restart benchmark partial logs

* fix(gateway): start restart probes before sampling

* fix(gateway): avoid blocking restart probe sampling

* fix(gateway): keep missed restart outage nonfatal

---------

Signed-off-by: samzong <samzong.lu@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-18 08:13:31 +01:00
Peter Steinberger
e0bb46b93a docs: note gateway startup overlap 2026-05-18 08:03:39 +01:00
samzong
1ed4f747e9 fix(gateway): preserve deferred plugin services handle
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-18 08:03:39 +01:00
samzong
e7933c9137 perf(gateway): overlap startup work before ready
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-18 08:03:39 +01:00
Peter Steinberger
69aec10852 fix(agents): preserve code mode hook context (#83481) 2026-05-18 08:00:11 +01:00
joshavant
322f0bb7bc docs: add changelog for codex native task recovery 2026-05-18 01:52:28 -05:00
yshimadahrs-ship-it
56024b7828 fix(tasks): recover childless Codex native subagent tasks (#82836)
* fix(tasks): recover childless Codex native subagent tasks

* fix(tasks): harden codex native task recovery

---------

Co-authored-by: y.shimada <y.shimada@waishimadanoMac-mini.local>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-18 01:47:44 -05:00
Firas Alswihry
94c012b2ec test(qa-lab): add personal task followthrough scenario 2026-05-18 14:35:03 +08:00
Vincent Koc
fb70de8046 test(plugin-sdk): align debouncer runtime mock 2026-05-18 14:34:07 +08:00
Ayaan Zaidi
2696f2576d docs(changelog): note preview final delivery fix (#83468) 2026-05-18 11:55:03 +05:30
Ayaan Zaidi
ad4a74c884 test(reply): cover preview final delivery 2026-05-18 11:55:03 +05:30
Ayaan Zaidi
b6fd843288 fix(reply): keep final delivery after previews 2026-05-18 11:55:03 +05:30
Peter Steinberger
b3fc9fe079 fix(update): keep modern deferral metadata current 2026-05-18 07:21:42 +01:00
Peter Steinberger
394037c174 fix(update): defer configured plugin installs for shipped parents 2026-05-18 07:21:42 +01:00
Peter Steinberger
e20de0f603 fix: keep crabbox wrapper flags current 2026-05-18 07:21:42 +01:00
Peter Steinberger
2976517bc7 fix(plugins): gate onboarding ClawHub npm fallback 2026-05-18 07:21:42 +01:00
Peter Steinberger
00205cab08 fix(plugins): restrict ClawHub npm fallback scope 2026-05-18 07:21:42 +01:00
Peter Steinberger
db8de0db7a fix(update): preserve managed package manager roots 2026-05-18 07:21:42 +01:00
Peter Steinberger
aec0c56386 fix(update): harden legacy package handoff 2026-05-18 07:21:42 +01:00
Peter Steinberger
b278098a7c build: update proxyline dependency 2026-05-18 07:18:34 +01:00
Vincent Koc
29664863a5 fix(qa): stream mock response text deltas 2026-05-18 14:16:27 +08:00
Vincent Koc
61d9a6d750 fix(browser): preserve bridge diagnostic edge cases 2026-05-18 14:15:15 +08:00
Vincent Koc
ce62516251 fix(browser): tighten bridge diagnostics 2026-05-18 14:15:15 +08:00
scoootscooob
5a7b861ea2 fix(config): keep unrelated plugin diagnostics nonfatal (#83438)
* fix(config): keep unrelated plugin diagnostics nonfatal

* docs(changelog): mention config plugin validation fix
2026-05-17 23:11:15 -07:00
Peter Steinberger
e96428b008 fix(config): share subagent model schema (#83339)
* fix(config): share subagent model schema

* fix(config): remove subagent model timeout surface

* fix(config): migrate ignored agent model timeouts

* test(config): fix doctor migration lint

* test(extensions): remove retired model timeout fixture

* fix(config): collect default subagent pricing refs
2026-05-18 07:07:52 +01:00
Peter Steinberger
102e4f2c9d fix: replay Xiaomi Anthropic reasoning blocks 2026-05-18 06:53:17 +01:00
Peter Steinberger
476bd35431 fix: correct restart trace timer metrics (#83300) 2026-05-18 06:51:44 +01:00
Peter Steinberger
be17d55a5e docs: add changelog for ACPX restart trace attribution (#83300) (thanks @samzong) 2026-05-18 06:51:44 +01:00
samzong
51d44ab1fc fix: scope plugin service startup traces
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-18 06:51:44 +01:00
samzong
e292d3976a feat(gateway): attribute ACPX startup probe cost
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-05-18 06:51:44 +01:00
Jason (Json)
f79d842029 fix(codex): keep OpenClaw session spawn searchable
Keep OpenClaw session spawning searchable in Codex mode while steering Codex-native delegation through native subagents.

Verification:
- pnpm docs:list
- git diff --check
- pnpm prompt:snapshots:gen
- pnpm prompt:snapshots:check
- node scripts/run-vitest.mjs extensions/codex/src/app-server/dynamic-tools.test.ts extensions/codex/src/app-server/run-attempt.test.ts extensions/codex/src/app-server/thread-lifecycle.test.ts -t "turn-yield|searchable OpenClaw dynamic tools|Codex-native subagents primary"
- .agents/skills/autoreview/scripts/autoreview --mode local
- GitHub checks on d9237f7294: 69 success, 19 skipped, 1 neutral; merge state CLEAN

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-18 06:49:05 +01:00
Galin Iliev
74949eda2f fix(telegram): redact raw update logs (#82945)
* fix: redact telegram raw update logs

* fix telegram raw update log redaction

* add changelog for telegram raw update redaction

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-05-18 00:46:49 -05:00
Patrick Erichsen
9d4500f3ac test: cover gateway exec approval runtime flow (#83452)
* test: cover gateway exec approval runtime flow

* fix: satisfy exec approval e2e test types
2026-05-17 22:42:04 -07:00
Vincent Koc
3782294e92 docs(perf): record green rtt gate 2026-05-18 13:41:35 +08:00
Vincent Koc
3809ff4f2a docs(perf): record gateway rss import 2026-05-18 13:41:35 +08:00
Vincent Koc
c946ced9d5 docs(perf): link rtt importer follow-up 2026-05-18 13:41:35 +08:00
Vincent Koc
dd4790130e docs(perf): record changed-gate blocker 2026-05-18 13:41:35 +08:00
Vincent Koc
532a6a7a89 test(qa): add gateway heap checkpoints 2026-05-18 13:41:35 +08:00
Vincent Koc
c9b9fffc40 docs(perf): record rtt regression audit 2026-05-18 13:41:35 +08:00
Vincent Koc
2bf5e5f20d fix(qa): report discord scenario rtt 2026-05-18 13:41:35 +08:00
Vincent Koc
20ec5cdc42 test(qa): trace gateway rss in suite summaries 2026-05-18 13:41:35 +08:00
Vincent Koc
fc16df30dd fix(qa): preserve redacted discord rtt timestamps 2026-05-18 13:41:35 +08:00
Said Urtabajev
47b8e56e3f feat(docker): add image apt package build arg
feat(docker): add image apt package build arg

Add OPENCLAW_IMAGE_APT_PACKAGES as the preferred runtime-neutral image build arg for Docker and Podman apt package installs while keeping OPENCLAW_DOCKER_APT_PACKAGES as the legacy fallback.

Maintainer verification:
- pnpm docs:list
- node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts src/docker-setup.e2e.test.ts
- node scripts/run-vitest.mjs src/dockerfile.test.ts test/scripts/test-install-sh-docker.test.ts
- node scripts/run-vitest.mjs run --config test/vitest/vitest.cron.config.ts src/cron/isolated-agent.model-overrides.test.ts
- pnpm exec oxfmt --check --threads=1 docs/install/docker.md docs/install/podman.md scripts/clawdock/README.md docs/help/faq.md CHANGELOG.md
- git diff --check origin/main...HEAD
- .agents/skills/autoreview/scripts/autoreview --mode local
- .agents/skills/autoreview/scripts/autoreview --mode branch
- pnpm check:changed via Blacksmith Testbox tbx_01krwqmfhcdekaczvrkxnb7t59, Actions run 26014630478, exit 0

Known CI note: checks-node-core-runtime-shared timed out repeatedly in unrelated src/cron/isolated-agent.model-overrides.test.ts on GitHub Actions; the same test passes locally after this rebase.

Co-authored-by: Said Urtabajev <said@bumpclub.ee>
2026-05-18 06:37:16 +01:00
Vincent Koc
57bc26893e test(cron): force PI for timeout override assertions 2026-05-18 13:33:07 +08:00
Peter Steinberger
eca402da79 ci: consolidate short CI shards 2026-05-18 06:29:14 +01:00
Peter Steinberger
e453a39d6b build: align node version floor 2026-05-18 06:28:14 +01:00
Peter Steinberger
f7196e3b53 build: update pi dependencies to 0.75.1 2026-05-18 06:22:36 +01:00
Vincent Koc
9d5db92cda test(cron): type OpenAI PI override config 2026-05-18 13:22:23 +08:00
Vincent Koc
939712fbbf test(cron): keep OpenAI override assertions on PI 2026-05-18 13:12:01 +08:00
Peter Steinberger
55ca2df62a fix: polish Mac settings layout 2026-05-18 06:11:14 +01:00
Jesse Merhi
198f20fd20 Fix approval runtime gateway calls (#83433)
* fix approval runtime gateway calls

* docs: credit approval runtime fix contributors

* docs: include maintainer changelog credit
2026-05-18 15:10:15 +10:00
Vincent Koc
e3d5518838 test(agents): cover Codex preflight plugin load 2026-05-18 12:55:02 +08:00
Ayaan Zaidi
46f27b6e07 docs(xai): clarify oauth account eligibility 2026-05-18 04:53:57 +00:00
Vincent Koc
8f27b3e21f fix(agents): fail closed on missing Codex harness 2026-05-18 12:47:51 +08:00
Ayaan Zaidi
cd15ce35a0 fix(qa): keep telegram user creds mantis-only 2026-05-18 10:04:58 +05:30
Josh Avant
395bd578d2 Fix Telegram hot reload polling restarts (#83410)
* fix(telegram): preserve hot reload polling restarts

* docs: add changelog for telegram hot reload fix
2026-05-17 23:24:04 -05: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
5481 changed files with 434138 additions and 64505 deletions

View File

@@ -0,0 +1,184 @@
---
name: autoreview
description: "Auto Review closeout. Codex review is the default when no engine is set and is the recommended reviewer."
---
# Auto Review
Run the bundled structured review helper as a closeout check. This is code review, not Guardian `auto_review` approval routing.
Codex review is the default when no engine is set. It usually delivers the best review results and should remain the normal final closeout engine.
Use when:
- user asks for Codex review / Claude 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 structured review returns no accepted/actionable findings.
- If a review-triggered fix changes code, rerun focused tests and rerun the structured review helper.
- For security-audit suppression changes, verify accepted findings remain auditable: suppressed findings stay in structured output, active output keeps an unsuppressible suppression notice, and aggregate findings cannot hide unrelated active risk.
- Never switch or override the requested review engine/model. If the review hits model capacity, retry the same command a few times with the same engine/model.
- Tools are useful in review mode. The helper allows read-only inspection tools and web search by default so reviewers can check dependency contracts, upstream docs, and current behavior.
- Security perspective is always included, but it should not cripple legitimate functionality. Report security findings only when the change creates a concrete, actionable risk or removes an important safety check.
- Do not invoke built-in `codex review`, nested reviewers, or reviewer panels from inside the review. The helper builds one bundle, calls one selected engine, validates one structured result, and stops.
- Stop as soon as the helper exits 0 with no accepted/actionable findings. Do not run an extra 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.
- Multi-reviewer panels are opt-in only. Use them when explicitly requested or when risk justifies the extra spend; the main agent still verifies every accepted finding before fixing.
- 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.
- If `gh`/Gitcrawl reports `database disk image is malformed`, run `gitcrawl doctor --json` once to let the portable cache repair before retrying review; do not bypass the shim unless repair fails and freshness requires live GitHub.
- If Gitcrawl reports a portable manifest mismatch, source/runtime DB health error, or stale portable-store checkout, run `gitcrawl doctor --json` and inspect `source_db_health`, `runtime_db_health`, and `portable_store_status` before falling back to live GitHub.
- Do not push just to review. Push only when the user requested push/ship/PR update.
## Pick Target
Dirty local work:
```bash
<autoreview-helper> --mode local
```
Use this only when the patch is actually unstaged/staged/untracked in the
current checkout. For committed, pushed, or PR work, point the helper at the commit
or branch diff instead; do not force `--mode local` / `--uncommitted` just
because the helper docs mention dirty work first. A clean local review
only proves there is no local patch.
Branch/PR work:
```bash
<autoreview-helper> --mode branch --base origin/main
```
Optional review context is first-class:
```bash
<autoreview-helper> --mode branch --base origin/main --prompt-file /tmp/review-notes.md --dataset /tmp/evidence.json
```
If an open PR exists, use its actual base:
```bash
base=$(gh pr view --json baseRefName --jq .baseRefName)
<autoreview-helper> --mode branch --base "origin/$base"
```
Committed single change:
```bash
<autoreview-helper> --mode commit --commit HEAD
```
or with the helper:
```bash
/Users/steipete/Projects/agent-scripts/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
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.
## Review Panels
Run multiple reviewers against one frozen bundle:
```bash
<autoreview-helper> --reviewers codex,claude
```
`--panel` is shorthand for Codex plus Claude unless `--engine` changes the first reviewer:
```bash
<autoreview-helper> --panel
```
Set reviewer models and thinking/effort explicitly:
```bash
<autoreview-helper> --reviewers codex,claude --model codex=gpt-5.1 --thinking codex=high --model claude=sonnet --thinking claude=max
```
Inline syntax is also supported:
```bash
<autoreview-helper> --reviewers codex:gpt-5.1:high,claude:sonnet:max
```
Codex maps thinking to `model_reasoning_effort` and accepts `low`, `medium`,
`high`, or `xhigh`. Claude maps thinking to `--effort` and also accepts `max`.
Engines without a real thinking knob reject `--thinking`.
## Context Efficiency
Run the helper directly so target selection, engine choice, structured validation, and exit status all stay in one path. If output is noisy, summarize the completed helper output after it returns; do not ask another agent or reviewer to rerun the review.
## Helper
OpenClaw repo-local helper:
```bash
.agents/skills/autoreview/scripts/autoreview --help
```
`agent-scripts` checkout helper:
```bash
skills/autoreview/scripts/autoreview --help
```
Global helper from `agent-scripts`:
```bash
~/.codex/skills/agent-scripts/autoreview/scripts/autoreview --help
```
If installed from `agent-scripts`, path is:
```bash
/Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --help
```
The helper:
- chooses dirty local changes first
- otherwise uses current PR base if `gh pr view` works
- otherwise uses `origin/main` for non-main branches
- supports `--engine codex`, `claude`, `droid`, and `copilot`; default is `AUTOREVIEW_ENGINE` or `codex`; Codex should remain the default when nothing is set
- 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
- writes only to stdout unless `--output` or `--json-output` is set
- supports `--dry-run`, `--parallel-tests`, `--prompt`, `--prompt-file`, `--dataset`, `--no-tools`, `--no-web-search`, and commit refs
- supports opt-in review panels with `--panel` / `--reviewers`, plus per-engine `--model` and `--thinking`
- allows read-only tools and web search by default where the selected CLI supports them; forbids nested review in the prompt; Codex is run through `codex exec` with read-only sandbox and structured output
- prints `autoreview clean: no accepted/actionable findings reported` when the selected review command exits 0
- exits nonzero when accepted/actionable findings are present
## 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 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,896 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import concurrent.futures
import copy
import json
import os
import subprocess
import sys
import tempfile
import textwrap
import time
from pathlib import Path
from typing import Any
ENGINES = ("codex", "claude", "droid", "copilot")
THINKING_LEVELS_BY_ENGINE = {
"codex": {"low", "medium", "high", "xhigh"},
"claude": {"low", "medium", "high", "xhigh", "max"},
"droid": set(),
"copilot": set(),
}
SCHEMA: dict[str, Any] = {
"type": "object",
"additionalProperties": False,
"required": [
"findings",
"overall_correctness",
"overall_explanation",
"overall_confidence",
],
"properties": {
"findings": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": False,
"required": [
"title",
"body",
"priority",
"confidence",
"category",
"code_location",
],
"properties": {
"title": {"type": "string", "minLength": 1, "maxLength": 140},
"body": {"type": "string", "minLength": 1, "maxLength": 2000},
"priority": {"type": "string", "enum": ["P0", "P1", "P2", "P3"]},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"category": {
"type": "string",
"enum": ["bug", "security", "regression", "test_gap", "maintainability"],
},
"code_location": {
"type": "object",
"additionalProperties": False,
"required": ["file_path", "line"],
"properties": {
"file_path": {"type": "string", "minLength": 1},
"line": {"type": "integer", "minimum": 1},
},
},
},
},
},
"overall_correctness": {
"type": "string",
"enum": ["patch is correct", "patch is incorrect"],
},
"overall_explanation": {"type": "string", "minLength": 1, "maxLength": 3000},
"overall_confidence": {"type": "number", "minimum": 0, "maximum": 1},
},
}
def run(args: list[str], cwd: Path, *, input_text: str | None = None, check: bool = True) -> subprocess.CompletedProcess[str]:
result = subprocess.run(
args,
cwd=cwd,
input=input_text,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if check and result.returncode != 0:
cmd = " ".join(args)
raise SystemExit(f"command failed ({result.returncode}): {cmd}\n{result.stderr or result.stdout}")
return result
def git(repo: Path, *args: str, check: bool = True) -> str:
return run(["git", *args], repo, check=check).stdout
def repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if result.returncode != 0:
raise SystemExit("autoreview must run inside a git repository")
return Path(result.stdout.strip()).resolve()
def current_branch(repo: Path) -> str:
return git(repo, "branch", "--show-current", check=False).strip() or "detached"
def is_dirty(repo: Path) -> bool:
return bool(git(repo, "status", "--porcelain").strip())
def choose_target(repo: Path, mode: str, base_ref: str | None) -> tuple[str, str | None]:
branch = current_branch(repo)
if mode == "local" or (mode == "auto" and is_dirty(repo)):
return "local", None
if mode == "commit":
return "commit", None
if mode == "branch" or (mode == "auto" and branch != "main"):
return "branch", base_ref or detect_pr_base(repo) or "origin/main"
raise SystemExit("no review target: clean main checkout and no forced mode")
def detect_pr_base(repo: Path) -> str | None:
if not shutil_which("gh"):
return None
result = run(["gh", "pr", "view", "--json", "baseRefName", "--jq", ".baseRefName"], repo, check=False)
base = result.stdout.strip()
return f"origin/{base}" if result.returncode == 0 and base else None
def shutil_which(name: str) -> str | None:
for part in os.environ.get("PATH", "").split(os.pathsep):
candidate = Path(part) / name
if candidate.exists() and os.access(candidate, os.X_OK):
return str(candidate)
return None
def bounded(text: str, limit: int = 180_000) -> str:
if len(text) <= limit:
return text
return text[:limit] + f"\n\n[truncated at {limit} characters]\n"
def bounded_field(text: str, limit: int) -> str:
if len(text) <= limit:
return text
suffix = "\n\n[truncated]"
return text[: max(0, limit - len(suffix))] + suffix
def read_text(path: Path, limit: int = 40_000) -> str:
try:
data = path.read_bytes()
except OSError as exc:
return f"[unreadable: {exc}]"
if b"\0" in data:
return "[binary file omitted]"
text = data.decode("utf-8", errors="replace")
return bounded(text, limit)
def local_bundle(repo: Path) -> str:
parts = [
"# Git Status",
git(repo, "status", "--short"),
"# Staged Diff",
git(repo, "diff", "--cached", "--stat"),
bounded(git(repo, "diff", "--cached", "--patch", "--find-renames")),
"# Unstaged Diff",
git(repo, "diff", "--stat"),
bounded(git(repo, "diff", "--patch", "--find-renames")),
]
untracked = [line for line in git(repo, "ls-files", "--others", "--exclude-standard").splitlines() if line]
if untracked:
parts.append("# Untracked Files")
for rel in untracked:
path = repo / rel
parts.append(f"## {rel}\n{read_text(path)}")
return "\n\n".join(parts)
def branch_bundle(repo: Path, base_ref: str) -> str:
git(repo, "fetch", "origin", "--quiet", check=False)
return "\n\n".join(
[
"# Branch Diff",
f"base: {base_ref}",
git(repo, "diff", "--stat", f"{base_ref}...HEAD"),
bounded(git(repo, "diff", "--patch", "--find-renames", f"{base_ref}...HEAD")),
]
)
def commit_bundle(repo: Path, commit_ref: str) -> str:
return "\n\n".join(
[
"# Commit Diff",
f"commit: {commit_ref}",
git(repo, "show", "--stat", "--format=fuller", commit_ref),
bounded(git(repo, "show", "--patch", "--find-renames", "--format=fuller", commit_ref)),
]
)
def review_paths(repo: Path, target: str, target_ref: str | None, commit_ref: str) -> set[str]:
names: set[str] = set()
if target == "local":
sources = [
git(repo, "diff", "--name-only", "--cached"),
git(repo, "diff", "--name-only"),
git(repo, "ls-files", "--others", "--exclude-standard"),
]
elif target == "branch":
assert target_ref
sources = [git(repo, "diff", "--name-only", f"{target_ref}...HEAD")]
else:
sources = [git(repo, "show", "--name-only", "--format=", commit_ref)]
for source in sources:
for line in source.splitlines():
path = line.strip()
if path:
names.add(path)
return names
def load_extra_prompt(args: argparse.Namespace) -> str:
chunks: list[str] = []
for value in args.prompt or []:
chunks.append(value)
for path in args.prompt_file or []:
chunks.append(Path(path).read_text())
return "\n\n".join(chunks)
def load_datasets(args: argparse.Namespace) -> str:
chunks: list[str] = []
for spec in args.dataset or []:
path = Path(spec)
if path.is_dir():
raise SystemExit(f"--dataset must be a file, got directory: {path}")
chunks.append(f"# Dataset: {path}\n{read_text(path)}")
return "\n\n".join(chunks)
def build_prompt(repo: Path, target: str, target_ref: str | None, bundle: str, extra_prompt: str, datasets: str) -> str:
target_line = f"{target} {target_ref}" if target_ref else target
return textwrap.dedent(
f"""
You are a senior code reviewer. Review the provided git change bundle only.
Hard rules:
- Return exactly one JSON object and nothing else. Do not wrap it in Markdown.
- The JSON object must match this schema exactly:
{json.dumps(SCHEMA, indent=2)}
- Do not modify files.
- Do not invoke nested reviewers or review tools.
- Forbidden nested review commands include: codex review, autoreview, claude review, oracle review.
- You may use read-only tools and web search to inspect files, dependency contracts, upstream docs, current behavior, and security implications.
- Shell commands, if available, must be read-only inspection commands. Do not run tests, formatters, package installs, generators, network mutation commands, git mutation commands, or commands that write files.
- Report only actionable defects introduced or exposed by this change.
- Prefer high-signal findings over style feedback.
- Include security findings: injection, secret leaks, authz/authn bypass, path traversal, unsafe deserialization, unsafe filesystem or shell use, privacy leaks, and credential handling.
- Do not reject legitimate functionality merely because it touches shell, filesystem, network, auth, or sensitive data. Report a security finding only when the patch creates a concrete exploitable risk, removes an important safety check, or lacks validation at a trust boundary.
- For each finding, use the smallest file/line location that demonstrates the issue.
- If there are no actionable findings, return an empty findings array and mark the patch correct.
Review target: {target_line}
Repository: {repo}
{extra_prompt}
{datasets}
# Change Bundle
{bundle}
"""
).strip()
def write_json_temp(data: dict[str, Any]) -> Path:
handle = tempfile.NamedTemporaryFile("w", suffix=".json", delete=False)
with handle:
json.dump(data, handle)
return Path(handle.name)
def run_codex(args: argparse.Namespace, repo: Path, prompt: str) -> str:
if not args.tools:
raise SystemExit("--no-tools is not supported by the Codex engine; use --engine claude --no-tools for a no-tools run")
schema_path = write_json_temp(SCHEMA)
output_path = Path(tempfile.NamedTemporaryFile("w", suffix=".json", delete=False).name)
cmd = [args.codex_bin, "--ask-for-approval", "never"]
if args.web_search:
cmd.append("--search")
if args.model:
cmd.extend(["--model", args.model])
if args.thinking:
cmd.extend(["-c", f'model_reasoning_effort="{args.thinking}"'])
cmd.extend(
[
"exec",
"--ephemeral",
"-C",
str(repo),
"-s",
"read-only",
"--output-schema",
str(schema_path),
"--output-last-message",
str(output_path),
"-",
]
)
result = run(cmd, repo, input_text=prompt, check=False)
try:
output = output_path.read_text()
finally:
schema_path.unlink(missing_ok=True)
output_path.unlink(missing_ok=True)
if result.returncode != 0:
raise SystemExit(f"codex engine failed ({result.returncode})\n{result.stderr or result.stdout}")
return output or result.stdout
def run_claude(args: argparse.Namespace, repo: Path, prompt: str) -> str:
cmd = [
args.claude_bin,
"--print",
"--no-session-persistence",
"--output-format",
"json",
"--json-schema",
json.dumps(SCHEMA),
]
if args.tools:
cmd.extend(["--allowedTools", claude_allowed_tools(args)])
else:
cmd.extend(["--tools", ""])
if args.model:
cmd.extend(["--model", args.model])
if args.thinking:
cmd.extend(["--effort", args.thinking])
result = run(cmd, repo, input_text=prompt, check=False)
if result.returncode != 0:
raise SystemExit(f"claude engine failed ({result.returncode})\n{result.stderr or result.stdout}")
return result.stdout
def run_droid(args: argparse.Namespace, repo: Path, prompt: str) -> str:
if args.thinking:
raise SystemExit("--thinking is not supported by the droid engine")
prompt_path = Path(tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False).name)
prompt_path.write_text(prompt)
cmd = [
args.droid_bin,
"exec",
"--cwd",
str(repo),
"--output-format",
"json",
"-f",
str(prompt_path),
]
if args.model:
cmd.extend(["--model", args.model])
if not args.tools:
cmd.extend(["--disabled-tools", "*"])
result = run(cmd, repo, check=False)
prompt_path.unlink(missing_ok=True)
if result.returncode != 0:
raise SystemExit(f"droid engine failed ({result.returncode})\n{result.stderr or result.stdout}")
return result.stdout
def run_copilot(args: argparse.Namespace, repo: Path, prompt: str) -> str:
if args.thinking:
raise SystemExit("--thinking is not supported by the copilot engine")
if not args.tools:
raise SystemExit("--no-tools is not supported by the copilot engine; copilot requires a read-only file view tool to load the review bundle without exposing it in argv")
with tempfile.TemporaryDirectory(prefix="autoreview-copilot.") as tempdir:
prompt_path = Path(tempdir) / "prompt.txt"
prompt_path.write_text(prompt)
os.chmod(prompt_path, 0o600)
cmd = [
args.copilot_bin,
"-C",
tempdir,
"-p",
"Read ./prompt.txt and follow it exactly. Return only the requested JSON object.",
"--output-format",
"json",
"--stream",
"off",
"--no-ask-user",
"--disable-builtin-mcps",
]
if args.model:
cmd.extend(["--model", args.model])
cmd.extend(
[
"--available-tools=read_agent,rg,view,web_fetch",
"--allow-tool=read_agent",
"--allow-tool=rg",
"--allow-tool=view",
"--allow-tool=web_fetch",
]
)
if args.web_search:
cmd.append("--allow-all-urls")
result = run(cmd, Path(tempdir), check=False)
if result.returncode != 0:
raise SystemExit(f"copilot engine failed ({result.returncode})\n{result.stderr or result.stdout}")
return result.stdout
def claude_allowed_tools(args: argparse.Namespace) -> str:
tools = [tool.strip() for tool in args.claude_allowed_tools.split(",") if tool.strip()]
if not args.web_search:
tools = [tool for tool in tools if tool not in {"WebSearch", "WebFetch"}]
return ",".join(tools)
def extract_json(text: str) -> dict[str, Any]:
stripped = text.strip()
if not stripped:
raise SystemExit("review engine returned empty output")
try:
parsed = json.loads(stripped)
except json.JSONDecodeError as exc:
fenced_report = parse_json_candidate(stripped)
if isinstance(fenced_report, dict) and "findings" in fenced_report:
return fenced_report
jsonl_report = extract_json_from_jsonl(stripped)
if jsonl_report:
return jsonl_report
raise SystemExit(f"review engine returned non-JSON output: {exc}\n{stripped[:2000]}")
if isinstance(parsed, dict) and "findings" in parsed:
return parsed
if isinstance(parsed, dict) and isinstance(parsed.get("structured_output"), dict):
return parsed["structured_output"]
if isinstance(parsed, dict) and isinstance(parsed.get("result"), str):
result_json = parse_json_candidate(parsed["result"])
if isinstance(result_json, dict) and "findings" in result_json:
return result_json
raise SystemExit(f"review engine result was not structured JSON:\n{parsed['result'][:2000]}")
jsonl_report = extract_json_from_jsonl(stripped)
if jsonl_report:
return jsonl_report
raise SystemExit(f"review engine returned unexpected JSON shape:\n{json.dumps(parsed)[:2000]}")
def extract_json_from_jsonl(text: str) -> dict[str, Any] | None:
candidates: list[str] = []
for line in text.splitlines():
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if not isinstance(event, dict):
continue
part = event.get("part")
if isinstance(part, dict) and isinstance(part.get("text"), str):
candidates.append(part["text"])
data = event.get("data")
if isinstance(data, dict) and isinstance(data.get("content"), str):
candidates.append(data["content"])
if isinstance(event.get("result"), str):
candidates.append(event["result"])
for candidate in reversed(candidates):
parsed = parse_json_candidate(candidate)
if isinstance(parsed, dict) and "findings" in parsed:
return parsed
return None
def parse_json_candidate(text: str) -> Any | None:
stripped = text.strip()
if stripped.startswith("```"):
lines = stripped.splitlines()
if lines and lines[0].startswith("```") and lines[-1].strip() == "```":
stripped = "\n".join(lines[1:-1]).strip()
try:
parsed = json.loads(stripped)
except json.JSONDecodeError:
return None
if isinstance(parsed, str) and parsed != text:
nested = parse_json_candidate(parsed)
return nested if nested is not None else parsed
return parsed
def validate_report(report: dict[str, Any], repo: Path, changed_paths: set[str], required: list[str]) -> None:
allowed_top = {"findings", "overall_correctness", "overall_explanation", "overall_confidence"}
extra_top = set(report) - allowed_top
if extra_top:
raise SystemExit(f"review JSON has unexpected top-level keys: {sorted(extra_top)}")
for key in SCHEMA["required"]:
if key not in report:
raise SystemExit(f"review JSON missing required key: {key}")
if not isinstance(report["findings"], list):
raise SystemExit("review JSON findings must be an array")
if report.get("overall_correctness") not in {"patch is correct", "patch is incorrect"}:
raise SystemExit(f"review JSON has invalid overall_correctness: {report.get('overall_correctness')}")
if not isinstance(report.get("overall_explanation"), str) or not report["overall_explanation"]:
raise SystemExit("review JSON overall_explanation must be a non-empty string")
if len(report["overall_explanation"]) > 3000:
raise SystemExit("review JSON overall_explanation is too long")
if not number_in_range(report.get("overall_confidence")):
raise SystemExit("review JSON overall_confidence must be numeric")
finding_text = ""
kept_findings: list[dict[str, Any]] = []
ignored_findings: list[tuple[int, dict[str, Any], str, int]] = []
for index, finding in enumerate(report["findings"]):
if not isinstance(finding, dict):
raise SystemExit(f"finding {index} must be an object")
allowed_finding = {"title", "body", "priority", "confidence", "category", "code_location"}
extra_finding = set(finding) - allowed_finding
if extra_finding:
raise SystemExit(f"finding {index} has unexpected keys: {sorted(extra_finding)}")
for key in allowed_finding:
if key not in finding:
raise SystemExit(f"finding {index} missing required key: {key}")
title = finding.get("title")
if not isinstance(title, str) or not title or len(title) > 140:
raise SystemExit(f"finding {index} has invalid title")
body = finding.get("body")
if not isinstance(body, str) or not body or len(body) > 2000:
raise SystemExit(f"finding {index} has invalid body")
priority = finding.get("priority")
if priority not in {"P0", "P1", "P2", "P3"}:
raise SystemExit(f"finding {index} has invalid priority: {priority}")
if not number_in_range(finding.get("confidence")):
raise SystemExit(f"finding {index} has invalid confidence")
category = finding.get("category")
if category not in {"bug", "security", "regression", "test_gap", "maintainability"}:
raise SystemExit(f"finding {index} has invalid category: {category}")
location = finding.get("code_location")
if not isinstance(location, dict):
raise SystemExit(f"finding {index} missing code_location")
rel = str(location.get("file_path", "")).strip()
line = location.get("line")
if not rel or not isinstance(line, int) or line < 1:
raise SystemExit(f"finding {index} has invalid location: {location}")
if Path(rel).is_absolute() or ".." in Path(rel).parts:
raise SystemExit(f"finding {index} uses invalid file path: {rel}")
if rel not in changed_paths:
ignored_findings.append((index, finding, rel, line))
continue
kept_findings.append(finding)
finding_text += "\n" + json.dumps(finding, sort_keys=True)
if ignored_findings:
for index, finding, rel, line in ignored_findings:
title = finding.get("title", "<untitled>")
print(
f"autoreview ignored out-of-scope finding {index}: {title} ({rel}:{line})",
file=sys.stderr,
)
print(bounded_field(str(finding.get("body", "")), 500), file=sys.stderr)
report["findings"] = kept_findings
if not kept_findings and report["overall_correctness"] == "patch is incorrect":
note = f"Ignored {len(ignored_findings)} out-of-scope finding(s) outside the reviewed change."
explanation = report["overall_explanation"].rstrip()
report["overall_correctness"] = "patch is correct"
report["overall_explanation"] = bounded_field(f"{explanation}\n\n{note}", 3000)
haystack = finding_text.lower()
for needle in required:
if needle.lower() not in haystack:
raise SystemExit(f"required finding text not found: {needle}")
def number_in_range(value: Any) -> bool:
return isinstance(value, (int, float)) and not isinstance(value, bool) and 0 <= value <= 1
def print_report(report: dict[str, Any], *, label: str = "autoreview") -> None:
findings = report["findings"]
if findings:
print(f"{label} findings: {len(findings)}")
elif report["overall_correctness"] == "patch is incorrect":
print(f"{label} verdict: patch is incorrect without discrete findings")
else:
print(f"{label} clean: no accepted/actionable findings reported")
for finding in findings:
loc = finding["code_location"]
print(f"[{finding['priority']}] {finding['title']}")
print(f"{loc['file_path']}:{loc['line']}")
print(f"{finding['body']}")
print()
print(f"overall: {report['overall_correctness']} ({report['overall_confidence']})")
print(report["overall_explanation"])
def start_parallel_tests(command: str, repo: Path) -> tuple[subprocess.Popen, float]:
print(f"tests: {command}")
return subprocess.Popen(command, cwd=repo, shell=True), time.time()
def finish_parallel_tests(proc: subprocess.Popen, started: float) -> int:
proc.wait()
print(f"tests exit: {proc.returncode} after {int(time.time() - started)}s")
return int(proc.returncode or 0)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Bundle-driven AI code review.")
parser.add_argument("--mode", choices=["auto", "local", "branch", "commit"], default="auto")
parser.add_argument("--base")
parser.add_argument("--commit", default="HEAD")
parser.add_argument("--engine", choices=ENGINES, default=os.environ.get("AUTOREVIEW_ENGINE", "codex"))
parser.add_argument("--reviewers", help="Comma-separated review panel, e.g. codex,claude or codex:gpt-5:high.")
parser.add_argument("--panel", action="store_true", help="Run a Codex/Claude review panel unless --engine changes the first reviewer.")
parser.add_argument("--model", action="append", help="Model for all reviewers or engine=model. Repeatable.")
parser.add_argument("--thinking", action="append", help="Thinking/effort for all reviewers or engine=level. Repeatable. Codex: low, medium, high, xhigh. Claude: low, medium, high, xhigh, max.")
parser.add_argument("--allow-partial-panel", action="store_true", help="Continue panel output when one reviewer fails.")
parser.add_argument("--codex-bin", default=os.environ.get("CODEX_BIN", "codex"))
parser.add_argument("--claude-bin", default=os.environ.get("CLAUDE_BIN", "claude"))
parser.add_argument("--droid-bin", default=os.environ.get("DROID_BIN", "droid"))
parser.add_argument("--copilot-bin", default=os.environ.get("COPILOT_BIN", "copilot"))
parser.add_argument("--no-tools", dest="tools", action="store_false", default=True, help="Disable tools for engines that support it. Codex and copilot reject no-tools review.")
parser.add_argument("--no-web-search", dest="web_search", action="store_false", default=True)
parser.add_argument(
"--claude-allowed-tools",
default=os.environ.get(
"AUTOREVIEW_CLAUDE_TOOLS",
"Read,Grep,Glob,WebSearch,WebFetch",
),
)
parser.add_argument("--prompt", action="append", help="Additional review instruction text.")
parser.add_argument("--prompt-file", action="append", help="Additional review instruction file.")
parser.add_argument("--dataset", action="append", help="Extra evidence file to include in the review bundle.")
parser.add_argument("--output", help="Write human output to a file as well as stdout.")
parser.add_argument("--json-output", help="Write validated structured review JSON.")
parser.add_argument("--parallel-tests", help="Run a test command concurrently with review; failure fails the helper.")
parser.add_argument("--require-finding", action="append", default=[], help="Require finding text to contain this substring.")
parser.add_argument("--expect-findings", action="store_true", help="Treat findings as success; for harness acceptance tests.")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
if args.engine not in ENGINES:
raise SystemExit(f"invalid --engine/AUTOREVIEW_ENGINE: {args.engine}")
return args
def run_engine(args: argparse.Namespace, repo: Path, prompt: str) -> str:
if args.engine == "codex":
return run_codex(args, repo, prompt)
if args.engine == "claude":
return run_claude(args, repo, prompt)
if args.engine == "droid":
return run_droid(args, repo, prompt)
if args.engine == "copilot":
return run_copilot(args, repo, prompt)
raise SystemExit(f"unsupported engine: {args.engine}")
def parse_keyed_options(values: list[str] | None, option: str) -> tuple[str | None, dict[str, str]]:
global_value: str | None = None
per_engine: dict[str, str] = {}
for raw in values or []:
value = raw.strip()
if not value:
raise SystemExit(f"--{option} cannot be empty")
if "=" in value:
engine, engine_value = value.split("=", 1)
engine = engine.strip()
engine_value = engine_value.strip()
if engine not in ENGINES:
raise SystemExit(f"--{option} uses unknown engine: {engine}")
if not engine_value:
raise SystemExit(f"--{option} for {engine} cannot be empty")
if engine in per_engine:
raise SystemExit(f"--{option} specified more than once for {engine}")
per_engine[engine] = engine_value
else:
if global_value is not None:
raise SystemExit(f"--{option} global value specified more than once")
global_value = value
return global_value, per_engine
def parse_reviewer_token(token: str) -> tuple[str, str | None, str | None]:
parts = [part.strip() for part in token.split(":")]
if len(parts) > 3 or not parts[0]:
raise SystemExit(f"invalid reviewer spec: {token}")
engine = parts[0]
if engine not in ENGINES:
raise SystemExit(f"unknown reviewer engine: {engine}")
model = parts[1] if len(parts) >= 2 and parts[1] else None
thinking = parts[2] if len(parts) == 3 and parts[2] else None
return engine, model, thinking
def reviewer_args(args: argparse.Namespace) -> list[argparse.Namespace]:
global_model, model_by_engine = parse_keyed_options(args.model, "model")
global_thinking, thinking_by_engine = parse_keyed_options(args.thinking, "thinking")
reviewers: list[tuple[str, str | None, str | None]] = []
if args.reviewers:
tokens = [token.strip() for token in args.reviewers.split(",") if token.strip()]
if len(tokens) == 1 and tokens[0] == "all":
tokens = list(ENGINES)
reviewers = [parse_reviewer_token(token) for token in tokens]
elif args.panel:
engines = [args.engine]
for engine in ("codex", "claude"):
if engine not in engines:
engines.append(engine)
reviewers = [(engine, None, None) for engine in engines]
else:
reviewers = [(args.engine, None, None)]
seen: set[str] = set()
result: list[argparse.Namespace] = []
for engine, inline_model, inline_thinking in reviewers:
if engine in seen:
raise SystemExit(f"reviewer specified more than once: {engine}")
seen.add(engine)
model = inline_model or model_by_engine.get(engine) or global_model
thinking = inline_thinking or thinking_by_engine.get(engine) or global_thinking
if thinking and thinking not in THINKING_LEVELS_BY_ENGINE[engine]:
valid = ", ".join(sorted(THINKING_LEVELS_BY_ENGINE[engine])) or "none"
raise SystemExit(f"invalid thinking level for {engine}: {thinking} (valid: {valid})")
clone = copy.copy(args)
clone.engine = engine
clone.model = model
clone.thinking = thinking
result.append(clone)
return result
def reviewer_label(args: argparse.Namespace) -> str:
parts = [args.engine]
if args.model:
parts.append(f"model={args.model}")
if args.thinking:
parts.append(f"thinking={args.thinking}")
return " ".join(parts)
def run_reviewer(args: argparse.Namespace, repo: Path, prompt: str, changed_paths: set[str], required: list[str]) -> dict[str, Any]:
raw = run_engine(args, repo, prompt)
report = extract_json(raw)
validate_report(report, repo, changed_paths, required)
return report
def merge_panel_reports(reports: list[tuple[str, dict[str, Any]]]) -> dict[str, Any]:
findings: list[dict[str, Any]] = []
seen: set[tuple[str, int, str, str]] = set()
for label, report in reports:
for finding in report["findings"]:
location = finding["code_location"]
key = (
location["file_path"],
location["line"],
finding["category"],
" ".join(finding["title"].lower().split()),
)
if key in seen:
continue
seen.add(key)
merged = copy.deepcopy(finding)
merged["body"] = bounded_field(f"Reviewer: {label}\n\n{merged['body']}", 2000)
findings.append(merged)
incorrect = bool(findings) or any(report["overall_correctness"] == "patch is incorrect" for _, report in reports)
summary = ", ".join(f"{label}: {len(report['findings'])} finding(s)" for label, report in reports)
return {
"findings": findings,
"overall_correctness": "patch is incorrect" if incorrect else "patch is correct",
"overall_explanation": f"Panel review complete. {summary}.",
"overall_confidence": max((report["overall_confidence"] for _, report in reports), default=0.5),
}
def run_panel(args: argparse.Namespace, reviewers: list[argparse.Namespace], repo: Path, prompt: str, changed_paths: set[str]) -> dict[str, Any]:
reports: list[tuple[str, dict[str, Any]]] = []
failures: list[str] = []
with concurrent.futures.ThreadPoolExecutor(max_workers=len(reviewers)) as executor:
future_by_label = {
executor.submit(run_reviewer, reviewer, repo, prompt, changed_paths, []): reviewer_label(reviewer)
for reviewer in reviewers
}
for future in concurrent.futures.as_completed(future_by_label):
label = future_by_label[future]
try:
reports.append((label, future.result()))
except SystemExit as exc:
failures.append(f"{label}: {exc}")
except Exception as exc:
failures.append(f"{label}: {exc}")
if failures and not args.allow_partial_panel:
raise SystemExit("autoreview panel failed\n" + "\n".join(failures))
if failures:
for failure in failures:
print(f"panel reviewer failed: {failure}")
if not reports:
raise SystemExit("autoreview panel produced no reports")
reports.sort(key=lambda item: item[0])
report = merge_panel_reports(reports)
validate_report(report, repo, changed_paths, args.require_finding)
return report
def main() -> int:
args = parse_args()
reviewers = reviewer_args(args)
repo = repo_root()
target, target_ref = choose_target(repo, args.mode, args.base)
print(f"autoreview target: {target}")
print(f"branch: {current_branch(repo)}")
if len(reviewers) == 1 and not args.reviewers and not args.panel:
print(f"engine: {reviewers[0].engine}")
if reviewers[0].model:
print(f"model: {reviewers[0].model}")
if reviewers[0].thinking:
print(f"thinking: {reviewers[0].thinking}")
else:
print(f"reviewers: {', '.join(reviewer_label(reviewer) for reviewer in reviewers)}")
print(f"tools: {'on' if args.tools else 'off'}")
print(f"web_search: {'on' if args.web_search else 'off'}")
display_ref = args.commit if target == "commit" else target_ref
if display_ref:
print(f"ref: {display_ref}")
if args.dry_run:
return 0
if target == "local":
bundle = local_bundle(repo)
elif target == "branch":
assert target_ref
bundle = branch_bundle(repo, target_ref)
else:
bundle = commit_bundle(repo, args.commit)
target_ref = args.commit
prompt = build_prompt(repo, target, target_ref, bundle, load_extra_prompt(args), load_datasets(args))
changed_paths = review_paths(repo, target, target_ref, args.commit)
print(f"bundle: {len(prompt)} chars")
tests_proc: tuple[subprocess.Popen, float] | None = None
if args.parallel_tests:
tests_proc = start_parallel_tests(args.parallel_tests, repo)
try:
if len(reviewers) == 1:
report = run_reviewer(reviewers[0], repo, prompt, changed_paths, args.require_finding)
label = "autoreview"
else:
report = run_panel(args, reviewers, repo, prompt, changed_paths)
label = "autoreview panel"
if args.json_output:
Path(args.json_output).write_text(json.dumps(report, indent=2) + "\n")
if args.output:
original_stdout = sys.stdout
with Path(args.output).open("w") as handle:
sys.stdout = Tee(original_stdout, handle)
print_report(report, label=label)
sys.stdout = original_stdout
else:
print_report(report, label=label)
finally:
tests_status = finish_parallel_tests(*tests_proc) if tests_proc else 0
has_findings = bool(report["findings"])
overall_incorrect = report["overall_correctness"] == "patch is incorrect"
if tests_status != 0:
return 1
if args.expect_findings:
return 0 if has_findings else 1
return 1 if has_findings or overall_incorrect else 0
class Tee:
def __init__(self, *streams: Any) -> None:
self.streams = streams
def write(self, data: str) -> None:
for stream in self.streams:
stream.write(data)
def flush(self) -> None:
for stream in self.streams:
stream.flush()
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,176 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: test-review-harness [--fixture malicious|benign] [--engine codex|claude|droid|copilot]...
Creates a temporary git repo with either a deliberately unsafe patch or a
security-sensitive-but-safe patch, then verifies each selected engine through
autoreview.
Default engines: codex, claude.
EOF
}
engines=()
fixture=malicious
while [[ $# -gt 0 ]]; do
case "$1" in
--fixture)
fixture=${2:-}
shift 2
;;
--engine)
engines+=("${2:-}")
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
usage >&2
exit 2
;;
esac
done
case "$fixture" in
malicious|benign) ;;
*)
usage >&2
exit 2
;;
esac
if [[ ${#engines[@]} -eq 0 ]]; then
engines=(codex claude)
fi
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
repo=$(mktemp -d "${TMPDIR:-/tmp}/autoreview-fixture.XXXXXX")
trap 'rm -rf "$repo"' EXIT
cd "$repo"
git init --quiet
git config user.name "Review Fixture"
git config user.email "review-fixture@example.com"
if [[ "$fixture" == malicious ]]; then
cat > app.js <<'EOF'
export function uploadPath(name) {
return `uploads/${name.replaceAll("/", "")}`;
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
EOF
else
cat > app.js <<'EOF'
import path from "node:path";
const uploadsRoot = path.resolve("uploads");
function safeChildPath(root, name) {
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
throw new Error("invalid child name");
}
const resolved = path.resolve(root, name);
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
return resolved;
}
export function uploadPath(name) {
return safeChildPath(uploadsRoot, name);
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
EOF
fi
git add app.js
git commit --quiet -m "initial safe version"
if [[ "$fixture" == malicious ]]; then
cat > app.js <<'EOF'
import { execSync } from "node:child_process";
export function uploadPath(name) {
return `uploads/${name}`;
}
export function deleteUpload(name) {
return execSync(`rm -rf uploads/${name}`);
}
export function publicUser(user) {
return { id: user.id, name: user.name, password: user.password };
}
EOF
else
cat > app.js <<'EOF'
import { execFile } from "node:child_process";
import path from "node:path";
import { promisify } from "node:util";
const uploadsRoot = path.resolve("uploads");
const reposRoot = path.resolve("repos");
const execFileAsync = promisify(execFile);
function safeChildPath(root, name) {
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
throw new Error("invalid child name");
}
const resolved = path.resolve(root, name);
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
return resolved;
}
function repoChildPath(root, name) {
if (!/^[A-Za-z0-9._-]+$/.test(name)) throw new Error("invalid repo name");
return safeChildPath(root, name);
}
export function uploadPath(name) {
return safeChildPath(uploadsRoot, name);
}
export async function repoStatus(repoName) {
const { stdout } = await execFileAsync("git", ["status", "--short"], {
cwd: repoChildPath(reposRoot, repoName),
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
});
return stdout;
}
export function publicUser(user) {
return { id: user.id, name: user.name };
}
export function accountSettingsForOwner(user, requesterId) {
if (String(requesterId) !== String(user.id)) throw new Error("forbidden");
return { id: user.id, hasPassword: Boolean(user.passwordHash) };
}
EOF
fi
for engine in "${engines[@]}"; do
echo "== $engine =="
if [[ "$fixture" == malicious ]]; then
"$script_dir/autoreview" \
--mode local \
--engine "$engine" \
--prompt "This is an acceptance test fixture. The changed app.js patch contains real security bugs. Review normally and report only actionable defects from the patch." \
--require-finding "command" \
--expect-findings
else
"$script_dir/autoreview" \
--mode local \
--engine "$engine" \
--prompt "Security calibration fixture: this patch intentionally uses filesystem paths, async execFile, and owner-gated password-adjacent state safely. Do not flag legitimate shell/filesystem/auth-adjacent functionality unless there is a concrete exploitable risk in the diff."
fi
done

View File

@@ -0,0 +1,44 @@
---
name: channel-message-flows
description: "Use when previewing local channel message flow fixtures."
---
# Channel Message Flows
Use this from the OpenClaw repo root to send canned channel preview flows while iterating on message UX. These are real sends/edits/deletes against the configured channel target.
## Telegram
Native Telegram `sendMessageDraft` tool progress, then a final answer:
```bash
node --import tsx scripts/dev/channel-message-flows.ts \
--channel telegram \
--target <telegram-chat-id> \
--flow working-final \
--duration-ms 20000
```
Thinking preview, then a final answer:
```bash
node --import tsx scripts/dev/channel-message-flows.ts \
--channel telegram \
--target <telegram-chat-id> \
--flow thinking-final
```
## Options
- `--account <accountId>`: Telegram account id when not using the default.
- `--thread-id <id>`: Telegram forum topic/message thread id.
- `--delay-ms <ms>`: Override preview update cadence.
- `--duration-ms <ms>`: Simulated working duration for `working-final`.
- `--final-text <text>`: Override the durable final message.
## Notes
- `--target` is the numeric Telegram chat id.
- `working-final` exercises native Telegram `sendMessageDraft` with static `Working` status and sample tool progress.
- `thinking-final` exercises formatted `Thinking` reasoning preview clearing before the final answer.
- Only `--channel telegram` is implemented for now.

View File

@@ -1,103 +0,0 @@
---
name: codex-review
description: "Codex code review closeout: local dirty changes, PR branch vs main, parallel tests."
---
# Codex Review
Run Codex's built-in code review as a closeout check. This is code review (`codex review`), not Guardian `auto_review` approval routing.
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 Codex review returns no accepted/actionable findings.
- If a review-triggered fix changes code, rerun focused tests and rerun Codex review.
- 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
```
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
```
## Parallel Closeout
Format first if formatting can change line locations. Then it is OK to run tests and review in parallel:
```bash
scripts/codex-review --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.
## 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
~/.codex/skills/codex-review/scripts/codex-review --help
```
If installed from `agent-scripts`, path is:
```bash
/Users/steipete/Projects/agent-scripts/skills/codex-review/scripts/codex-review --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
- writes only to stdout unless `--output` or `CODEX_REVIEW_OUTPUT` is set
- supports `--dry-run` and `--parallel-tests`
## Final Report
Include:
- review command used
- tests/proof run
- findings accepted/rejected, briefly why
- final clean review command, or why a remaining finding was consciously rejected

View File

@@ -1,188 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: codex-review [options]
Options:
--mode auto|local|branch Target selection. Default: auto.
--base REF Base ref for branch review. Default: PR base or origin/main.
--codex-bin PATH Codex binary. Default: codex.
--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>
auto dirty tree -> local, else PR/current branch -> branch
EOF
}
mode=auto
base_ref=
codex_bin=${CODEX_BIN:-codex}
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
;;
--codex-bin)
codex_bin=${2:-}
shift 2
;;
--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 "$mode" in
auto|local|branch) ;;
*)
echo "invalid --mode: $mode" >&2
exit 2
;;
esac
git rev-parse --show-toplevel >/dev/null
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" == 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" review --uncommitted)
else
review_cmd=("$codex_bin" review --base "$base_ref")
fi
printf 'codex-review target: %s\n' "$review_kind"
printf 'branch: %s\n' "${current_branch:-detached}"
if [[ -n "$pr_url" ]]; then
printf 'pr: %s\n' "$pr_url"
fi
printf 'review:'
printf ' %q' "${review_cmd[@]}"
printf '\n'
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
run_review() {
if [[ -n "$output" ]]; then
mkdir -p "$(dirname "$output")"
"${review_cmd[@]}" 2>&1 | tee "$output"
else
"${review_cmd[@]}"
fi
}
if [[ -z "$parallel_tests" ]]; then
run_review
exit $?
fi
review_status_file=$(mktemp)
tests_status_file=$(mktemp)
(
set +e
run_review
status=$?
printf '%s\n' "$status" > "$review_status_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")
tests_status=$(cat "$tests_status_file")
rm -f "$review_status_file" "$tests_status_file"
printf 'codex-review exit: %s\n' "$review_status"
printf 'tests exit: %s\n' "$tests_status"
if [[ "$review_status" != 0 || "$tests_status" != 0 ]]; then
exit 1
fi

View File

@@ -0,0 +1,74 @@
---
name: control-ui-e2e
description: Use when testing, fixing, or extending the OpenClaw Control UI GUI with Vitest + Playwright end-to-end checks, mocked Gateway WebSocket flows, mocked dashboard runs, screenshots/videos, or agent-verifiable browser proof.
---
# Control UI E2E
Use this for Control UI changes that need a real browser flow with deterministic Gateway data.
## Test Shape
- Use `ui/src/**/*.e2e.test.ts` for full GUI flows.
- Use `ui/src/test-helpers/control-ui-e2e.ts` to start the Vite Control UI and install a mocked Gateway WebSocket.
- Keep scenarios deterministic. Do not use live provider keys, real channel credentials, or a real Gateway unless the user explicitly asks for live proof.
- Prefer existing `.browser.test.ts` or unit tests for narrow rendering logic; use this E2E lane when the proof should cover routing, app boot, Gateway handshake, requests, and visible UI behavior together.
## Commands
- Target one E2E test in a Codex worktree:
```bash
node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/chat-flow.e2e.test.ts
```
- Run the whole local lane in a normal checkout:
```bash
pnpm test:ui:e2e
```
If dependencies are missing in a Codex worktree, install once with `pnpm install`; for broad GUI proof or dependency-heavy checks, use Testbox/Crabbox instead of running a wide local pnpm lane.
## Visual Proof Default
When running mocked Control UI/dashboard validation for a user-facing feature, produce visual proof by default unless the user explicitly opts out.
- Keep the Vitest E2E assertions deterministic; do not commit generated screenshots or videos.
- After or alongside the focused E2E test, run the mocked Control UI app when available, for example `pnpm dev:ui:mock -- --port <port>`.
- Drive Chromium with Playwright against the local mock URL and capture a video plus screenshots for each meaningful state: initial view, interaction input, result state, and final/paginated/selected state.
- Use `browser.newContext({ recordVideo: { dir, size }, viewport })`, `page.screenshot({ path })`, and close the context before reporting the video path.
- Put artifacts under `.artifacts/control-ui-e2e/<short-feature-name>/` or another clearly named local temp directory, and report the absolute paths in the final answer.
- Treat recording as validation, not only demo capture. If the recorder fails or shows surprising behavior, stop, fix the behavior, add or update a regression test, then rerecord.
- If visual proof is blocked, state the exact blocker and still report the textual E2E evidence.
## Mock Pattern
Start the app server, install the mock before `page.goto`, then assert both Gateway traffic and visible UI:
```ts
const server = await startControlUiE2eServer();
const page = await context.newPage();
const gateway = await installMockGateway(page, {
historyMessages: [{ role: "assistant", content: [{ type: "text", text: "Ready." }] }],
});
await page.goto(`${server.baseUrl}chat`);
await page.locator(".agent-chat__composer-combobox textarea").fill("hello");
await page.getByRole("button", { name: "Send message" }).click();
const request = await gateway.waitForRequest("chat.send");
await gateway.emitChatFinal({ runId: String(request.params.idempotencyKey), text: "Done." });
await page.getByText("Done.").waitFor();
```
Extend `installMockGateway` with typed scenario options or method responses when a new flow needs more Gateway surface.
## Standalone Recording
When recording an already-running mocked Control UI URL, use a temporary Playwright script or `playwright test` spec and keep the recording flow focused:
- Open the mock URL, interact through stable `data-*` selectors or user-facing role selectors, and wait on asserted states instead of relying on fixed sleeps.
- Assert both visible UI state and mocked Gateway traffic for request-driven flows. For example, verify the expected count/row is visible and that `sessions.list` was called with the expected `search`, `offset`, and `limit`.
- Use short sleeps only after assertions to make the captured video readable.
- Store the generated video under `.artifacts/control-ui-e2e/<feature>/`; do not commit it.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Control UI E2E"
short_description: "Mocked browser E2E for Control UI"
default_prompt: "Use $control-ui-e2e to verify a Control UI change with the mocked Vitest + Playwright browser lane."

View File

@@ -1,23 +1,32 @@
---
name: crabbox
description: Use Crabbox for OpenClaw remote validation across Linux, macOS, Windows, and WSL2. Default to the repo Crabbox config, use brokered AWS for normal broad proof, and keep Blacksmith Testbox as an explicit opt-in or outage diagnostic path.
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: the repo `.crabbox.yaml`, currently brokered AWS. Do not
override it to Blacksmith unless the user explicitly asks for Blacksmith proof,
the task is specifically about Testbox behavior, or AWS/brokered Crabbox is the
broken layer.
Crabbox is the transport/orchestration surface. The actual backend can be:
Blacksmith Testbox is a delegated fallback, not the default router. If a
Blacksmith run queues, fails capacity, fails auth, or cannot allocate, stop
after one real attempt and switch to the repo default or report the blocker.
Do not retry Blacksmith in a loop.
- 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
@@ -34,10 +43,19 @@ 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 and honor them. For normal Linux
validation, omit `--provider` so the wrapper uses brokered AWS.
- Pass `--provider blacksmith-testbox` only for explicit Blacksmith/Testbox
work or a deliberate comparison.
- Check `.crabbox.yaml` for direct-provider defaults. Omitting `--provider`
means brokered AWS today.
- The brokered AWS default is a Linux developer image in `eu-west-1`; the repo
config pins hot `eu-west-1a/b/c` placement so Fast Snapshot Restore can apply.
If warmup drifts well past the minute-scale path, verify image promotion,
region/AZ placement, and FSR state before blaming OpenClaw.
- 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
@@ -64,6 +82,41 @@ Use these only when the task needs an existing non-Linux host. OpenClaw broad
Linux validation uses the repo Crabbox config unless a provider is explicitly
requested.
Native brokered Windows is available for Windows-specific proof. Use the AWS
developer image in `us-west-2` on demand; it has the expected OpenClaw developer
toolchain and Docker image cache. Keep broad Linux gates on Linux/Testbox unless
the bug is Windows-specific:
```sh
../crabbox/bin/crabbox warmup \
--provider aws \
--target windows \
--windows-mode normal \
--region us-west-2 \
--market on-demand \
--timing-json
```
The hydrate workflow assumes Docker should already be baked into Linux images
and only installs it as a fallback. Do not add per-run Docker installs to proof
commands unless the image probe shows Docker is actually missing.
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:
```sh
@@ -83,11 +136,10 @@ Crabbox supports static SSH targets:
with `../crabbox/bin/crabbox run --help`, config/flag tests, and the Crabbox
Go test suite.
## Default Brokered AWS 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:
@@ -124,9 +176,9 @@ pnpm crabbox:run -- \
Read the JSON summary. Useful fields:
- `provider`: should normally be `aws`
- `provider`: `aws`
- `leaseId`: `cbx_...`
- `syncDelegated`: should normally be `false`
- `syncDelegated`: `false`
- `commandPhases`: populated when the command prints `CRABBOX_PHASE:<name>`
- `commandMs` / `totalMs`
- `exitCode`
@@ -138,6 +190,41 @@ cleanup when a run fails, is interrupted, or the command output is unclear:
../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
Use these on debugging runs before inventing ad hoc logging:
@@ -223,6 +310,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
@@ -244,8 +338,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
@@ -353,18 +448,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"
@@ -377,6 +472,32 @@ 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

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

@@ -0,0 +1,64 @@
---
name: openclaw-docker-e2e-authoring
description: "Author OpenClaw Docker E2E and live provider Docker lanes."
---
# OpenClaw Docker E2E Authoring
Use this when adding or changing Docker E2E lanes, release-path Docker tests,
or live-provider Docker proof.
## Lane Choice
- Deterministic Docker: fake the dependency/server and assert the exact runtime
contract crossing the boundary.
- Live Docker: use real provider credentials/model only when user-visible
behavior needs the real service.
- Prefer both when they prove different risks: deterministic for byte/payload
routing, live for actual provider behavior.
## Authoring Rules
- Test-only helpers live in `test/helpers` or `scripts/e2e/lib/<lane>/`, not
`src/**`, unless production imports them.
- Package-installed app runs from `/app`; mount only explicit harness/helper
paths read-only.
- Fake servers should log boundary requests as JSONL and clients should assert
the real dependency payload, not just process success.
- Add the package script and `scripts/lib/docker-e2e-scenarios.mjs` lane in the
same change.
- If a lane installs a plugin from npm, default the spec via env so published
and local override paths are both testable.
## Media And Vision
- Expected answer must exist only in pixels or provider output being tested.
- Use neutral filenames, neutral prompts, and no metadata leaks.
- Random bitmap/OCR tokens reuse the repo OCR-safe alphabet `24567ACEF` unless
the test owns a stronger glyph set.
- Make the expected answer unique per run when proving real image
understanding.
## `chat.send` E2E
- Require `chat.send` to return `status: "started"` and a string `runId`.
- Wait for completion with `agent.wait`.
- Assert final user-visible text via `chat.history` when event ordering is not
the behavior under test.
- Keep originating channel/account metadata only when the bug path needs queued
inbound/channel context.
## Verification
Run the smallest proof that covers the touched lane:
```bash
pnpm exec oxfmt --write <changed files>
node --check <new .mjs files>
bash -n <new .sh files>
node scripts/run-vitest.mjs test/scripts/docker-e2e-plan.test.ts
OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:<lane>
```
For real-provider lanes, run the matching live Docker script after deterministic
Docker is green. Finish with `$autoreview` before commit/PR.

View File

@@ -0,0 +1,165 @@
---
name: openclaw-landable-bug-sweep
description: "Find or repair small high-confidence non-SDK-boundary OpenClaw bugfix PRs until five are landable."
---
# OpenClaw Landable Bug Sweep
Autonomous maintainer workflow for producing five landable OpenClaw bugfix PR URLs.
Use for broad issue/PR sweeps where the bar is high and the output is PRs, not notes.
Do not use for plugin SDK/API boundary work; those need separate architecture review.
## Target
Return exactly five PR URLs, each with:
- bug summary
- why the fix is low-risk
- proof: rebased-head local/Testbox/live commands or run IDs
- autoreview: clean result on the exact head being shown
- CI green on the exact pushed PR head
- issue/duplicate cleanup done or still pending
The five URLs may be existing PRs that were reviewed/fixed, or new PRs created from issues/clusters.
Do not present a PR URL to the maintainer until it has been refreshed on current `main`, left-tested, autoreviewed clean, pushed, and verified green in live GitHub CI.
If code, tests, changelog, PR body, or branch base changes after autoreview, rerun autoreview before showing the URL.
## Companion Skills
Use `$gitcrawl` for discovery/clustering, `$openclaw-pr-maintainer` for live GitHub mutation rules, `$github-author-context` when contributor trust matters, `$openclaw-testing` for proof choice, `$autoreview` before publishing/landing, and `$crabbox` for broad/E2E/live proof.
## Candidate Bar
Accept only when all are true:
- bug or paper cut, not feature/product/support/docs-only
- root cause is proven in current code
- dependency behavior checked via upstream docs/source/types when relevant
- production/runtime diff is small, ideally much smaller than 500 LOC and always below 500 LOC
- tests may be larger, but focused
- no new dependency
- no new config option
- no backward-incompatible behavior
- no security/product/owner-boundary decision needed
- no plugin SDK, public plugin API, or `src/plugin-sdk/**` boundary change
- no broad refactor smell
- focused proof is feasible
- branch can be rebased/refreshed and pushed, or a replacement PR can be created
Good examples:
- provider parameter mismatch proven against dependency/API contract
- CLI command diverges from adjacent command behavior
- narrow runtime state/serialization bug with failing test
- issue already fixed on current `main`, with proof and closeable duplicates
Reject:
- feature requests, new knobs, migrations, release work, workflow policy, support
- plugin SDK/API boundary changes, including compatibility shims, new SDK methods, SDK exports, or plugin-facing channel/provider seams
- auth/security boundary changes unless explicitly assigned
- bugs needing live credentials that are unavailable
- PRs with red CI unless you fix, rebase, push, and recheck them green
- PRs you only reviewed locally but did not refresh/push/check live
- PRs whose final head has not passed `$autoreview`
- fixes whose clean shape is a larger architecture move
- speculative reports without reproducible/provable cause
- UI/UX changes requiring product judgment
## Sweep Loop
1. Start clean:
- `git status -sb`
- `git pull --ff-only`
- verify branch is expected, usually `main`
2. Build candidate clusters:
- `gitcrawl` open issues/PRs, neighbors, and search
- live `gh issue/pr view`
- include PRs linked from issues and duplicates
3. For each cluster:
- read issue/PR body, comments, labels, linked refs, current source, adjacent tests
- suppress maintainer-owned queue noise unless it is the best fix path
- identify opener/author and preserve credit
- decide: `repair-existing-pr`, `create-new-pr`, `close-fixed-on-main`, `close-duplicate`, or `reject`
4. Prove before patching:
- failing test, focused repro, log/source proof, or dependency contract proof
- if already fixed on `main`, prove with current source/test/commit and close kindly
5. Patch:
- prefer existing PR when good and writable
- if unwritable or wrong shape, create own PR and preserve useful contributor credit
- if no PR exists, create one
- add regression test when it fits
- changelog for user-facing fixes; thank credited human reporter/contributor
6. Review, refresh, and publish:
- rebase or otherwise refresh the PR branch on current `origin/main`
- resolve drift, including newly exposed CI failures, rather than counting the PR as ready
- changelog-only conflicts are routine on busy `main`; resolve them mechanically when already refreshing, but do not treat them as a real code conflict, a reason to reject the PR, or evidence that the branch needs extra fixup beyond the changelog entry order
- left-test the rebased head with the smallest meaningful local/Testbox/live command that proves the bug
- run `$autoreview` until no accepted/actionable findings remain before creating, updating, or presenting the PR URL
- create/update PR with real body and proof fields
- push the exact reviewed head
- verify live GitHub CI is green for that pushed head; do not count pending, red, dirty, conflicting, or externally blocked PRs in the five
7. Hygiene:
- close duplicates and fixed-on-main issues/PRs with proof as soon as you notice them during the sweep
- never mutate more than five associated items in one cluster without explicit confirmation
- comments must be kind, concrete, and include proof/PR/commit links
8. Repeat until five landable PR URLs are ready.
## PR Body Proof
Use the repo PR template. Include these exact labels:
```text
Behavior addressed:
Real environment tested:
Exact steps or command run after this patch:
Evidence after fix:
Observed result after fix:
What was not tested:
```
## Existing PR Rules
- Review code path beyond the diff before trusting it.
- If PR is good: rebase/refresh on current `main`, fix small issues, left-test, autoreview clean, push, and get CI green before showing or counting it.
- If PR is not good but has a useful idea: recreate locally, co-author when warranted, close original with thanks and explanation.
- If PR is duplicate or fixed on `main`: comment proof, close.
- If maintainer cannot push to contributor branch: create own branch/PR, preserve useful commits or credit.
- If CI turns red after local proof, treat that as normal work: inspect the failing job, fix or reject, rerun, and only count the PR once green.
## Output Ledger
Maintain a running ledger:
```text
accepted:
- PR URL:
source refs:
bug:
root cause:
fix:
risk:
rebase/head:
left-test:
autoreview:
CI:
credit/thanks:
cleanup:
rejected:
- ref:
reason:
closed:
- ref:
reason:
proof/comment:
```
Final answer:
- exactly five accepted PR URLs
- 2-4 sentence explainer per PR
- proof/CI state per PR
- closed duplicates/fixed-on-main refs
- current branch/status

View File

@@ -0,0 +1,4 @@
interface:
display_name: "OpenClaw Landable Bug Sweep"
short_description: "Find five small non-SDK landable bugfix PRs"
default_prompt: "Use $openclaw-landable-bug-sweep to find or repair five small high-confidence non-SDK-boundary OpenClaw bugfix PRs and get them landable."

View File

@@ -0,0 +1,95 @@
---
name: openclaw-mac-release
description: "Run or recover OpenClaw macOS release signing, notarization, appcast, and asset promotion."
---
# OpenClaw Mac Release
Use with `$openclaw-release-maintainer`, `$openclaw-release-ci`, and `$one-password` when stable macOS assets, private mac preflight, notarization, appcast promotion, or mac release recovery is involved.
## Credentials
- Canonical ASC item: vault `Molty`, title `API Key - App Store Connect - Personal - Release`.
- Fields: `private_key_p8`, `key_id`, `issuer_id`.
- Current known good key id: `AKVLXW849T`.
- Legacy mirror: vault `Private`, title `API Key - App Store Connect - Personal`; keep it synced for older refs.
- Stale/revoked key symptom: `xcrun notarytool submit` fails with `HTTP status code: 401. Unauthenticated`.
- Validate candidate ASC credentials with `xcrun notarytool history` before setting GitHub secrets.
## 1Password
- Use `$one-password`: all `op` work inside one persistent tmux session, no secret output.
- Prefer `OP_SERVICE_ACCOUNT_TOKEN` from `~/.profile` for Molty reads.
- Do not assume `MOLTY_OP_SERVICE_ACCOUNT_TOKEN` is alive; it has previously pointed at a deleted service account.
- If a service token fails, run status-only checks: token present/length and `op whoami`; never print token values.
- If desktop app auth is needed but Touch ID is unavailable, set `OP_BIOMETRIC_UNLOCK_ENABLED=false` for the manual `op account add --signin` path.
## GitHub Secrets
Target private repo environment: `openclaw/releases-private`, env `mac-release`.
Set only after local notary auth validation:
- `APP_STORE_CONNECT_API_KEY_P8`
- `APP_STORE_CONNECT_KEY_ID`
- `APP_STORE_CONNECT_ISSUER_ID`
Do not update these from mixed sources. All three ASC fields must come from the same 1Password item.
## Workflow Shape
- Public release branch may carry mac-only packaging fixes after the stable tag/npm are already live.
- Use `source_ref=release/YYYY.M.D` for private mac preflight/validation when building that branch variation.
- Keep `tag=vYYYY.M.D` pointing at the original stable release commit.
- Real mac publish must reuse:
- a successful private mac preflight run for the same tag/source SHA
- a successful private mac validation run for the same tag/source SHA
- If preflight source SHA differs from tag SHA, validation must also use the same `source_ref`; promotion rejects mismatched proof.
## Notarization
- OpenClaw uses `scripts/notarize-mac-artifact.sh`.
- `xcrun notarytool submit` should use `--no-s3-acceleration`; accelerated upload can surface misleading 401s even when `notarytool history` succeeds.
- If signing succeeds but notarization fails immediately with 401, check ASC key freshness first.
- If notarization stays in progress for several minutes after key-file write, that is normal Apple wait time; do not edit blindly.
## Dispatch
Private preflight:
```bash
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --ref main \
-f tag=vYYYY.M.D \
-f source_ref=release/YYYY.M.D \
-f preflight_only=true \
-f smoke_test_only=false \
-f allow_late_calver_recovery=false \
-f public_release_branch=release/YYYY.M.D
```
Private validation for a branch-variation preflight:
```bash
gh workflow run openclaw-macos-validate.yml --repo openclaw/releases-private --ref main \
-f tag=vYYYY.M.D \
-f source_ref=release/YYYY.M.D
```
Real publish:
```bash
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --ref main \
-f tag=vYYYY.M.D \
-f preflight_only=false \
-f smoke_test_only=false \
-f preflight_run_id=<successful-preflight-run> \
-f validate_run_id=<successful-validation-run> \
-f allow_late_calver_recovery=false \
-f public_release_branch=release/YYYY.M.D
```
## Verify
- `gh release view vYYYY.M.D --repo openclaw/openclaw` shows zip, dmg, dSYM zip, not draft, not prerelease.
- Public `main` `appcast.xml` points at `OpenClaw-YYYY.M.D.zip`.
- Appcast entry has `sparkle:version`, `sparkle:shortVersionString`, length, and `sparkle:edSignature`.

View File

@@ -58,7 +58,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- For beta/stable verification, resolve the tag immediately before the run (`npm view openclaw@beta version dist.tarball` or `npm view openclaw@latest ...`). Tags can move while a long VM matrix is already running; restart the matrix when the intended prerelease appears after an earlier registry 404/tag-lag check.
- Use the configured secret workflow to inject only the provider keys needed by OpenAI/Anthropic lanes. Do not print secrets or env dumps; pass provider secrets through the guest exec environment.
- Same-guest update verification should set the default model explicitly to `openai/gpt-5.4` before the agent turn and use a fresh explicit `--session-id` so old session model state does not leak into the check.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. Treat any Ubuntu guest with major version `>= 24` as acceptable when the exact default VM is missing, preferring the closest version match. On Peter's current host today, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. Treat any Ubuntu guest with major version `>= 24` as acceptable when the exact default VM is missing, preferring the newest versioned Ubuntu guest with a fresh poweroff snapshot. On Peter's current host today, use `Ubuntu 26.04`.
- On macOS same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; launchd can otherwise report a loaded service while the old process has exited and the fresh process is not RPC-ready yet.
- The npm-update aggregate's macOS update leg writes the guest update script as root, then runs it as the desktop user. If `prlctl exec "$MACOS_VM" --current-user ...` cannot authenticate, retry through plain root `prlctl exec` plus `sudo -u <desktop-user> /usr/bin/env HOME=/Users/<desktop-user> USER=<desktop-user> LOGNAME=<desktop-user> PATH=/opt/homebrew/bin:/opt/homebrew/opt/node/bin:/usr/bin:/bin:/usr/sbin:/sbin ...`. That is a Parallels transport fallback; still verify `openclaw --version`, gateway RPC, and an agent turn after the update.
- On Windows same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; in-place global npm updates can otherwise leave stale hashed `dist/*` module imports alive in the running service.
@@ -93,8 +93,8 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- If that release-to-dev lane fails with `reason=preflight-no-good-commit` and repeated `sh: pnpm: command not found` tails from `preflight build`, treat it as an updater regression first. The fix belongs in the git/dev updater bootstrap path, not in Parallels retry logic.
- Until the public stable train includes that updater bootstrap fix, the macOS release-to-dev lane may seed a temporary guest-local `pnpm` shim immediately before `openclaw update --channel dev`. Keep that workaround scoped to the smoke harness and remove it once the latest stable no longer needs it.
- In Tahoe `prlctl exec --current-user` runs, prefer explicit `node .../openclaw.mjs ...` invocations for the release->dev handoff itself and for post-update verification. The shebanged global `openclaw` wrapper can fail with `env: node: No such file or directory`, and self-updating through the wrapper is a weaker lane than invoking the entrypoint under a fixed `node`.
- Default to the snapshot closest to `macOS 26.3.1 latest`.
- On Peter's Tahoe VM, `fresh-latest-march-2026` can hang in `prlctl snapshot-switch`; if restore times out there, rerun with `--snapshot-hint 'macOS 26.3.1 latest'` before blaming auth or the harness.
- Default to the snapshot closest to `macOS 26.5 latest`.
- On Peter's Tahoe VM, `fresh-latest-march-2026` can hang in `prlctl snapshot-switch`; if restore times out there, rerun with `--snapshot-hint 'macOS 26.5 latest'` before blaming auth or the harness.
- `parallels-macos-smoke.sh` now retries `snapshot-switch` once after force-stopping a stuck running/suspended guest. If Tahoe still times out after that recovery path, then treat it as a real Parallels/host issue and rerun manually.
- The macOS smoke should include a dashboard load phase after gateway health: resolve the tokenized URL with `openclaw dashboard --no-open`, verify the served HTML contains the Control UI title/root shell, then open Safari and require an established localhost TCP connection from Safari to the gateway port.
- For Tahoe `fresh.gateway-status`, prefer non-TTY `prlctl exec --current-user ... openclaw gateway status ...` plus a few short retries. `prlctl enter` can spam TTY control bytes and hang the phase log even when the CLI itself is healthy.
@@ -140,8 +140,8 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
## Linux flow
- Preferred entrypoint: `pnpm test:parallels:linux`
- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`.
- If that exact VM is missing on the host, any Ubuntu guest with major version `>= 24` is acceptable; prefer the closest versioned Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
- Use the newest versioned Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 26.04`.
- If an exact requested Ubuntu VM is missing on the host, any Ubuntu guest with major version `>= 24` is acceptable; prefer the newest versioned Ubuntu guest over older fallback snapshots.
- Use plain `prlctl exec`; `--current-user` is not the right transport on this snapshot.
- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`.
- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap.

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.
@@ -139,7 +169,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, 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.
- 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.
- Provenance must separate roles when they differ: blamed code author username, blamed PR merger/committer username, current PR author username, PR number, and date. Do not collapse them into one "introduced by" actor.
- For any confirmed bug, run `git blame` on the implicated line(s) after identifying the root cause. Report who broke it as the blamed PR merger/committer, and also name the blamed code author. Include the PR number. If no PR is traceable, use the blamed commit as the provenance: commit SHA, date, and author username. Do not guess a merger or frame missing PR metadata as a separate finding.
- 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.
@@ -162,7 +194,7 @@ 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. provenance for regressions when traceable by bounded git/PR history
3. blame-backed provenance for regressions when traceable, including blamed PR merger and date, or commit SHA/date when no PR is traceable
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.
@@ -217,6 +249,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

@@ -28,7 +28,10 @@ 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.
1Password service-account values are the first source for release provider
preflight. Inject those exact targeted keys first, then run the verifier; use
ambient env only when it was already intentionally injected for this release.
The script prints only provider status and HTTP class, never tokens.
## Dispatch

View File

@@ -170,6 +170,13 @@ live`; keep it clearly beta and avoid implying stable promotion.
CI, validation, or internal release mechanics unless the release is explicitly
about those. Peter prefers concrete user wins: features, integrations,
workflow improvements, and practical reliability fixes.
- Do not feature QA parity, test coverage, release gates, or validation lanes in
user-facing launch tweets. Keep them for release notes or maintainer proof
unless the operator explicitly asks for validation-focused copy.
- Do not feature plugin-author or developer tooling such as SDK helpers,
tool-plugin scaffolding, build/validate/init commands, or internal CLI
plumbing in general user-facing launch tweets unless the operator explicitly
asks for developer-focused copy.
- Tone: high-signal, slightly cheeky, confident, not corporate. One joke is
enough. Avoid punching down, insulting users, or promising what was not
verified.

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;
@@ -758,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]),
@@ -774,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

@@ -24,8 +24,11 @@ Prove the touched surface first. Do not reflexively run the whole suite.
- 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:
`node scripts/crabbox-wrapper.mjs run ... --shell -- "pnpm check:changed"`
and let `.crabbox.yaml` choose the provider
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.
@@ -46,13 +49,18 @@ Prove the touched surface first. Do not reflexively run the whole suite.
`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 Crabbox first and omit `--provider` unless a specific
provider is being tested. The repo Crabbox config routes normal broad proof to
brokered AWS. Blacksmith Testbox is explicit opt-in; if it queues, fails
capacity, or cannot allocate, retry once through the default Crabbox route or
report the Testbox blocker. Reuse only an id/slug created in this operator
session; `blacksmith testbox list` is diagnostics only, not a shared work
queue.
- 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
@@ -123,6 +131,8 @@ gh run view <run-id> --job <job-id> --log
- Check exact SHA. Ignore newer unrelated `main` unless asked.
- For cancelled same-branch runs, confirm whether a newer run superseded it.
- Fetch full logs only for failed or relevant jobs.
- Prefer `gh run view <run-id> --json jobs` over PR rollup while debugging; rollup can be stale/noisy.
- For `prompt:snapshots:check` failures, treat Linux Node 24 as CI truth. If macOS passes but CI drifts, reproduce in a Linux Node 24 container or Testbox, commit that generated output, then rerun.
## GitHub Release Workflows

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

@@ -17,7 +17,8 @@ artifact bundle. The runner leases the shared burner account from Convex.
Run from the OpenClaw repo and branch under test:
```bash
pnpm qa:telegram-user:crabbox -- start \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" start \
--tdlib-url http://artifacts.openclaw.ai/tdlib-v1.8.0-linux-x64.tgz \
--output-dir .artifacts/qa-e2e/telegram-user-crabbox/pr-review
```
@@ -39,7 +40,8 @@ For deterministic visual repros, put the exact mock-model reply in a file and
pass it to `start`:
```bash
pnpm qa:telegram-user:crabbox -- start \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" start \
--tdlib-url http://artifacts.openclaw.ai/tdlib-v1.8.0-linux-x64.tgz \
--mock-response-file .artifacts/qa-e2e/telegram-user-crabbox/reply.txt \
--output-dir .artifacts/qa-e2e/telegram-user-crabbox/pr-review
@@ -55,15 +57,16 @@ For visual proof, first send or identify a bottom marker message, then open the
group/topic directly by message id:
```bash
pnpm qa:telegram-user:crabbox -- view \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" view \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json \
--message-id <message-id>
```
This uses Telegram Desktop directly with `tg://privatepost`, not `xdg-open`.
It also resizes Telegram to `650x1000` at the tested desktop position so
Telegram switches to single-chat mode with no left chat list or right info
pane. Do not press Escape after this; Escape can close the selected chat.
the crop can isolate the chat pane even if Telegram keeps a split/sidebar
layout. Do not press Escape after this; Escape can close the selected chat.
Bottom behavior matters:
@@ -71,13 +74,14 @@ Bottom behavior matters:
later messages appear live in the recording
- deep-linking to an older message does not auto-scroll to new arrivals; link
again to the newest/final marker instead of clicking the down-arrow
- `650px` is the largest tested clean width; `660px` switches Telegram back to
split/sidebar layout
- the cropped GIF intentionally uses the chat pane, not the whole desktop or
whole Telegram window
Send as the real Telegram user:
```bash
pnpm qa:telegram-user:crabbox -- send \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" send \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json \
--text /status
```
@@ -87,7 +91,8 @@ For slash commands, omit the bot username; the runner targets the SUT bot.
Run arbitrary commands on the Crabbox:
```bash
pnpm qa:telegram-user:crabbox -- run \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" run \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json \
-- bash -lc 'source /tmp/openclaw-telegram-user-crabbox/env.sh && python3 /tmp/openclaw-telegram-user-crabbox/user-driver.py transcript --limit 20 --json'
```
@@ -106,14 +111,16 @@ python3 /tmp/openclaw-telegram-user-crabbox/user-driver.py probe --text '@{sut}
Capture the current desktop without ending the session:
```bash
pnpm qa:telegram-user:crabbox -- screenshot \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" screenshot \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json
```
Check lease state and get the WebVNC command:
```bash
pnpm qa:telegram-user:crabbox -- status \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" status \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json
```
@@ -122,7 +129,8 @@ pnpm qa:telegram-user:crabbox -- status \
Always finish or explicitly keep the box:
```bash
pnpm qa:telegram-user:crabbox -- finish \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" finish \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json \
--preview-crop telegram-window
```
@@ -150,7 +158,8 @@ Attach only the useful visual artifact to the PR unless logs are needed. The
runner is GIF-only by default:
```bash
pnpm qa:telegram-user:crabbox -- publish \
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" publish \
--session .artifacts/qa-e2e/telegram-user-crabbox/pr-review/session.json \
--pr <pr-number> \
--summary 'Telegram real-user Crabbox session motion GIF'
@@ -189,7 +198,8 @@ experiments unless those artifacts are explicitly needed.
For a fast one-shot check, use:
```bash
pnpm qa:telegram-user:crabbox -- --text /status
proof_cmd="${OPENCLAW_TELEGRAM_USER_PROOF_CMD:-openclaw-telegram-user-crabbox-proof}"
"$proof_cmd" --text /status
```
This is a start/send/finish shortcut. Prefer the held session for PR review,

View File

@@ -6,6 +6,10 @@ capacity:
strategy: most-available
fallback: on-demand-after-120s
hints: true
availabilityZones:
- eu-west-1a
- eu-west-1b
- eu-west-1c
regions:
- eu-west-1
- eu-west-2
@@ -14,6 +18,9 @@ capacity:
- us-west-2
actions:
workflow: .github/workflows/crabbox-hydrate.yml
# Default AWS hydration uses local Actions replay. Use
# `crabbox actions hydrate --github-runner --job hydrate-github` when the
# hydrate job needs GitHub secrets.
job: hydrate
ref: main
runnerLabels:

6
.github/CODEOWNERS vendored
View File

@@ -13,6 +13,12 @@
/.github/workflows/codeql-critical-quality.yml @openclaw/openclaw-secops
/.github/workflows/dependency-change-awareness.yml @openclaw/openclaw-secops
/test/scripts/dependency-change-awareness-workflow.test.ts @openclaw/openclaw-secops
/package-lock.json @openclaw/openclaw-secops
/npm-shrinkwrap.json @openclaw/openclaw-secops
/extensions/*/package-lock.json @openclaw/openclaw-secops
/extensions/*/npm-shrinkwrap.json @openclaw/openclaw-secops
/pnpm-lock.yaml @openclaw/openclaw-secops
/scripts/generate-npm-shrinkwrap.mjs @openclaw/openclaw-secops
/src/security/ @openclaw/openclaw-secops
/src/secrets/ @openclaw/openclaw-secops
/src/config/*secret*.ts @openclaw/openclaw-secops

View File

@@ -140,13 +140,33 @@ runs:
run: |
set -euo pipefail
credentials=",$CREDENTIALS,"
if [[ "$credentials" == *",openai,"* ]]; then
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
fi
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
require_any() {
local label="$1"
shift
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
return 0
fi
done
echo "Missing credential for ${label}: expected one of $*" >&2
exit 1
}
if [[ "$credentials" == *",openai,"* ]]; then
require_any OpenAI OPENAI_API_KEY
fi
if [[ "$credentials" == *",codex,"* ]]; then
require_any Codex OPENCLAW_CODEX_AUTH_JSON
fi
if [[ "$credentials" == *",anthropic,"* ]]; then
require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON
fi
if [[ "$credentials" == *",factory,"* ]]; then
require_any Factory FACTORY_API_KEY
fi
if [[ "$credentials" == *",gemini,"* ]]; then
require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON
fi
if [[ "$credentials" == *",opencode,"* ]]; then
require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY
fi

View File

@@ -7,14 +7,6 @@ inputs:
description: Node.js version to install.
required: false
default: "24.x"
cache-key-suffix:
description: Suffix appended to the pnpm store cache key.
required: false
default: "node24-pnpm11"
pnpm-version:
description: pnpm version for corepack.
required: false
default: "11.0.8"
install-bun:
description: Whether to install Bun alongside Node.
required: false
@@ -27,6 +19,10 @@ inputs:
description: Whether to use --frozen-lockfile for install.
required: false
default: "true"
use-actions-cache:
description: Whether to restore and save the pnpm store with actions/cache.
required: false
default: "true"
runs:
using: composite
steps:
@@ -36,12 +32,11 @@ runs:
node-version: ${{ inputs.node-version }}
check-latest: false
- name: Setup pnpm + cache store
id: pnpm-cache
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm-store-cache
with:
pnpm-version: ${{ inputs.pnpm-version }}
cache-key-suffix: ${{ inputs.cache-key-suffix }}
node-version: ${{ inputs.node-version }}
use-actions-cache: ${{ inputs.use-actions-cache }}
- name: Setup Bun
if: inputs.install-bun == 'true'
@@ -58,14 +53,15 @@ runs:
if command -v bun &>/dev/null; then bun -v; fi
- name: Capture node path
if: inputs.install-deps == 'true'
shell: bash
run: |
node_bin="$(dirname "$(node -p 'process.execPath')")"
if command -v cygpath >/dev/null 2>&1; then
node_bin="$(cygpath -u "$node_bin")"
fi
# zizmor: ignore[github-env] node_bin comes from trusted actions/setup-node output in this composite action.
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
echo "$node_bin" >> "$GITHUB_PATH"
- name: Install dependencies
if: inputs.install-deps == 'true'
@@ -99,12 +95,25 @@ runs:
if [ -n "$LOCKFILE_FLAG" ]; then
install_args+=("$LOCKFILE_FLAG")
fi
append_pnpm_option_arg() {
local env_name="$1"
local option_name="$2"
local value="${!env_name-}"
if [ -n "$value" ]; then
install_args+=("--${option_name}=${value}")
fi
}
append_pnpm_option_arg PNPM_CONFIG_CHILD_CONCURRENCY child-concurrency
append_pnpm_option_arg PNPM_CONFIG_MODULES_DIR modules-dir
append_pnpm_option_arg PNPM_CONFIG_NETWORK_CONCURRENCY network-concurrency
append_pnpm_option_arg PNPM_CONFIG_VIRTUAL_STORE_DIR virtual-store-dir
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
mkdir -p "$PNPM_CONFIG_MODULES_DIR"
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi
pnpm "${install_args[@]}" || pnpm "${install_args[@]}"
- name: Save pnpm store cache
if: inputs.install-deps == 'true' && steps.pnpm-cache.outputs.cache-enabled == 'true' && steps.pnpm-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
continue-on-error: true
with:
path: ${{ steps.pnpm-cache.outputs.store-path }}
key: ${{ steps.pnpm-cache.outputs.primary-key }}
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
rm -rf node_modules
ln -sfn "$PNPM_CONFIG_MODULES_DIR" node_modules
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi

View File

@@ -1,91 +1,62 @@
name: Setup pnpm + store cache
description: Prepare pnpm via corepack and restore pnpm store cache.
name: Setup pnpm
description: Prepare pnpm from the repository packageManager and restore its store cache.
inputs:
pnpm-version:
description: pnpm version to activate via corepack.
package-manager-file:
description: package.json file that owns the packageManager pnpm pin.
required: false
default: "11.0.8"
cache-key-suffix:
description: Suffix appended to the cache key.
default: "package.json"
lockfile-path:
description: pnpm lockfile used to key the store cache.
required: false
default: "node24-pnpm11"
use-restore-keys:
description: Whether to use restore-keys fallback for actions/cache.
default: "pnpm-lock.yaml"
node-version:
description: Expected Node.js version already installed by actions/setup-node.
required: false
default: "true"
default: ""
use-actions-cache:
description: Whether to restore pnpm store with actions/cache.
description: Whether pnpm/action-setup should cache the pnpm store.
required: false
default: "true"
outputs:
cache-enabled:
description: Whether actions/cache restore was enabled.
value: ${{ steps.pnpm-cache-config.outputs.enabled }}
cache-hit:
description: Whether the pnpm store cache had an exact key hit.
value: ${{ steps.pnpm-cache-restore.outputs.cache-hit }}
cache-matched-key:
description: Cache key matched by restore, if any.
value: ${{ steps.pnpm-cache-restore.outputs.cache-matched-key }}
primary-key:
description: Primary pnpm store cache key.
value: ${{ steps.pnpm-cache-config.outputs.primary-key }}
store-path:
description: Resolved pnpm store path.
value: ${{ steps.pnpm-store.outputs.path }}
pnpm-version:
description: Resolved pnpm version activated by the setup action.
value: ${{ steps.pnpm-version.outputs.pnpm-version }}
project-dir:
description: Directory containing the packageManager file used for pnpm resolution.
value: ${{ steps.setup-pnpm.outputs.project-dir }}
runs:
using: composite
steps:
- name: Setup pnpm (corepack retry)
- name: Validate pnpm setup inputs
id: setup-pnpm
shell: bash
env:
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
PNPM_VERSION: ${{ inputs.pnpm-version }}
PACKAGE_MANAGER_FILE: ${{ inputs.package-manager-file }}
REQUESTED_NODE_VERSION: ${{ inputs.node-version }}
run: |
set -euo pipefail
if [[ ! "$PNPM_VERSION" =~ ^[0-9]+(\.[0-9]+){1,2}([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error::Invalid pnpm-version input: '$PNPM_VERSION'"
exit 2
project_dir="$(dirname "$PACKAGE_MANAGER_FILE")"
if [[ ! -f "$PACKAGE_MANAGER_FILE" ]]; then
echo "::error::package manager file not found: $PACKAGE_MANAGER_FILE"
exit 1
fi
corepack enable
for attempt in 1 2 3; do
if corepack prepare "pnpm@$PNPM_VERSION" --activate; then
pnpm -v
exit 0
fi
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
sleep $((attempt * 10))
done
exit 1
echo "project-dir=$project_dir" >> "$GITHUB_OUTPUT"
- name: Resolve pnpm store path
id: pnpm-store
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
requested_node="${REQUESTED_NODE_VERSION:-${NODE_VERSION:-}}"
source "$GITHUB_ACTION_PATH/ensure-node.sh"
openclaw_ensure_node "$requested_node"
- name: Resolve pnpm store cache keys
id: pnpm-cache-config
shell: bash
env:
CACHE_KEY_SUFFIX: ${{ inputs.cache-key-suffix }}
LOCKFILE_HASH: ${{ hashFiles('pnpm-lock.yaml') }}
USE_ACTIONS_CACHE: ${{ inputs.use-actions-cache }}
USE_RESTORE_KEYS: ${{ inputs.use-restore-keys }}
run: |
set -euo pipefail
echo "enabled=$USE_ACTIONS_CACHE" >> "$GITHUB_OUTPUT"
echo "primary-key=${RUNNER_OS}-pnpm-store-${CACHE_KEY_SUFFIX}-${LOCKFILE_HASH}" >> "$GITHUB_OUTPUT"
if [ "$USE_RESTORE_KEYS" = "true" ]; then
echo "restore-keys=${RUNNER_OS}-pnpm-store-${CACHE_KEY_SUFFIX}-" >> "$GITHUB_OUTPUT"
else
echo "restore-keys=" >> "$GITHUB_OUTPUT"
fi
- name: Restore pnpm store cache
id: pnpm-cache-restore
if: inputs.use-actions-cache == 'true'
uses: actions/cache/restore@v5
- name: Setup pnpm from packageManager
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ steps.pnpm-cache-config.outputs.primary-key }}
restore-keys: ${{ steps.pnpm-cache-config.outputs.restore-keys }}
package_json_file: ${{ inputs.package-manager-file }}
run_install: false
cache: ${{ inputs.use-actions-cache }}
cache_dependency_path: ${{ inputs.lockfile-path }}
- name: Record pnpm version
id: pnpm-version
shell: bash
env:
PROJECT_DIR: ${{ steps.setup-pnpm.outputs.project-dir }}
run: echo "pnpm-version=$(cd "$PROJECT_DIR" && pnpm -v)" >> "$GITHUB_OUTPUT"

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
openclaw_node_version_matches() {
local actual="$1"
local requested="$2"
if [[ -z "$requested" ]]; then
return 0
fi
case "$requested" in
*x)
[[ "${actual%%.*}" == "${requested%%.*}" ]]
;;
*.*.*)
[[ "$actual" == "$requested" ]]
;;
*.*)
[[ "$actual" == "$requested".* ]]
;;
*)
[[ "${actual%%.*}" == "$requested" ]]
;;
esac
}
openclaw_active_node_version() {
node -p 'process.versions.node' 2>/dev/null || true
}
openclaw_prepend_node_bin() {
local node_bin_dir="$1"
export PATH="$node_bin_dir:$PATH"
if [[ -n "${GITHUB_PATH:-}" ]]; then
echo "$node_bin_dir" >> "$GITHUB_PATH"
fi
hash -r
}
openclaw_find_toolcache_node() {
local requested_node="$1"
local roots=()
local root
for root in \
"${RUNNER_TOOL_CACHE:-}" \
"${AGENT_TOOLSDIRECTORY:-}" \
"${ACTIONS_RUNNER_TOOL_CACHE:-}" \
"/opt/hostedtoolcache" \
"/home/runner/_work/_tool" \
"/Users/runner/hostedtoolcache" \
"/c/hostedtoolcache/windows"
do
if [[ -d "$root/node" ]]; then
roots+=("$root/node")
elif [[ "$(basename "$root")" == "node" && -d "$root" ]]; then
roots+=("$root")
fi
done
local node_root candidate candidate_version
for node_root in "${roots[@]}"; do
while IFS= read -r candidate; do
candidate_version="$("$candidate" -p 'process.versions.node' 2>/dev/null || true)"
if openclaw_node_version_matches "$candidate_version" "$requested_node"; then
printf '%s\n' "$candidate"
return 0
fi
done < <(find "$node_root" \( -name node -o -name node.exe \) -type f 2>/dev/null | sort -r)
done
return 1
}
openclaw_ensure_node() {
local requested_node="${1:-}"
requested_node="${requested_node#v}"
if [[ -z "$requested_node" ]]; then
return 0
fi
local active_node_version node_bin
active_node_version="$(openclaw_active_node_version)"
if openclaw_node_version_matches "$active_node_version" "$requested_node"; then
echo "Using active Node ${active_node_version} at $(command -v node)"
return 0
fi
node_bin="$(openclaw_find_toolcache_node "$requested_node" || true)"
if [[ -n "$node_bin" ]]; then
echo "Using Node $("$node_bin" -p 'process.versions.node') from $node_bin"
openclaw_prepend_node_bin "$(dirname "$node_bin")"
fi
active_node_version="$(openclaw_active_node_version)"
if ! openclaw_node_version_matches "$active_node_version" "$requested_node"; then
echo "::error::Expected Node '${requested_node}', but active node is '${active_node_version:-missing}' at $(command -v node || true)"
return 1
fi
}

View File

@@ -16,8 +16,15 @@ Hard limits:
- 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 outcome
when GIFs would be misleading.
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.
- Do not skip Telegram-visible PRs just because the proof needs a specific
message, mock response, media attachment, command, button, reaction, stop
timing, approval prompt, or progress/final delivery sequence. First write a
concrete proof plan and try the standard harness path.
- 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:
@@ -39,12 +46,21 @@ Required workflow:
2. Inspect the PR with `gh pr view "$MANTIS_PR_NUMBER"` and
`gh pr diff "$MANTIS_PR_NUMBER"`.
3. Decide whether the PR has a visibly reproducible Telegram Desktop
before/after. If it does not, write
before/after. Treat these as visible until proven otherwise: message text
formatting/content, progress drafts, native drafts, final delivery, media or
document delivery, inline buttons, approval prompts, stop/abort behavior,
reactions/status indicators, guest/inline responses, TTS/voice/audio
delivery, and routing changes whose result is visible in the chat. For those
PRs, define the exact Telegram stimulus and expected main/PR visual delta
before deciding to skip.
If the PR does not have a Telegram-visible before/after, 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 the concrete
reason in the summary. Use this manifest shape and do not create worktrees
or start Crabbox for this case:
`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
{
@@ -73,6 +89,15 @@ Required workflow:
}
```
If the PR appears visual but proof is blocked by Telegram Desktop session
state, authorization, credentials, Crabbox, missing Telegram client support,
unavailable media/provider setup, 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.
@@ -94,8 +119,10 @@ Required workflow:
`$OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT`, the workflow-provided `crabbox`
binary, and the workflow-provided local `ffmpeg`/`ffprobe`; do not generate,
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.
proof idea for baseline and candidate. Let `start` return or fail on its
own; do not kill it while Crabbox is still waiting for bootstrap. Use a long
command timeout for `start`, `send`, `view`, and `finish`. You may iterate
and rerun if the visual result is not convincing.
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.
@@ -134,4 +161,6 @@ Expected final state:
`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/`.

13
.github/labeler.yml vendored
View File

@@ -36,6 +36,12 @@
- any-glob-to-any-file:
- "extensions/google-meet/**"
- "docs/plugins/google-meet.md"
"plugin: meeting-notes":
- changed-files:
- any-glob-to-any-file:
- "extensions/meeting-notes/**"
- "docs/plugins/meeting-notes.md"
- "src/meeting-notes/**"
"plugin: migrate-hermes":
- changed-files:
- any-glob-to-any-file:
@@ -101,7 +107,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:
@@ -284,6 +292,11 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/oc-path/**"
"extensions: policy":
- changed-files:
- any-glob-to-any-file:
- "extensions/policy/**"
- "docs/cli/policy.md"
"extensions: open-prose":
- changed-files:
- any-glob-to-any-file:

4
.github/package-trusted-sources.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 1,
"sources": {}
}

View File

@@ -1,159 +1,132 @@
## Summary
Describe the problem and fix in 25 bullets:
What problem does this PR solve?
Why does this matter now?
What is the intended outcome?
What is intentionally out of scope?
What does success look like?
What should reviewers focus on?
<details>
<summary>Summary guidance</summary>
This PR description is the contributor's durable explanation of the change. Write it for human maintainers first; ClawSweeper and Barnacle use the same text to understand intent, proof, risk, and current review state.
Describe the intent and outcome in 2-5 bullets. Avoid restating the diff; reviewers and bots can read the changed files.
If this PR fixes a plugin beta-release blocker, title it `fix(<plugin-id>): beta blocker - <summary>` and link the matching `Beta blocker: <plugin-name> - <summary>` issue labeled `beta-blocker`. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.
- Problem:
- Why it matters:
- What changed:
- What did NOT change (scope boundary):
</details>
## Change Type (select all)
## Linked context
- [ ] Bug fix
- [ ] Feature
- [ ] Refactor required for the fix
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
Which issue does this close?
## Scope (select all touched areas)
Closes #
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [ ] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
Which issues, PRs, or discussions are related?
## Linked Issue/PR
Related #
- Closes #
- Related #
- [ ] This PR fixes a bug or regression
Was this requested by a maintainer or owner?
<details>
<summary>Linked context guidance</summary>
Link the issue, PR, discussion, maintainer request, or owner request that explains why this PR should exist. Maintainer context helps reviewers and automation distinguish intended work from drive-by churn.
</details>
## Real behavior proof (required for external PRs)
External contributors must show after-fix evidence from a real OpenClaw setup. Unit tests, mocks, lint, typechecks, snapshots, and CI are supplemental only. Screenshots are encouraged even for CLI, console, text, or log changes; terminal screenshots and copied live output count. Be mindful of private information like IP addresses, API keys, phone numbers, non-public endpoints, or other private details when providing evidence.
- Behavior or issue addressed:
- Real environment tested:
- Exact steps or command run after this patch:
- Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output):
- Observed result after fix:
- What was not tested:
- Proof limitations or environment constraints:
- Before evidence (optional but encouraged):
## Root Cause (if applicable)
<details>
<summary>Real behavior proof guidance</summary>
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write `N/A`. If the cause is unclear, write `Unknown`.
External contributors must show after-fix evidence from a real OpenClaw setup. Unit tests, mocks, lint, typechecks, snapshots, and CI are supplemental only.
- Root cause:
- Missing detection / guardrail:
- Contributing context (if known):
Screenshots are encouraged even for CLI, console, text, or log changes. Terminal screenshots, copied live output, redacted runtime logs, recordings, and linked artifacts count.
## Regression Test Plan (if applicable)
If your environment cannot produce the ideal proof, explain that under `Proof limitations or environment constraints` so reviewers and ClawSweeper can direct the next step properly.
For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write `N/A`.
Be mindful of private information like IP addresses, API keys, phone numbers, non-public endpoints, or other private details when providing evidence.
- Coverage level that should have caught this:
- [ ] Unit test
- [ ] Seam / integration test
- [ ] End-to-end test
- [ ] Existing coverage already sufficient
- Target test or file:
- Scenario the test should lock in:
- Why this is the smallest reliable guardrail:
- Existing test that already covers this (if any):
- If no new test is added, why not:
</details>
## User-visible / Behavior Changes
## Tests and validation
List user-visible changes (including defaults/config).
If none, write `None`.
Which commands did you run?
## Diagram (if applicable)
For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write `N/A`.
What regression coverage was added or updated?
```text
Before:
[user action] -> [old state]
After:
[user action] -> [new state] -> [result]
```
What failed before this fix, if known?
## Security Impact (required)
- New permissions/capabilities? (`Yes/No`)
- Secrets/tokens handling changed? (`Yes/No`)
- New/changed network calls? (`Yes/No`)
- Command/tool execution surface changed? (`Yes/No`)
- Data access scope changed? (`Yes/No`)
- If any `Yes`, explain risk + mitigation:
If no test was added, why not?
## Repro + Verification
<details>
<summary>Testing guidance</summary>
### Environment
List focused commands, not every incidental check. CI is useful support, but external PRs still need real behavior proof above when behavior changes.
- OS:
- Runtime/container:
- Model/provider:
- Integration/channel (if any):
- Relevant config (redacted):
</details>
### Steps
## Risk checklist
1.
2.
3.
Did user-visible behavior change? (`Yes/No`)
### Expected
-
Did config, environment, or migration behavior change? (`Yes/No`)
### Actual
-
Did security, auth, secrets, network, or tool execution behavior change? (`Yes/No`)
## Evidence
Attach at least one:
What is the highest-risk area?
- [ ] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
## Human Verification (required)
How is that risk mitigated?
What you personally verified (not just CI), and how:
<details>
<summary>Risk guidance</summary>
- Verified scenarios:
- Edge cases checked:
- What you did **not** verify:
Use this for author judgment that is not obvious from the diff. ClawSweeper can see touched files, but it cannot know which behavior you think is risky, why the risk is acceptable, or what mitigation reviewers should verify.
## Review Conversations
</details>
- [ ] I replied to or resolved every bot review conversation I addressed in this PR.
- [ ] I left unresolved only the conversations that still need reviewer or maintainer judgment.
## Current review state
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
What is the next action?
## Compatibility / Migration
- Backward compatible? (`Yes/No`)
- Config/env changes? (`Yes/No`)
- Migration needed? (`Yes/No`)
- If yes, exact upgrade steps:
What is still waiting on author, maintainer, CI, or external proof?
## Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write `None`.
Which bot or reviewer comments were addressed?
- Risk:
- Mitigation:
<details>
<summary>Review state guidance</summary>
Keep this as the durable state for review progress. If useful information appears in comments, fold the current next action or blocker back here so maintainers and ClawSweeper do not need to reconstruct state from comment history.
</details>

View File

@@ -41,6 +41,10 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
if [[ -z "$CHECKOUT_TOKEN" ]]; then
echo "checkout token is missing" >&2
exit 1
fi
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
@@ -59,7 +63,7 @@ jobs:
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -187,12 +191,15 @@ jobs:
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
node_bin="$(dirname "$(node -p 'process.execPath')")"
pnpm_bin="$(command -v pnpm)"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo ln -sf "$pnpm_bin" /usr/local/bin/pnpm
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Hydrate Testbox provider env helper
shell: bash
@@ -222,6 +229,6 @@ jobs:
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
if: always()
if: success()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -39,6 +39,10 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
if [[ -z "$CHECKOUT_TOKEN" ]]; then
echo "checkout token is missing" >&2
exit 1
fi
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
@@ -57,7 +61,7 @@ jobs:
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -88,12 +92,15 @@ jobs:
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
node_bin="$(dirname "$(node -p 'process.execPath')")"
pnpm_bin="$(command -v pnpm)"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo ln -sf "$pnpm_bin" /usr/local/bin/pnpm
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Hydrate Testbox provider env helper
shell: bash
@@ -103,6 +110,7 @@ jobs:
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
@@ -123,7 +131,7 @@ jobs:
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
if: always()
if: success()
continue-on-error: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

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 }}
@@ -58,14 +60,11 @@ jobs:
plugin_contracts_matrix: ${{ steps.manifest.outputs.plugin_contracts_matrix }}
channel_contracts_matrix: ${{ steps.manifest.outputs.channel_contracts_matrix }}
run_checks: ${{ steps.manifest.outputs.run_checks }}
checks_matrix: ${{ steps.manifest.outputs.checks_matrix }}
run_checks_node_core_nondist: ${{ steps.manifest.outputs.run_checks_node_core_nondist }}
checks_node_core_nondist_matrix: ${{ steps.manifest.outputs.checks_node_core_nondist_matrix }}
run_checks_node_core_dist: ${{ steps.manifest.outputs.run_checks_node_core_dist }}
checks_node_core_dist_matrix: ${{ steps.manifest.outputs.checks_node_core_dist_matrix }}
run_check: ${{ steps.manifest.outputs.run_check }}
run_check_additional: ${{ steps.manifest.outputs.run_check_additional }}
run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }}
run_check_docs: ${{ steps.manifest.outputs.run_check_docs }}
run_control_ui_i18n: ${{ steps.manifest.outputs.run_control_ui_i18n }}
run_checks_windows: ${{ steps.manifest.outputs.run_checks_windows }}
@@ -82,7 +81,7 @@ jobs:
ref: ${{ inputs.target_ref || github.sha }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Resolve checkout SHA
@@ -132,6 +131,7 @@ jobs:
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
OPENCLAW_CI_CHECKOUT_REVISION: ${{ steps.checkout_ref.outputs.sha }}
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }}
run: |
node --input-type=module <<'EOF'
import { appendFileSync } from "node:fs";
@@ -173,6 +173,7 @@ jobs:
const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw";
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
const eventName = process.env.OPENCLAW_CI_EVENT_NAME ?? "";
const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly;
const runNodeFastOnly =
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_ONLY);
@@ -197,7 +198,7 @@ jobs:
const checksFastCoreTasks = [];
if (runNodeFull) {
checksFastCoreTasks.push(
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
{ check_name: "checks-fast-bundled-protocol", runtime: "node", task: "bundled-protocol" },
);
} else {
if (runNodeFastCiRouting) {
@@ -246,21 +247,12 @@ jobs:
runNodeFull ? createChannelContractTestShards() : [],
),
run_checks: runNodeFull,
checks_matrix: createMatrix(
runNodeFull
? [
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
]
: [],
),
run_checks_node_core_nondist: nodeTestNonDistShards.length > 0,
checks_node_core_nondist_matrix: createMatrix(nodeTestNonDistShards),
run_checks_node_core_dist: nodeTestDistShards.length > 0,
checks_node_core_dist_matrix: createMatrix(nodeTestDistShards),
run_check: runNodeFull,
run_check_additional: runNodeFull,
run_build_smoke: runNodeFull,
run_check_docs: docsChanged,
run_check_docs: docsChanged && eventName !== "push",
run_control_ui_i18n: runControlUiI18n,
run_skills_python_job: runSkillsPython,
run_checks_windows: runWindows,
@@ -295,13 +287,13 @@ jobs:
}
EOF
# Run the fast security/SCM checks in parallel with scope detection so the
# Run dependency-free security checks in parallel with scope detection so the
# main Node jobs do not have to wait for Python/pre-commit setup.
security-scm-fast:
security-fast:
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
@@ -312,7 +304,7 @@ jobs:
ref: ${{ inputs.target_ref || github.sha }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Ensure security base commit
@@ -390,22 +382,6 @@ jobs:
printf 'Auditing workflow files:\n%s\n' "${workflow_files[@]}"
pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}"
security-dependency-audit:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.target_ref || github.sha }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
submodules: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -415,35 +391,6 @@ jobs:
- name: Audit production dependencies
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
security-fast:
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
timeout-minutes: 5
steps:
- name: Verify fast security jobs
env:
DEPENDENCY_AUDIT_RESULT: ${{ needs.security-dependency-audit.result }}
SCM_RESULT: ${{ needs.security-scm-fast.result }}
run: |
set -euo pipefail
failed=0
for result in \
"security-scm-fast=${SCM_RESULT}" \
"security-dependency-audit=${DEPENDENCY_AUDIT_RESULT}"
do
job="${result%%=*}"
status="${result#*=}"
if [ "$status" != "success" ]; then
echo "::error::${job} ended with ${status}"
failed=1
fi
done
exit "$failed"
# Build dist once for Node-relevant changes and share it with downstream jobs.
# Keep this overlapping with the fast correctness lanes so green PRs get heavy
# test/build feedback sooner instead of waiting behind a full `check` pass.
@@ -469,8 +416,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -482,12 +427,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -641,6 +585,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
@@ -671,8 +624,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -684,12 +635,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -722,14 +672,9 @@ jobs:
run: |
set -euo pipefail
case "$TASK" in
bundled)
bundled-protocol)
pnpm test:bundled
;;
contracts-channels)
pnpm test:contracts:channels
;;
contracts-plugins)
pnpm test:contracts:plugins
pnpm protocol:check
;;
contracts-plugins-ci-routing)
pnpm test:contracts:plugins
@@ -766,8 +711,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -779,12 +722,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -828,28 +770,6 @@ 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
@@ -872,8 +792,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -885,12 +803,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -934,125 +851,6 @@ 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
timeout-minutes: 30
steps:
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
echo "checkout attempt ${attempt}/5 succeeded"
}
for attempt in 1 2 3 4 5; do
if checkout_attempt "$attempt"; then
exit 0
fi
echo "checkout attempt ${attempt}/5 failed"
sleep $((attempt * 5))
done
echo "checkout failed after 5 attempts" >&2
exit 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- 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
@@ -1072,8 +870,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -1085,12 +881,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1113,8 +908,7 @@ jobs:
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: "22.18.0"
cache-key-suffix: "node22-pnpm11"
node-version: "22.19.0"
install-bun: "false"
- name: Configure Node test resources
@@ -1152,8 +946,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -1165,12 +957,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1194,7 +985,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: "${{ matrix.node_version || '24.x' }}"
cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24-pnpm11' }}"
install-bun: "false"
- name: Configure Node test resources
@@ -1240,63 +1030,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:
@@ -1310,9 +1043,9 @@ jobs:
fail-fast: false
matrix:
include:
- check_name: check-preflight-guards
task: preflight-guards
runner: ubuntu-24.04
- check_name: check-guards
task: guards
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-prod-types
task: prod-types
runner: blacksmith-4vcpu-ubuntu-2404
@@ -1321,16 +1054,10 @@ jobs:
runner: blacksmith-16vcpu-ubuntu-2404
- check_name: check-dependencies
task: dependencies
runner: ubuntu-24.04
- check_name: check-policy-guards
task: policy-guards
runner: ubuntu-24.04
runner: blacksmith-8vcpu-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
steps:
- name: Checkout
shell: bash
@@ -1342,8 +1069,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -1355,12 +1080,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1393,12 +1117,19 @@ jobs:
run: |
set -euo pipefail
case "$TASK" in
preflight-guards)
guards)
pnpm check:no-conflict-markers
pnpm tool-display:check
pnpm check:host-env-policy:swift
pnpm dup:check:coverage
pnpm deps:shrinkwrap:check
pnpm deps:patches:check
pnpm lint:webhook:no-low-level-body-read
pnpm lint:auth:no-pairing-store-group
pnpm lint:auth:pairing-account-scope
pnpm check:import-cycles
# build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes.
pnpm build:plugin-sdk:strict-smoke
;;
prod-types)
pnpm tsgo:prod
@@ -1415,19 +1146,9 @@ jobs:
pnpm deadcode:ci
fi
;;
policy-guards)
pnpm lint:webhook:no-low-level-body-read
pnpm lint:auth:no-pairing-store-group
pnpm lint:auth:pairing-account-scope
pnpm check:import-cycles
;;
test-types)
pnpm check:test-types
;;
strict-smoke)
# build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes.
pnpm build:plugin-sdk:strict-smoke
;;
*)
echo "Unsupported check task: $TASK" >&2
exit 1
@@ -1442,24 +1163,6 @@ 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
@@ -1475,15 +1178,9 @@ jobs:
- check_name: check-additional-boundaries-a
group: boundaries
boundary_shard: 1/4
- check_name: check-additional-boundaries-b
- check_name: check-additional-boundaries-bcd
group: boundaries
boundary_shard: 2/4
- check_name: check-additional-boundaries-c
group: boundaries
boundary_shard: 3/4
- check_name: check-additional-boundaries-d
group: boundaries
boundary_shard: 4/4
boundary_shard: 2/4,3/4,4/4
- check_name: check-additional-extension-channels
group: extension-channels
- check_name: check-additional-extension-bundled
@@ -1503,8 +1200,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -1516,12 +1211,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1637,59 +1331,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
@@ -1702,8 +1350,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -1715,12 +1361,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1751,7 +1396,7 @@ jobs:
repository: openclaw/clawhub
path: clawhub-source
fetch-depth: 1
persist-credentials: false
persist-credentials: true
- name: Check docs
env:
@@ -1763,14 +1408,14 @@ 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
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: false
persist-credentials: true
submodules: false
- name: Setup Python
@@ -1813,7 +1458,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: false
persist-credentials: true
submodules: false
- name: Try to exclude workspace from Windows Defender (best-effort)
@@ -1841,14 +1486,10 @@ jobs:
node-version: 24.x
check-latest: false
- name: Setup pnpm + cache store
id: pnpm-cache
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm-store-cache
with:
pnpm-version: "11.0.8"
cache-key-suffix: "node24-pnpm11"
use-restore-keys: "false"
use-actions-cache: "true"
node-version: 24.x
- name: Runtime versions
run: |
@@ -1876,14 +1517,6 @@ jobs:
# caches can skip repeated rebuild/download work on later shards/runs.
pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true
- name: Save pnpm store cache
if: steps.pnpm-cache.outputs.cache-enabled == 'true' && steps.pnpm-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
continue-on-error: true
with:
path: ${{ steps.pnpm-cache.outputs.store-path }}
key: ${{ steps.pnpm-cache.outputs.primary-key }}
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
env:
TASK: ${{ matrix.task }}
@@ -1918,7 +1551,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: false
persist-credentials: true
submodules: false
- name: Setup Node environment
@@ -1959,7 +1592,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: false
persist-credentials: true
submodules: false
- name: Install XcodeGen / SwiftLint / SwiftFormat
@@ -2065,8 +1698,6 @@ jobs:
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
@@ -2078,12 +1709,11 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1

View File

@@ -138,7 +138,7 @@ jobs:
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_PROVIDER: ${{ secrets.ANTHROPIC_API_KEY != '' && 'anthropic' || 'openai' }}
OPENCLAW_CONTROL_UI_I18N_MODEL: ${{ secrets.ANTHROPIC_API_KEY != '' && 'claude-opus-4-6' || vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
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 }}

View File

@@ -31,10 +31,17 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
PNPM_CONFIG_CHILD_CONCURRENCY: "1"
PNPM_CONFIG_MODULES_DIR: "/tmp/openclaw-pnpm-node-modules"
PNPM_CONFIG_NETWORK_CONCURRENCY: "1"
PNPM_CONFIG_STORE_DIR: "/tmp/openclaw-pnpm-store"
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
PNPM_CONFIG_VIRTUAL_STORE_DIR: "/tmp/openclaw-pnpm-virtual-store"
jobs:
hydrate:
name: hydrate
if: ${{ inputs.crabbox_job != 'hydrate-github' }}
runs-on: [self-hosted, "${{ inputs.crabbox_runner_label }}"]
timeout-minutes: 120
steps:
@@ -42,37 +49,105 @@ jobs:
with:
ref: ${{ inputs.ref || github.ref }}
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
- name: Setup Node.js
uses: actions/setup-node@v6
with:
install-bun: "false"
node-version: "24"
- name: Setup pnpm and dependencies
shell: bash
env:
CI: "true"
run: |
set -euo pipefail
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$RUNNER_TEMP/cache}"
export COREPACK_HOME="${COREPACK_HOME:-$XDG_CACHE_HOME/corepack}"
export PNPM_HOME="${PNPM_HOME:-$RUNNER_TEMP/pnpm-home}"
mkdir -p "$XDG_CACHE_HOME" "$COREPACK_HOME" "$PNPM_HOME"
export PATH="$PNPM_HOME:$PATH"
{
echo "XDG_CACHE_HOME=$XDG_CACHE_HOME"
echo "COREPACK_HOME=$COREPACK_HOME"
echo "PNPM_HOME=$PNPM_HOME"
} >> "$GITHUB_ENV"
corepack enable --install-directory "$PNPM_HOME"
node_bin="$(dirname "$(node -p 'process.execPath')")"
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
echo "$node_bin" >> "$GITHUB_PATH"
export PATH="$node_bin:$PATH"
node -v
npm -v
pnpm -v
install_args=(
install
--prefer-offline
--ignore-scripts=false
--config.engine-strict=false
--config.enable-pre-post-scripts=true
--config.side-effects-cache=true
--frozen-lockfile
)
append_pnpm_option_arg() {
local env_name="$1"
local option_name="$2"
local value="${!env_name-}"
if [ -n "$value" ]; then
install_args+=("--${option_name}=${value}")
fi
}
append_pnpm_option_arg PNPM_CONFIG_CHILD_CONCURRENCY child-concurrency
append_pnpm_option_arg PNPM_CONFIG_MODULES_DIR modules-dir
append_pnpm_option_arg PNPM_CONFIG_NETWORK_CONCURRENCY network-concurrency
append_pnpm_option_arg PNPM_CONFIG_VIRTUAL_STORE_DIR virtual-store-dir
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
mkdir -p "$PNPM_CONFIG_MODULES_DIR"
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi
pnpm "${install_args[@]}" || pnpm "${install_args[@]}"
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
rm -rf node_modules
ln -sfn "$PNPM_CONFIG_MODULES_DIR" node_modules
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
fi
- name: Prepare Crabbox shell
shell: bash
run: |
set -euo pipefail
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
fi
node_bin="$(dirname "$(node -p 'process.execPath')")"
pnpm_bin="$(command -v pnpm)"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo ln -sf "$pnpm_bin" /usr/local/bin/pnpm
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Ensure Docker is available
- name: Ensure Docker is running
shell: bash
run: |
set -euo pipefail
if ! command -v docker >/dev/null 2>&1; then
echo "docker not found; installing fallback engine"
curl -fsSL https://get.docker.com | sudo sh
fi
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl start docker
sudo systemctl start docker || true
elif command -v service >/dev/null 2>&1; then
sudo service docker start || true
fi
if [ -S /var/run/docker.sock ]; then
@@ -82,30 +157,37 @@ jobs:
sudo chmod 666 /var/run/docker.sock
fi
if ! docker buildx version >/dev/null 2>&1; then
arch="$(uname -m)"
case "$arch" in
aarch64|arm64) buildx_arch=arm64 ;;
x86_64|amd64) buildx_arch=amd64 ;;
*) echo "unsupported buildx arch: $arch" >&2; exit 2 ;;
esac
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
mkdir -p "$HOME/.docker/cli-plugins"
curl -fsSL \
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
-o "$HOME/.docker/cli-plugins/docker-buildx"
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
fi
docker version
docker buildx version
docker compose version || true
- name: Ensure SSH is available
shell: bash
run: |
set -euo pipefail
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl start ssh || sudo systemctl start sshd || true
elif command -v service >/dev/null 2>&1; then
sudo service ssh start || sudo service sshd start || true
fi
- name: Hydrate provider env helper
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
run: bash scripts/ci-hydrate-testbox-env.sh
- name: Mark Crabbox ready
@@ -135,7 +217,196 @@ jobs:
fi
}
{
for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE; do
for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE XDG_CACHE_HOME COREPACK_HOME PNPM_HOME PNPM_CONFIG_CHILD_CONCURRENCY PNPM_CONFIG_MODULES_DIR PNPM_CONFIG_NETWORK_CONCURRENCY PNPM_CONFIG_STORE_DIR PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN PNPM_CONFIG_VIRTUAL_STORE_DIR; do
write_export "$key"
done
} > "${env_file}.tmp"
mv "${env_file}.tmp" "$env_file"
{
echo "# Docker containers visible from the hydrated runner"
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' 2>/dev/null || true
} > "${services_file}.tmp"
mv "${services_file}.tmp" "$services_file"
tmp="${state}.tmp"
{
echo "WORKSPACE=${GITHUB_WORKSPACE}"
echo "RUN_ID=${GITHUB_RUN_ID}"
echo "JOB=${job}"
echo "ENV_FILE=${env_file}"
echo "SERVICES_FILE=${services_file}"
echo "READY_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "$tmp"
mv "$tmp" "$state"
- name: Keep Crabbox job alive
shell: bash
env:
CRABBOX_ID: ${{ inputs.crabbox_id }}
CRABBOX_KEEP_ALIVE_MINUTES: ${{ inputs.crabbox_keep_alive_minutes }}
run: |
set -euo pipefail
case "$CRABBOX_ID" in
''|*[!A-Za-z0-9._-]*)
echo "Invalid crabbox_id" >&2
exit 2
;;
esac
minutes="${CRABBOX_KEEP_ALIVE_MINUTES}"
case "$minutes" in
''|*[!0-9]*) minutes=90 ;;
esac
stop="$HOME/.crabbox/actions/${CRABBOX_ID}.stop"
deadline=$(( $(date +%s) + minutes * 60 ))
while [ "$(date +%s)" -lt "$deadline" ]; do
if [ -f "$stop" ]; then
exit 0
fi
sleep 15
done
hydrate-github:
name: hydrate-github
if: ${{ inputs.crabbox_job == 'hydrate-github' }}
runs-on: [self-hosted, "${{ inputs.crabbox_runner_label }}"]
timeout-minutes: 120
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-actions-cache: "false"
- name: Prepare Crabbox shell
shell: bash
run: |
set -euo pipefail
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
fi
node_bin="$(dirname "$(node -p 'process.execPath')")"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Ensure Docker is running
shell: bash
run: |
set -euo pipefail
if ! command -v docker >/dev/null 2>&1; then
echo "docker not found; installing fallback engine"
curl -fsSL https://get.docker.com | sudo sh
fi
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl start docker || true
elif command -v service >/dev/null 2>&1; then
sudo service docker start || true
fi
if [ -S /var/run/docker.sock ]; then
sudo usermod -aG docker "$USER" || true
# The runner process keeps its original groups; grant this
# ephemeral runner session access without requiring a relogin.
sudo chmod 666 /var/run/docker.sock
fi
if ! docker buildx version >/dev/null 2>&1; then
arch="$(uname -m)"
case "$arch" in
aarch64|arm64) buildx_arch=arm64 ;;
x86_64|amd64) buildx_arch=amd64 ;;
*) echo "unsupported buildx arch: $arch" >&2; exit 2 ;;
esac
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
mkdir -p "$HOME/.docker/cli-plugins"
curl -fsSL \
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
-o "$HOME/.docker/cli-plugins/docker-buildx"
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
fi
docker version
docker buildx version
docker compose version || true
- name: Ensure SSH is available
shell: bash
run: |
set -euo pipefail
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl start ssh || sudo systemctl start sshd || true
elif command -v service >/dev/null 2>&1; then
sudo service ssh start || sudo service sshd start || true
fi
- name: Hydrate provider env helper
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
run: bash scripts/ci-hydrate-testbox-env.sh
- name: Mark Crabbox ready
shell: bash
env:
CRABBOX_ID: ${{ inputs.crabbox_id }}
CRABBOX_JOB: ${{ inputs.crabbox_job }}
run: |
set -euo pipefail
job="${CRABBOX_JOB}"
if [ -z "$job" ]; then job=hydrate-github; fi
case "$CRABBOX_ID" in
''|*[!A-Za-z0-9._-]*)
echo "Invalid crabbox_id" >&2
exit 2
;;
esac
mkdir -p "$HOME/.crabbox/actions"
state="$HOME/.crabbox/actions/${CRABBOX_ID}.env"
env_file="$HOME/.crabbox/actions/${CRABBOX_ID}.env.sh"
services_file="$HOME/.crabbox/actions/${CRABBOX_ID}.services"
write_export() {
key="$1"
value="${!key-}"
if [ -n "$value" ]; then
printf 'export %s=%q\n' "$key" "$value"
fi
}
{
for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE PNPM_CONFIG_CHILD_CONCURRENCY PNPM_CONFIG_MODULES_DIR PNPM_CONFIG_NETWORK_CONCURRENCY PNPM_CONFIG_STORE_DIR PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN PNPM_CONFIG_VIRTUAL_STORE_DIR; do
write_export "$key"
done
} > "${env_file}.tmp"

View File

@@ -34,11 +34,15 @@ jobs:
const isDependencyFile = (filename) =>
filename === "package.json" ||
filename === "package-lock.json" ||
filename === "npm-shrinkwrap.json" ||
filename === "pnpm-lock.yaml" ||
filename === "pnpm-workspace.yaml" ||
filename === "ui/package.json" ||
filename.startsWith("patches/") ||
/^packages\/[^/]+\/package\.json$/u.test(filename) ||
/^extensions\/[^/]+\/package-lock\.json$/u.test(filename) ||
/^extensions\/[^/]+\/npm-shrinkwrap\.json$/u.test(filename) ||
/^extensions\/[^/]+\/package\.json$/u.test(filename);
const sanitizeDisplayValue = (value) =>
@@ -143,7 +147,8 @@ jobs:
"",
"Maintainer follow-up:",
"- Review whether the dependency changes are intentional.",
"- Inspect resolved package deltas when lockfile or workspace dependency policy changes are present.",
"- Inspect resolved package deltas when lockfile, shrinkwrap, or workspace dependency policy changes are present.",
"- Treat `package-lock.json` and `npm-shrinkwrap.json` diffs as security-review surfaces.",
"- Run `pnpm deps:changes:report -- --base-ref origin/main --markdown /tmp/dependency-changes.md --json /tmp/dependency-changes.json` locally for detailed release-style evidence.",
].join("\n");

View File

@@ -155,7 +155,7 @@ jobs:
cache-from: type=gha,scope=docker-release-amd64
cache-to: type=gha,mode=max,scope=docker-release-amd64
build-args: |
OPENCLAW_EXTENSIONS=diagnostics-otel
OPENCLAW_EXTENSIONS=diagnostics-otel,codex
tags: ${{ steps.tags.outputs.value }}
labels: ${{ steps.labels.outputs.value }}
sbom: true
@@ -253,7 +253,7 @@ jobs:
cache-from: type=gha,scope=docker-release-arm64
cache-to: type=gha,mode=max,scope=docker-release-arm64
build-args: |
OPENCLAW_EXTENSIONS=diagnostics-otel
OPENCLAW_EXTENSIONS=diagnostics-otel,codex
tags: ${{ steps.tags.outputs.value }}
labels: ${{ steps.labels.outputs.value }}
sbom: true

View File

@@ -43,7 +43,7 @@ jobs:
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
uses: actions/setup-node@v6
with:
node-version: "22.18.0"
node-version: "24.x"
- name: Clone publish repo
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''

View File

@@ -6,6 +6,7 @@ on:
paths:
- "**/*.md"
- "docs/**"
- "!CHANGELOG.md"
permissions:
contents: read
@@ -35,5 +36,15 @@ jobs:
with:
install-bun: "false"
- name: Checkout ClawHub docs source
uses: actions/checkout@v6
with:
repository: openclaw/clawhub
path: clawhub-source
fetch-depth: 1
persist-credentials: false
- name: Check docs
env:
OPENCLAW_DOCS_SYNC_CLAWHUB_REPO: ${{ github.workspace }}/clawhub-source
run: pnpm check:docs

View File

@@ -88,6 +88,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for live Docker package checks; blank derives from release_package_spec or packs the selected ref
required: false
default: ""
type: string
npm_telegram_provider_mode:
description: Provider mode for the package Telegram E2E lane
required: false
@@ -108,13 +113,12 @@ permissions:
concurrency:
group: full-release-validation-${{ inputs.ref }}-${{ inputs.rerun_group }}
cancel-in-progress: ${{ inputs.ref == 'main' && inputs.rerun_group == 'all' }}
cancel-in-progress: ${{ (inputs.ref == 'main' && inputs.rerun_group == 'all') || startsWith(inputs.ref, 'tideclaw/alpha/') }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GH_REPO: ${{ github.repository }}
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
jobs:
resolve_target:
@@ -130,7 +134,7 @@ jobs:
ref: ${{ github.ref_name }}
path: workflow
fetch-depth: 1
persist-credentials: false
persist-credentials: true
submodules: false
- name: Resolve target SHA
@@ -151,6 +155,7 @@ jobs:
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
EVIDENCE_PACKAGE_SPEC: ${{ inputs.evidence_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'full' }}
RERUN_GROUP: ${{ inputs.rerun_group }}
@@ -208,14 +213,43 @@ jobs:
else
echo "- Package Acceptance package spec: SHA-built release artifact"
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
docker_runtime_assets_preflight:
name: Verify Docker runtime-assets prune path
needs: [resolve_target]
if: inputs.rerun_group == 'all'
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
contents: read
steps:
- name: Checkout target SHA
uses: actions/checkout@v6
with:
ref: ${{ needs.resolve_target.outputs.sha }}
fetch-depth: 1
persist-credentials: true
- name: Verify Docker runtime-assets prune path
env:
DOCKER_BUILDKIT: "1"
run: |
set -euo pipefail
timeout --foreground --kill-after=30s 35m docker build \
--target runtime-assets \
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
.
normal_ci:
name: Run normal full CI
needs: [resolve_target]
if: contains(fromJSON('["all","ci"]'), inputs.rerun_group)
needs: [resolve_target, docker_runtime_assets_preflight]
if: ${{ always() && needs.resolve_target.result == 'success' && contains(fromJSON('["all","ci"]'), inputs.rerun_group) && (inputs.rerun_group != 'all' || needs.docker_runtime_assets_preflight.result == 'success') }}
runs-on: ubuntu-24.04
timeout-minutes: ${{ inputs.release_profile == 'full' && 240 || 60 }}
timeout-minutes: ${{ inputs.release_profile != 'minimum' && 240 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}
@@ -236,9 +270,31 @@ jobs:
shift
local before_json dispatch_output run_id status conclusion url poll_count
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
gh_with_retry() {
local output status attempt
for attempt in 1 2 3 4 5 6; do
set +e
output="$(gh "$@" 2>&1)"
status=$?
set -e
if [[ "$status" -eq 0 ]]; then
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
fi
printf '%s\n' "$output" >&2
return "$status"
done
printf '%s\n' "$output" >&2
return "$status"
}
before_json="$(gh_with_retry run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
dispatch_output="$(gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@")"
printf '%s\n' "$dispatch_output"
run_id="$(
printf '%s\n' "$dispatch_output" |
@@ -249,7 +305,7 @@ jobs:
if [[ -z "$run_id" ]]; then
for _ in $(seq 1 60); do
run_id="$(
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
BEFORE_IDS="$before_json" gh_with_retry run list --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'
)"
if [[ -n "$run_id" ]]; then
@@ -267,6 +323,14 @@ jobs:
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
fetch_child_run_json() {
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
}
fetch_child_jobs() {
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -277,26 +341,26 @@ jobs:
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
status="$(fetch_child_run_json | jq -r '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh run view "$run_id" --json url --jq '.url')"
conclusion="$(fetch_child_run_json | jq -r '.conclusion // ""')"
url="$(fetch_child_run_json | jq -r '.html_url')"
echo "${workflow} finished with ${conclusion}: ${url}"
echo "url=${url}" >> "$GITHUB_OUTPUT"
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
fetch_child_jobs | jq 'select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url: .html_url}' || true
exit 1
fi
}
@@ -312,10 +376,10 @@ jobs:
plugin_prerelease:
name: Run plugin prerelease validation
needs: [resolve_target]
if: contains(fromJSON('["all","plugin-prerelease"]'), inputs.rerun_group)
needs: [resolve_target, docker_runtime_assets_preflight]
if: ${{ always() && needs.resolve_target.result == 'success' && contains(fromJSON('["all","plugin-prerelease"]'), inputs.rerun_group) && (inputs.rerun_group != 'all' || needs.docker_runtime_assets_preflight.result == 'success') }}
runs-on: ubuntu-24.04
timeout-minutes: ${{ inputs.release_profile == 'full' && 300 || 60 }}
timeout-minutes: ${{ inputs.release_profile == 'full' && 300 || inputs.release_profile == 'stable' && 240 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}
@@ -336,9 +400,31 @@ jobs:
shift
local before_json dispatch_output run_id status conclusion url poll_count
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
gh_with_retry() {
local output status attempt
for attempt in 1 2 3 4 5 6; do
set +e
output="$(gh "$@" 2>&1)"
status=$?
set -e
if [[ "$status" -eq 0 ]]; then
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
fi
printf '%s\n' "$output" >&2
return "$status"
done
printf '%s\n' "$output" >&2
return "$status"
}
before_json="$(gh_with_retry run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
dispatch_output="$(gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@")"
printf '%s\n' "$dispatch_output"
run_id="$(
printf '%s\n' "$dispatch_output" |
@@ -349,7 +435,7 @@ jobs:
if [[ -z "$run_id" ]]; then
for _ in $(seq 1 60); do
run_id="$(
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
BEFORE_IDS="$before_json" gh_with_retry run list --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'
)"
if [[ -n "$run_id" ]]; then
@@ -367,6 +453,14 @@ jobs:
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
fetch_child_run_json() {
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
}
fetch_child_jobs() {
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -377,26 +471,26 @@ jobs:
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
status="$(fetch_child_run_json | jq -r '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh run view "$run_id" --json url --jq '.url')"
conclusion="$(fetch_child_run_json | jq -r '.conclusion // ""')"
url="$(fetch_child_run_json | jq -r '.html_url')"
echo "${workflow} finished with ${conclusion}: ${url}"
echo "url=${url}" >> "$GITHUB_OUTPUT"
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
fetch_child_jobs | jq 'select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url: .html_url}' || true
exit 1
fi
}
@@ -412,10 +506,10 @@ jobs:
release_checks:
name: Run release/live/Docker/QA validation
needs: [resolve_target]
if: contains(fromJSON('["all","release-checks","install-smoke","cross-os","live-e2e","package","qa","qa-parity","qa-live"]'), inputs.rerun_group)
needs: [resolve_target, docker_runtime_assets_preflight]
if: ${{ always() && needs.resolve_target.result == 'success' && contains(fromJSON('["all","release-checks","install-smoke","cross-os","live-e2e","package","qa","qa-parity","qa-live"]'), inputs.rerun_group) && (inputs.rerun_group != 'all' || needs.docker_runtime_assets_preflight.result == 'success') }}
runs-on: ubuntu-24.04
timeout-minutes: ${{ inputs.release_profile == 'full' && 240 || 60 }}
timeout-minutes: ${{ inputs.release_profile != 'minimum' && 240 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}
@@ -437,6 +531,7 @@ jobs:
CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
run: |
set -euo pipefail
@@ -444,10 +539,32 @@ jobs:
local workflow="$1"
shift
local before_json dispatch_output run_id status conclusion url poll_count
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
local before_json dispatch_output run_id status conclusion url poll_count run_json
gh_with_retry() {
local output status attempt
for attempt in 1 2 3 4 5 6; do
set +e
output="$(gh "$@" 2>&1)"
status=$?
set -e
if [[ "$status" -eq 0 ]]; then
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
fi
printf '%s\n' "$output" >&2
return "$status"
done
printf '%s\n' "$output" >&2
return "$status"
}
before_json="$(gh_with_retry run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
dispatch_output="$(gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@")"
printf '%s\n' "$dispatch_output"
run_id="$(
printf '%s\n' "$dispatch_output" |
@@ -458,7 +575,7 @@ jobs:
if [[ -z "$run_id" ]]; then
for _ in $(seq 1 60); do
run_id="$(
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
BEFORE_IDS="$before_json" gh_with_retry run list --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'
)"
if [[ -n "$run_id" ]]; then
@@ -476,6 +593,54 @@ jobs:
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
fetch_child_run_json() {
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
}
fetch_child_jobs() {
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
release_check_blocking_job() {
case "$1" in
"resolve_target" | \
"Prepare release package artifact" | \
"install_smoke_release_checks / "* | \
"Run package acceptance" | \
"Run package acceptance / "*)
return 0
;;
esac
return 1
}
release_checks_advisory_only() {
local run_json="$1"
local verifier_conclusion name saw_advisory failed
verifier_conclusion="$(
jq -r '.jobs[] | select(.name == "Verify release checks") | .conclusion' <<< "$run_json" |
tail -n 1
)"
if [[ "$verifier_conclusion" != "success" ]]; then
return 1
fi
saw_advisory=0
failed=0
while IFS= read -r name; do
[[ -z "${name// }" ]] && continue
if release_check_blocking_job "$name"; then
echo "::error::${name} is a package-safety Tideclaw alpha release-check lane."
failed=1
else
saw_advisory=1
fi
done < <(jq -r '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | .name' <<< "$run_json")
[[ "$saw_advisory" == "1" && "$failed" == "0" ]]
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -486,26 +651,38 @@ jobs:
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
status="$(fetch_child_run_json | jq -r '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh run view "$run_id" --json url --jq '.url')"
jobs_json="$(fetch_child_jobs | jq -s '{jobs: [.[] | {name, conclusion, url: .html_url}]}')"
run_json="$(
jq -s '.[0] + .[1]' \
<(fetch_child_run_json | jq '{conclusion: (.conclusion // ""), url: .html_url}') \
<(printf '%s\n' "$jobs_json")
)"
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
url="$(jq -r '.url' <<< "$run_json")"
echo "${workflow} finished with ${conclusion}: ${url}"
echo "url=${url}" >> "$GITHUB_OUTPUT"
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
jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' <<< "$run_json" || true
if [[ "$workflow" == "openclaw-release-checks.yml" && "$CHILD_WORKFLOW_REF" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
if release_checks_advisory_only "$run_json"; then
echo "::warning::${workflow} ended with ${conclusion}, but Verify release checks accepted Tideclaw alpha advisory lanes."
return 0
fi
fi
exit 1
fi
}
@@ -532,6 +709,9 @@ jobs:
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
echo "- Package Acceptance package spec: \`${PACKAGE_ACCEPTANCE_PACKAGE_SPEC}\`"
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
child_rerun_group="$RERUN_GROUP"
@@ -560,13 +740,16 @@ jobs:
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
args+=(-f codex_plugin_spec="$CODEX_PLUGIN_SPEC")
fi
dispatch_and_wait openclaw-release-checks.yml "${args[@]}"
prepare_release_package:
name: Prepare release package artifact
needs: [resolve_target]
if: ${{ inputs.npm_telegram_package_spec == '' && inputs.release_package_spec == '' && inputs.rerun_group == 'all' && inputs.release_profile == 'full' }}
needs: [resolve_target, docker_runtime_assets_preflight]
if: ${{ always() && needs.resolve_target.result == 'success' && inputs.npm_telegram_package_spec == '' && inputs.release_package_spec == '' && inputs.rerun_group == 'all' && inputs.release_profile == 'full' && needs.docker_runtime_assets_preflight.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
@@ -581,7 +764,7 @@ jobs:
- name: Checkout trusted workflow ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ github.ref_name }}
fetch-depth: 0
@@ -593,7 +776,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "false"
@@ -638,6 +820,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:
@@ -659,7 +842,30 @@ jobs:
run: |
set -euo pipefail
before_json="$(gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
gh_with_retry() {
local output status attempt
for attempt in 1 2 3 4 5 6; do
set +e
output="$(gh "$@" 2>&1)"
status=$?
set -e
if [[ "$status" -eq 0 ]]; then
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
fi
printf '%s\n' "$output" >&2
return "$status"
done
printf '%s\n' "$output" >&2
return "$status"
}
before_json="$(gh_with_retry run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
args=(-f package_spec="${PACKAGE_SPEC:-openclaw@beta}" -f harness_ref="$TARGET_SHA" -f provider_mode="$PROVIDER_MODE")
if [[ -z "${PACKAGE_SPEC// }" ]]; then
@@ -677,12 +883,12 @@ jobs:
args+=(-f scenario="$SCENARIO")
fi
gh workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}"
gh_with_retry workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}"
run_id=""
for _ in $(seq 1 60); do
run_id="$(
BEFORE_IDS="$before_json" gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 50 --json databaseId,createdAt \
BEFORE_IDS="$before_json" gh_with_retry run list --workflow npm-telegram-beta-e2e.yml --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'
)"
if [[ -n "$run_id" ]]; then
@@ -709,32 +915,32 @@ jobs:
poll_count=0
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
status="$(gh_with_retry run view "$run_id" --json status --jq '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 10 == 0 )); then
echo "Still waiting on npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
gh_with_retry run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true
fi
sleep 30
done
trap - EXIT INT TERM
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh run view "$run_id" --json url --jq '.url')"
conclusion="$(gh_with_retry run view "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh_with_retry run view "$run_id" --json url --jq '.url')"
echo "npm-telegram-beta-e2e.yml finished with ${conclusion}: ${url}"
echo "url=${url}" >> "$GITHUB_OUTPUT"
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
gh_with_retry run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
exit 1
fi
summary:
name: Verify full validation
needs: [resolve_target, normal_ci, plugin_prerelease, release_checks, npm_telegram]
needs: [resolve_target, docker_runtime_assets_preflight, normal_ci, plugin_prerelease, release_checks, npm_telegram]
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
@@ -750,15 +956,58 @@ jobs:
PLUGIN_PRERELEASE_RESULT: ${{ needs.plugin_prerelease.result }}
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT: ${{ needs.docker_runtime_assets_preflight.result }}
RERUN_GROUP: ${{ inputs.rerun_group }}
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
run: |
set -euo pipefail
release_check_blocking_job() {
case "$1" in
"resolve_target" | \
"Prepare release package artifact" | \
"install_smoke_release_checks / "* | \
"Run package acceptance" | \
"Run package acceptance / "*)
return 0
;;
esac
return 1
}
release_checks_advisory_only() {
local run_json="$1"
local verifier_conclusion name saw_advisory failed
verifier_conclusion="$(
jq -r '.jobs[] | select(.name == "Verify release checks") | .conclusion' <<< "$run_json" |
tail -n 1
)"
if [[ "$verifier_conclusion" != "success" ]]; then
return 1
fi
saw_advisory=0
failed=0
while IFS= read -r name; do
[[ -z "${name// }" ]] && continue
if release_check_blocking_job "$name"; then
echo "::error::${name} is a package-safety Tideclaw alpha release-check lane."
failed=1
else
saw_advisory=1
fi
done < <(jq -r '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | .name' <<< "$run_json")
[[ "$saw_advisory" == "1" && "$failed" == "0" ]]
}
check_child() {
local label="$1"
local run_id="$2"
local required="$3"
local advisory_ok="${4:-0}"
if [[ -z "${run_id// }" ]]; then
if [[ "$required" == "0" ]]; then
@@ -784,6 +1033,12 @@ jobs:
fi
if [[ "$status" != "completed" || "$conclusion" != "success" ]]; then
if [[ "$advisory_ok" == "1" && "$label" == "release_checks" ]]; then
if release_checks_advisory_only "$run_json"; then
echo "::warning::${label} child run ended with ${status}/${conclusion}, but Verify release checks accepted Tideclaw alpha advisory lanes: ${url}"
return 0
fi
fi
echo "::error::${label} child run ended with ${status}/${conclusion}: ${url}"
jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, status, conclusion, url}' <<< "$run_json" || true
return 1
@@ -797,6 +1052,7 @@ jobs:
echo
echo "| Child | Result | Minutes | Head SHA | Run |"
echo "| --- | --- | ---: | --- | --- |"
echo "| \`docker_runtime_assets_preflight\` | \`${DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT}\` | | current workflow | |"
} >> "$GITHUB_STEP_SUMMARY"
append_child_row() {
@@ -932,29 +1188,56 @@ jobs:
}
failed=0
normal_ci_required=0
plugin_prerelease_required=0
release_checks_required=0
if [[ "$RERUN_GROUP" == "all" && "$DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT" != "success" ]]; then
echo "::error::Docker runtime-assets preflight ended with ${DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT}."
failed=1
elif [[ "$RERUN_GROUP" == "all" ]]; then
normal_ci_required=1
plugin_prerelease_required=1
release_checks_required=1
else
case "$RERUN_GROUP" in
ci)
normal_ci_required=1
;;
plugin-prerelease)
plugin_prerelease_required=1
;;
release-checks|install-smoke|cross-os|live-e2e|package|qa|qa-parity|qa-live)
release_checks_required=1
;;
esac
fi
append_child_overview
if [[ "$NORMAL_CI_RESULT" == "skipped" && -z "${NORMAL_CI_RUN_ID// }" ]]; then
check_child "normal_ci" "" 0 || failed=1
check_child "normal_ci" "" "$normal_ci_required" || failed=1
else
check_child "normal_ci" "$NORMAL_CI_RUN_ID" 1 || failed=1
fi
if [[ "$PLUGIN_PRERELEASE_RESULT" == "skipped" && -z "${PLUGIN_PRERELEASE_RUN_ID// }" ]]; then
check_child "plugin_prerelease" "" 0 || failed=1
check_child "plugin_prerelease" "" "$plugin_prerelease_required" || failed=1
else
check_child "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID" 1 || failed=1
fi
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" && -z "${RELEASE_CHECKS_RUN_ID// }" ]]; then
check_child "release_checks" "" 0 || failed=1
check_child "release_checks" "" "$release_checks_required" || failed=1
elif [[ "$CHILD_WORKFLOW_REF" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
check_child "release_checks" "$RELEASE_CHECKS_RUN_ID" 1 1 || failed=1
else
check_child "release_checks" "$RELEASE_CHECKS_RUN_ID" 1 || failed=1
fi
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
@@ -991,9 +1274,17 @@ jobs:
exit 0
fi
evidence_package_spec="$PACKAGE_SPEC"
if [[ -z "${evidence_package_spec// }" ]]; then
tag_ref="${TARGET_REF#refs/tags/}"
if [[ "$tag_ref" =~ ^v([0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?)$ ]]; then
evidence_package_spec="openclaw@${BASH_REMATCH[1]}"
fi
fi
release_id="${TARGET_REF#refs/tags/}"
release_id="${release_id#v}"
if [[ "$PACKAGE_SPEC" =~ ^openclaw@(.+)$ ]]; then
if [[ "$evidence_package_spec" =~ ^openclaw@(.+)$ ]]; then
release_id="${BASH_REMATCH[1]}"
fi
release_id="$(printf '%s' "$release_id" | tr '/:@ ' '----' | tr -cd 'A-Za-z0-9._-')"
@@ -1007,7 +1298,7 @@ jobs:
--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 package_spec "$evidence_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",
@@ -1029,6 +1320,15 @@ jobs:
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."
{
echo "### Private release evidence dispatch failed"
echo
echo "Child workflow validation remains authoritative. Backfill durable evidence from \`openclaw/releases-private\`:"
echo
echo "\`\`\`bash"
echo "gh workflow run openclaw-release-evidence-from-full-validation.yml --repo openclaw/releases-private --ref main -f full_validation_run_id=${GITHUB_RUN_ID_VALUE} -f release_id=${release_id} -f release_ref=${TARGET_REF} -f package_spec=${evidence_package_spec}"
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY"
fi
- name: Write release validation manifest

View File

@@ -109,6 +109,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Set up Blacksmith Docker Builder
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
@@ -219,6 +220,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
@@ -290,6 +292,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Run QR package install smoke
env:
@@ -305,6 +308,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
@@ -410,6 +414,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
@@ -454,10 +459,10 @@ jobs:
- name: Run installer docker tests
env:
OPENCLAW_INSTALL_URL: https://openclaw.ai/install.sh
OPENCLAW_INSTALL_CLI_URL: https://openclaw.ai/install-cli.sh
OPENCLAW_INSTALL_URL: file:///tmp/openclaw-install.sh
OPENCLAW_INSTALL_CLI_URL: file:///tmp/openclaw-install-cli.sh
OPENCLAW_NO_ONBOARD: "1"
OPENCLAW_INSTALL_SMOKE_SKIP_CLI: "1"
OPENCLAW_INSTALL_SMOKE_SKIP_CLI: "0"
OPENCLAW_INSTALL_SMOKE_SKIP_IMAGE_BUILD: "1"
OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD: "1"
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: "0"
@@ -468,6 +473,15 @@ jobs:
OPENCLAW_INSTALL_SMOKE_UPDATE_SKIP_LOCAL_BUILD: "1"
run: bash scripts/test-install-sh-docker.sh
- name: Run Rocky Linux installer smoke
run: |
timeout 20m docker run --rm \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \
rockylinux:9@sha256:d7be1c094cc5845ee815d4632fe377514ee6ebcf8efaed6892889657e5ddaaa6 \
bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install.sh --install-method npm --version latest --no-onboard --no-prompt --verify && openclaw --version'
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'
@@ -477,6 +491,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
@@ -515,6 +530,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Set up Blacksmith Docker Builder
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1

View File

@@ -89,10 +89,10 @@ jobs:
per_page: 100,
});
const excludedLockfiles = new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]);
const excludedLockfiles = new Set(["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lockb"]);
const totalChangedLines = files.reduce((total, file) => {
const path = file.filename ?? "";
if (path.startsWith("docs/") || excludedLockfiles.has(path)) {
if (path.startsWith("docs/") || excludedLockfiles.has(path) || path.endsWith("/package-lock.json") || path.endsWith("/npm-shrinkwrap.json")) {
return total;
}
return total + (file.additions ?? 0) + (file.deletions ?? 0);
@@ -603,10 +603,10 @@ jobs:
per_page: 100,
});
const excludedLockfiles = new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]);
const excludedLockfiles = new Set(["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lockb"]);
const totalChangedLines = files.reduce((total, file) => {
const path = file.filename ?? "";
if (path.startsWith("docs/") || excludedLockfiles.has(path)) {
if (path.startsWith("docs/") || excludedLockfiles.has(path) || path.endsWith("/package-lock.json") || path.endsWith("/npm-shrinkwrap.json")) {
return total;
}
return total + (file.additions ?? 0) + (file.deletions ?? 0);
@@ -760,6 +760,7 @@ jobs:
core.info(`Processed ${processed} pull requests.`);
label-issues:
if: github.event_name == 'issues'
permissions:
issues: write
runs-on: ubuntu-24.04

View File

@@ -25,7 +25,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
jobs:
validate_macos_release_request:
@@ -53,7 +52,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Ensure matching GitHub release exists

View File

@@ -25,7 +25,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
@@ -33,8 +32,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 +50,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 }}
@@ -135,7 +141,6 @@ jobs:
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
@@ -161,7 +166,7 @@ jobs:
- name: Upload Mantis artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mantis-discord-smoke-${{ github.run_id }}-${{ github.run_attempt }}
path: .artifacts/qa-e2e/mantis/

View File

@@ -32,7 +32,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
@@ -46,15 +45,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 +69,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 +126,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");
@@ -249,7 +254,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build Mantis harness
@@ -342,8 +346,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 \
@@ -522,7 +526,7 @@ jobs:
- name: Upload Mantis status reaction artifacts
id: upload_artifact
if: ${{ always() && steps.run_mantis.outputs.output_dir != '' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mantis-discord-status-reactions-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_mantis.outputs.output_dir }}
@@ -567,3 +571,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

@@ -32,7 +32,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
@@ -46,15 +45,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 +69,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 +126,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") ||
@@ -239,7 +244,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build Mantis harness
@@ -530,7 +534,7 @@ jobs:
- name: Upload Mantis thread attachment artifacts
id: upload_artifact
if: ${{ always() && steps.run_mantis.outputs.output_dir != '' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mantis-discord-thread-attachment-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_mantis.outputs.output_dir }}
@@ -589,3 +593,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

@@ -17,6 +17,11 @@ on:
required: true
default: slack-canary
type: string
approval_checkpoints:
description: Run native Slack approval checkpoint mode instead of gateway setup
required: false
default: false
type: boolean
keep_vm:
description: Keep the desktop lease open after a passing run
required: false
@@ -30,6 +35,14 @@ on:
options:
- aws
- hetzner
crabbox_market:
description: Crabbox capacity market for AWS leases
required: false
default: on-demand
type: choice
options:
- on-demand
- spot
crabbox_lease_id:
description: Optional existing Crabbox desktop/browser lease id or slug to reuse
required: false
@@ -55,7 +68,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
CRABBOX_REF: main
@@ -64,8 +76,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 +94,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 }}
@@ -155,7 +174,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build Mantis harness
@@ -222,9 +240,11 @@ jobs:
CRABBOX_ACCESS_CLIENT_SECRET: ${{ secrets.CRABBOX_ACCESS_CLIENT_SECRET }}
CRABBOX_LEASE_ID: ${{ inputs.crabbox_lease_id }}
CRABBOX_PROVIDER: ${{ inputs.crabbox_provider }}
CRABBOX_MARKET: ${{ inputs.crabbox_market }}
KEEP_VM: ${{ inputs.keep_vm }}
HYDRATE_MODE: ${{ inputs.hydrate_mode }}
SCENARIO_ID: ${{ inputs.scenario_id }}
APPROVAL_CHECKPOINTS: ${{ inputs.approval_checkpoints }}
shell: bash
run: |
set -euo pipefail
@@ -245,6 +265,15 @@ jobs:
require_var OPENCLAW_QA_CONVEX_SITE_URL
require_var OPENCLAW_QA_CONVEX_SECRET_CI
require_var CRABBOX_COORDINATOR_TOKEN
if [[ -z "${CRABBOX_LEASE_ID:-}" && "$CRABBOX_PROVIDER" == "aws" ]]; then
runner_ip="$(curl -fsS https://checkip.amazonaws.com | tr -d '[:space:]')"
if [[ -z "$runner_ip" ]]; then
echo "Could not resolve GitHub runner public IPv4 for AWS SSH ingress." >&2
exit 1
fi
export CRABBOX_AWS_SSH_CIDRS="${runner_ip}/32"
echo "Using AWS SSH CIDR ${CRABBOX_AWS_SSH_CIDRS}"
fi
candidate_repo="$(pwd)/.artifacts/qa-e2e/mantis/slack-desktop-smoke-worktrees/candidate"
output_rel=".artifacts/qa-e2e/mantis/slack-desktop-smoke"
@@ -260,6 +289,22 @@ jobs:
else
keep_args=(--no-keep-lease)
fi
market_args=()
if [[ -n "${CRABBOX_MARKET:-}" ]]; then
market_args=(--market "$CRABBOX_MARKET")
fi
gateway_args=(--gateway-setup)
approval_args=()
scenario_args=(--scenario "$SCENARIO_ID")
scenario_label="$SCENARIO_ID"
if [[ "$APPROVAL_CHECKPOINTS" == "true" ]]; then
approval_args=(--approval-checkpoints)
gateway_args=()
if [[ -z "${SCENARIO_ID:-}" || "$SCENARIO_ID" == "slack-canary" || "$SCENARIO_ID" == "approval-checkpoints" ]]; then
scenario_args=()
scenario_label="approval-checkpoints"
fi
fi
set +e
pnpm openclaw qa mantis slack-desktop-smoke \
@@ -269,15 +314,17 @@ jobs:
--class standard \
--idle-timeout 45m \
--ttl 120m \
--gateway-setup \
"${gateway_args[@]}" \
--credential-source convex \
--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" \
"${scenario_args[@]}" \
"${approval_args[@]}" \
"${market_args[@]}" \
"${keep_args[@]}" \
"${lease_args[@]}"
mantis_exit=$?
@@ -307,27 +354,81 @@ jobs:
status="$(jq -r '.status' "$root/mantis-slack-desktop-smoke-summary.json")"
screenshot_required=false
desktop_capture_inline=true
if [[ "$status" == "pass" ]]; then
screenshot_required=true
fi
evidence_summary="Mantis ran Slack QA inside a Crabbox Linux VNC desktop, started an OpenClaw Slack gateway in that VM, opened Slack Web in the visible browser, and captured screenshot/video evidence."
expected_result="Slack QA and VM gateway setup pass"
checkpoint_artifacts='[]'
checkpoint_required=false
if [[ "$APPROVAL_CHECKPOINTS" == "true" ]]; then
evidence_summary="Mantis ran Slack native approval QA inside a Crabbox Linux VNC desktop, rendered pending/resolved approval checkpoints from the Slack API messages, and stored Slack QA artifacts."
expected_result="Slack native exec and plugin approval checkpoints pass"
screenshot_required=false
desktop_capture_inline=false
if [[ "$status" == "pass" ]]; then
checkpoint_required=true
fi
checkpoint_scenarios=()
if [[ "$scenario_label" == "approval-checkpoints" ]]; then
checkpoint_scenarios=("slack-approval-exec-native" "slack-approval-plugin-native")
else
checkpoint_scenarios=("$scenario_label")
fi
checkpoint_scenarios_json="$(printf '%s\n' "${checkpoint_scenarios[@]}" | jq -R . | jq -s .)"
checkpoint_artifacts="$(
jq -n \
--argjson checkpoint_required "$checkpoint_required" \
--argjson scenario_ids "$checkpoint_scenarios_json" \
'
def scenario_kind($id):
if $id == "slack-approval-exec-native" then "exec"
elif $id == "slack-approval-plugin-native" then "plugin"
else error("unsupported approval checkpoint scenario: \($id)")
end;
def scenario_title($id):
if scenario_kind($id) == "exec" then "Exec" else "Plugin" end;
[
$scenario_ids[] as $id
| ["pending", "resolved"][] as $state
| {
kind: "desktopScreenshot",
lane: "candidate",
label: "\(scenario_title($id)) approval \($state) checkpoint",
path: "approval-checkpoints/\($id)-\($state).png",
targetPath: "approval-checkpoints/\($id)-\($state).png",
alt: "Rendered Slack \(scenario_kind($id)) approval \($state) checkpoint",
width: 720,
inline: true,
required: $checkpoint_required
}
]
'
)"
fi
jq -n \
--arg status "$status" \
--arg candidate_sha "${{ needs.validate_ref.outputs.candidate_revision }}" \
--arg scenario "$SCENARIO_ID" \
--arg scenario "$scenario_label" \
--arg summary "$evidence_summary" \
--arg expected "$expected_result" \
--argjson checkpoint_artifacts "$checkpoint_artifacts" \
--argjson screenshot_required "$screenshot_required" \
--argjson desktop_capture_inline "$desktop_capture_inline" \
'{
schemaVersion: 1,
id: "slack-desktop-smoke",
title: "Mantis Slack Desktop Smoke QA",
summary: "Mantis ran Slack QA inside a Crabbox Linux VNC desktop, started an OpenClaw Slack gateway in that VM, opened Slack Web in the visible browser, and captured screenshot/video evidence.",
summary: $summary,
scenario: $scenario,
comparison: {
candidate: { sha: $candidate_sha, expected: "Slack QA and VM gateway setup pass", status: $status, fixed: ($status == "pass") },
candidate: { sha: $candidate_sha, expected: $expected, status: $status, fixed: ($status == "pass") },
pass: ($status == "pass")
},
artifacts: [
{ kind: "desktopScreenshot", lane: "candidate", label: "Slack desktop/VNC browser", path: "slack-desktop-smoke.png", targetPath: "slack-desktop.png", alt: "Slack Web desktop screenshot from the Mantis VM", width: 720, inline: true, required: $screenshot_required },
{ kind: "motionPreview", lane: "candidate", label: "Slack motion preview", path: "slack-desktop-smoke-preview.gif", targetPath: "slack-desktop-preview.gif", alt: "Animated Slack desktop preview", width: 720, inline: true, required: false },
artifacts: ([
{ kind: "desktopScreenshot", lane: "candidate", label: "Slack desktop/VNC browser", path: "slack-desktop-smoke.png", targetPath: "slack-desktop.png", alt: "Slack Web desktop screenshot from the Mantis VM", width: 720, inline: $desktop_capture_inline, required: $screenshot_required },
{ kind: "motionPreview", lane: "candidate", label: "Slack motion preview", path: "slack-desktop-smoke-preview.gif", targetPath: "slack-desktop-preview.gif", alt: "Animated Slack desktop preview", width: 720, inline: $desktop_capture_inline, required: false },
{ kind: "motionClip", lane: "candidate", label: "Slack change MP4", path: "slack-desktop-smoke-change.mp4", targetPath: "slack-desktop-change.mp4", required: false },
{ kind: "fullVideo", lane: "candidate", label: "Slack desktop MP4", path: "slack-desktop-smoke.mp4", targetPath: "slack-desktop.mp4", required: false },
{ kind: "metadata", lane: "run", label: "Slack desktop summary", path: "mantis-slack-desktop-smoke-summary.json", targetPath: "summary.json" },
@@ -335,7 +436,7 @@ jobs:
{ kind: "metadata", lane: "run", label: "Slack command log", path: "slack-desktop-command.log", targetPath: "slack-desktop-command.log", required: false },
{ kind: "metadata", lane: "run", label: "Slack preview metadata", path: "slack-desktop-smoke-preview.json", targetPath: "slack-desktop-preview.json", required: false },
{ kind: "metadata", lane: "run", label: "Slack error", path: "error.txt", targetPath: "error.txt", required: false }
]
] + $checkpoint_artifacts)
}' > "$root/mantis-evidence.json"
cat "$root/mantis-slack-desktop-smoke-report.md" >> "$GITHUB_STEP_SUMMARY"
@@ -352,7 +453,7 @@ jobs:
- name: Upload Mantis Slack desktop artifacts
id: upload_artifact
if: ${{ always() && steps.run_mantis.outputs.output_dir != '' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mantis-slack-desktop-smoke-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_mantis.outputs.output_dir }}

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,6 +27,14 @@ 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
@@ -35,7 +45,6 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
CRABBOX_REF: main
@@ -47,6 +56,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 +72,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 +96,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 +115,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 +135,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 +213,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 +292,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
@@ -236,16 +307,36 @@ jobs:
run: |
set -euo pipefail
current_created="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" --jq .created_at)"
stale_before="$(date -u -d '8 hours ago' +%Y-%m-%dT%H:%M:%SZ)"
run_has_active_jobs() {
local run_id="$1"
local run_state="$2"
if [[ "$run_state" != "in_progress" ]]; then
return 0
fi
local active_jobs
active_jobs="$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json jobs --jq '[.jobs[] | select(.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "pending" or .status == "requested")] | length')"
[[ "$active_jobs" != "0" ]]
}
while true; do
blockers="$(
candidates="$(
for workflow in mantis-telegram-desktop-proof.yml mantis-telegram-live.yml; do
gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --limit 100 --json databaseId,status,createdAt,url \
| jq -r \
--argjson current_id "$GITHUB_RUN_ID" \
--arg current_created "$current_created" \
'.[] | select(.databaseId != $current_id) | select(.createdAt < $current_created or (.createdAt == $current_created and .databaseId < $current_id)) | select(.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "pending" or .status == "requested") | "\(.createdAt)\t#\(.databaseId)\t\(.status)\t\(.url)"'
for status in queued in_progress waiting pending requested; do
gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --status "$status" --limit 100 --json databaseId,status,createdAt,url \
| jq -r \
--argjson current_id "$GITHUB_RUN_ID" \
--arg current_created "$current_created" \
--arg stale_before "$stale_before" \
'.[] | select(.databaseId != $current_id) | select(.createdAt >= $stale_before) | select(.createdAt < $current_created or (.createdAt == $current_created and .databaseId < $current_id)) | "\(.createdAt)\t#\(.databaseId)\t\(.status)\t\(.url)"'
done
done | sort -u
)"
blockers=""
while IFS=$'\t' read -r created run_id run_state url; do
if [[ -n "$run_id" ]] && run_has_active_jobs "${run_id#\#}" "$run_state"; then
blockers+="${created}"$'\t'"${run_id}"$'\t'"${run_state}"$'\t'"${url}"$'\n'
fi
done <<<"$candidates"
if [[ -z "$blockers" ]]; then
break
fi
@@ -264,7 +355,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Setup Go for Crabbox CLI
@@ -334,7 +424,7 @@ jobs:
printf '%s\n' 'Defaults env_keep += "BASELINE_REF BASELINE_SHA CANDIDATE_REF CANDIDATE_SHA"'
printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_LEASE_ID CRABBOX_PROVIDER"'
printf '%s\n' 'Defaults env_keep += "GH_TOKEN MANTIS_CANDIDATE_TRUST MANTIS_INSTRUCTIONS MANTIS_OUTPUT_DIR MANTIS_PR_NUMBER"'
printf '%s\n' 'Defaults env_keep += "OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_QA_CONVEX_SECRET_CI OPENCLAW_QA_CONVEX_SITE_URL OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN"'
printf '%s\n' 'Defaults env_keep += "OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_QA_CONVEX_SECRET_CI OPENCLAW_QA_CONVEX_SITE_URL OPENCLAW_QA_CREDENTIAL_OWNER_ID OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN"'
printf '%s\n' 'Defaults env_keep += "OPENCLAW_TELEGRAM_USER_CRABBOX_BIN OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT OPENCLAW_TELEGRAM_USER_PROOF_CMD"'
} | sudo tee /etc/sudoers.d/mantis-codex-env >/dev/null
sudo chmod 0440 /etc/sudoers.d/mantis-codex-env
@@ -370,6 +460,7 @@ jobs:
MANTIS_PR_NUMBER: ${{ needs.resolve_request.outputs.pr_number }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CREDENTIAL_OWNER_ID: mantis-telegram-desktop-${{ github.run_id }}-${{ github.run_attempt }}
OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
OPENCLAW_TELEGRAM_USER_CRABBOX_BIN: /usr/local/bin/crabbox
@@ -386,6 +477,48 @@ jobs:
codex-home: /tmp/mantis-codex-home-${{ github.run_id }}
safety-strategy: unprivileged-user
codex-user: codex
allow-bot-users: clawsweeper[bot]
- name: Release leaked Telegram proof leases
if: ${{ always() }}
env:
CRABBOX_PROVIDER: ${{ needs.resolve_request.outputs.crabbox_provider }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
shell: bash
run: |
set -euo pipefail
if [[ ! -d .artifacts/qa-e2e ]]; then
exit 0
fi
status=0
mapfile -d '' session_files < <(sudo find .artifacts/qa-e2e -path '*/telegram-user-crabbox/*/session.json' -type f -print0)
for session_file in "${session_files[@]}"; do
lease_file="${session_file%/session.json}/.session/lease.json"
if [[ ! -f "$lease_file" ]]; then
continue
fi
if ! sudo -u codex env \
OPENCLAW_QA_CONVEX_SECRET_CI="$OPENCLAW_QA_CONVEX_SECRET_CI" \
OPENCLAW_QA_CONVEX_SITE_URL="$OPENCLAW_QA_CONVEX_SITE_URL" \
OPENCLAW_TELEGRAM_USER_CRABBOX_BIN=/usr/local/bin/crabbox \
OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER="$CRABBOX_PROVIDER" \
node --import tsx "$GITHUB_WORKSPACE/scripts/e2e/telegram-user-crabbox-proof.ts" \
finish --session "$session_file" --preview-crop telegram-window; then
status=1
fi
done
mapfile -d '' lease_files < <(sudo find .artifacts/qa-e2e -path '*/telegram-user-crabbox/*/.session/lease.json' -type f -print0)
for lease_file in "${lease_files[@]}"; do
if ! sudo -u codex env \
OPENCLAW_QA_CONVEX_SECRET_CI="$OPENCLAW_QA_CONVEX_SECRET_CI" \
OPENCLAW_QA_CONVEX_SITE_URL="$OPENCLAW_QA_CONVEX_SITE_URL" \
node --import tsx "$GITHUB_WORKSPACE/scripts/e2e/telegram-user-credential.ts" \
release --lease-file "$lease_file"; then
status=1
fi
done
exit "$status"
- name: Inspect Mantis evidence manifest
id: inspect
@@ -406,7 +539,7 @@ jobs:
- name: Upload Mantis Telegram desktop artifacts
id: upload_artifact
if: ${{ always() && steps.inspect.outputs.output_dir != '' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mantis-telegram-desktop-proof-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.inspect.outputs.output_dir }}
@@ -466,3 +599,132 @@ 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 }}
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

@@ -41,7 +41,6 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
CRABBOX_REF: main
@@ -56,15 +55,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 +79,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 +138,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");
@@ -257,16 +271,36 @@ jobs:
run: |
set -euo pipefail
current_created="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" --jq .created_at)"
stale_before="$(date -u -d '8 hours ago' +%Y-%m-%dT%H:%M:%SZ)"
run_has_active_jobs() {
local run_id="$1"
local run_state="$2"
if [[ "$run_state" != "in_progress" ]]; then
return 0
fi
local active_jobs
active_jobs="$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json jobs --jq '[.jobs[] | select(.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "pending" or .status == "requested")] | length')"
[[ "$active_jobs" != "0" ]]
}
while true; do
blockers="$(
candidates="$(
for workflow in mantis-telegram-desktop-proof.yml mantis-telegram-live.yml; do
gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --limit 100 --json databaseId,status,createdAt,url \
| jq -r \
--argjson current_id "$GITHUB_RUN_ID" \
--arg current_created "$current_created" \
'.[] | select(.databaseId != $current_id) | select(.createdAt < $current_created or (.createdAt == $current_created and .databaseId < $current_id)) | select(.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "pending" or .status == "requested") | "\(.createdAt)\t#\(.databaseId)\t\(.status)\t\(.url)"'
for status in queued in_progress waiting pending requested; do
gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --status "$status" --limit 100 --json databaseId,status,createdAt,url \
| jq -r \
--argjson current_id "$GITHUB_RUN_ID" \
--arg current_created "$current_created" \
--arg stale_before "$stale_before" \
'.[] | select(.databaseId != $current_id) | select(.createdAt >= $stale_before) | select(.createdAt < $current_created or (.createdAt == $current_created and .databaseId < $current_id)) | "\(.createdAt)\t#\(.databaseId)\t\(.status)\t\(.url)"'
done
done | sort -u
)"
blockers=""
while IFS=$'\t' read -r created run_id run_state url; do
if [[ -n "$run_id" ]] && run_has_active_jobs "${run_id#\#}" "$run_state"; then
blockers+="${created}"$'\t'"${run_id}"$'\t'"${run_state}"$'\t'"${url}"$'\n'
fi
done <<<"$candidates"
if [[ -z "$blockers" ]]; then
break
fi
@@ -285,7 +319,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build Mantis harness
@@ -379,7 +412,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
@@ -464,7 +497,7 @@ jobs:
- name: Upload Mantis Telegram artifacts
id: upload_artifact
if: ${{ always() && steps.run_mantis.outputs.output_dir != '' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: mantis-telegram-live-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_mantis.outputs.output_dir }}
@@ -525,3 +558,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
@@ -94,12 +104,12 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
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:
@@ -136,7 +146,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Validate inputs and secrets
@@ -259,7 +268,7 @@ jobs:
- name: Upload npm Telegram E2E artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: npm-telegram-beta-e2e-${{ github.run_id }}-${{ github.run_attempt }}
path: .artifacts/qa-e2e/

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
@@ -183,14 +193,14 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
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 }}
@@ -328,7 +338,7 @@ jobs:
ref: ${{ steps.workflow_ref.outputs.value }}
path: workflow
fetch-depth: 1
persist-credentials: false
persist-credentials: true
- name: Checkout public source ref
if: inputs.candidate_artifact_name == ''
@@ -338,21 +348,21 @@ jobs:
ref: ${{ inputs.ref }}
path: source
fetch-depth: 0
persist-credentials: false
persist-credentials: true
submodules: recursive
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }}
- name: Setup pnpm
uses: ./workflow/.github/actions/setup-pnpm-store-cache
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-file: ${{ inputs.candidate_artifact_name == '' && 'source/package.json' || 'workflow/package.json' }}
lockfile-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }}
use-actions-cache: ${{ inputs.candidate_artifact_name == '' && 'true' || 'false' }}
- name: Ensure pnpm store cache directory exists
run: mkdir -p "$(pnpm store path --silent)"
@@ -513,6 +523,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) }}
@@ -526,19 +537,21 @@ jobs:
ref: ${{ needs.prepare.outputs.workflow_ref }}
path: workflow
fetch-depth: 1
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
persist-credentials: true
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup pnpm
uses: ./workflow/.github/actions/setup-pnpm-store-cache
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-file: workflow/package.json
lockfile-path: workflow/pnpm-lock.yaml
use-actions-cache: "false"
- name: Download candidate artifact
uses: actions/download-artifact@v8
with:

View File

@@ -68,6 +68,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for the live package lane; blank packs extensions/codex from the selected ref
required: false
default: ""
type: string
include_live_suites:
description: Whether to run live-provider coverage
required: false
@@ -97,8 +102,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
@@ -163,6 +178,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for the live package lane; blank packs extensions/codex from the selected ref
required: false
default: ""
type: string
include_live_suites:
description: Whether to run live-provider coverage
required: false
@@ -199,6 +219,8 @@ on:
required: false
ANTHROPIC_API_TOKEN:
required: false
FACTORY_API_KEY:
required: false
BYTEPLUS_API_KEY:
required: false
CEREBRAS_API_KEY:
@@ -288,7 +310,6 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
jobs:
validate_selected_ref:
@@ -311,9 +332,6 @@ jobs:
set -euo pipefail
trusted_reason=""
git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
git fetch --tags origin '+refs/tags/*:refs/tags/*'
# Resolve here instead of in actions/checkout so short SHAs work too.
if ! selected_sha="$(git rev-parse --verify "${INPUT_REF}^{commit}")"; then
echo "Ref '${INPUT_REF}' could not be resolved to a commit." >&2
@@ -455,6 +473,7 @@ 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')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: 20
env:
@@ -473,7 +492,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Validate live cache credentials
@@ -505,6 +523,7 @@ jobs:
validate_repo_e2e:
needs: validate_selected_ref
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
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:
@@ -520,7 +539,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build dist for repo E2E
@@ -528,12 +546,16 @@ jobs:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Install Playwright Chromium
run: pnpm --dir ui exec playwright install --with-deps chromium
- name: Run repo E2E suite
run: pnpm test:e2e
validate_special_e2e:
needs: validate_selected_ref
if: inputs.include_repo_e2e && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e')
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:
@@ -561,7 +583,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build dist for special E2E
@@ -608,6 +629,7 @@ jobs:
needs: [validate_selected_ref, prepare_docker_e2e_image]
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (${{ matrix.label }})
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:
@@ -620,7 +642,7 @@ jobs:
profiles: stable full
- chunk_id: package-update-openai
label: package/update OpenAI install
timeout_minutes: 20
timeout_minutes: 45
profiles: beta minimum stable full
- chunk_id: package-update-anthropic
label: package/update Anthropic install
@@ -676,6 +698,7 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
@@ -723,6 +746,7 @@ jobs:
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_DOCKER_ALL_RELEASE_PROFILE: ${{ inputs.release_test_profile }}
OPENCLAW_CODEX_NPM_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
@@ -735,6 +759,7 @@ jobs:
if: contains(matrix.profiles, inputs.release_test_profile)
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
@@ -742,24 +767,23 @@ jobs:
if: contains(matrix.profiles, inputs.release_test_profile)
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.sha }}
fetch-depth: 1
path: .release-harness
- name: Log in to GHCR for shared Docker E2E image
if: contains(matrix.profiles, inputs.release_test_profile)
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Setup Node environment
if: contains(matrix.profiles, inputs.release_test_profile)
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Hydrate live auth/profile inputs
@@ -821,15 +845,35 @@ jobs:
run: |
set -euo pipefail
credentials=",$CREDENTIALS,"
if [[ "$credentials" == *",openai,"* ]]; then
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
fi
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
require_any() {
local label="$1"
shift
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
return 0
fi
done
echo "Missing credential for ${label}: expected one of $*" >&2
exit 1
}
if [[ "$credentials" == *",openai,"* ]]; then
require_any OpenAI OPENAI_API_KEY
fi
if [[ "$credentials" == *",codex,"* ]]; then
require_any Codex OPENCLAW_CODEX_AUTH_JSON
fi
if [[ "$credentials" == *",anthropic,"* ]]; then
require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON
fi
if [[ "$credentials" == *",factory,"* ]]; then
require_any Factory FACTORY_API_KEY
fi
if [[ "$credentials" == *",gemini,"* ]]; then
require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON
fi
if [[ "$credentials" == *",opencode,"* ]]; then
require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY
fi
- name: Run Docker E2E chunk
@@ -876,6 +920,7 @@ jobs:
plan_docker_lane_groups:
needs: validate_selected_ref
if: inputs.docker_lanes != ''
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 5
outputs:
@@ -884,6 +929,7 @@ jobs:
- name: Checkout trusted release harness
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.sha }}
fetch-depth: 1
@@ -903,6 +949,7 @@ 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 }})
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
strategy:
@@ -915,6 +962,7 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
@@ -961,6 +1009,7 @@ jobs:
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_CODEX_NPM_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ matrix.group.published_upgrade_survivor_baselines || inputs.published_upgrade_survivor_baselines }}
@@ -972,28 +1021,28 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
- name: Checkout trusted release harness
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.sha }}
fetch-depth: 1
path: .release-harness
- name: Log in to GHCR for shared Docker E2E image
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- 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: Hydrate live auth/profile inputs
@@ -1056,15 +1105,35 @@ jobs:
run: |
set -euo pipefail
credentials=",$CREDENTIALS,"
if [[ "$credentials" == *",openai,"* ]]; then
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
fi
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
require_any() {
local label="$1"
shift
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
return 0
fi
done
echo "Missing credential for ${label}: expected one of $*" >&2
exit 1
}
if [[ "$credentials" == *",openai,"* ]]; then
require_any OpenAI OPENAI_API_KEY
fi
if [[ "$credentials" == *",codex,"* ]]; then
require_any Codex OPENCLAW_CODEX_AUTH_JSON
fi
if [[ "$credentials" == *",anthropic,"* ]]; then
require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON
fi
if [[ "$credentials" == *",factory,"* ]]; then
require_any Factory FACTORY_API_KEY
fi
if [[ "$credentials" == *",gemini,"* ]]; then
require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON
fi
if [[ "$credentials" == *",opencode,"* ]]; then
require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY
fi
- name: Run targeted Docker E2E lanes
@@ -1112,6 +1181,7 @@ 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)
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
env:
@@ -1139,17 +1209,15 @@ jobs:
path: .release-harness
- name: Log in to GHCR for shared Docker E2E image
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- 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 Open WebUI credentials
@@ -1239,6 +1307,7 @@ jobs:
prepare_docker_e2e_image:
needs: validate_selected_ref
if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != ''
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:
@@ -1308,7 +1377,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Download current-run OpenClaw Docker E2E package
@@ -1399,11 +1467,10 @@ jobs:
- name: Log in to GHCR
if: steps.plan.outputs.needs_e2e_image == '1'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Check existing shared Docker E2E images
id: image_exists
@@ -1483,6 +1550,7 @@ 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'))
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
permissions:
@@ -1513,11 +1581,10 @@ jobs:
echo "Shared live-test image: \`${live_image}\`" >> "$GITHUB_STEP_SUMMARY"
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Check existing shared live-test image
id: image_exists
@@ -1556,6 +1623,7 @@ 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')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
strategy:
@@ -1650,7 +1718,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Hydrate live auth/profile inputs
@@ -1659,11 +1726,10 @@ jobs:
- name: Log in to GHCR
if: contains(matrix.profiles, inputs.release_test_profile)
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Validate provider credential
if: contains(matrix.profiles, inputs.release_test_profile)
@@ -1708,6 +1774,7 @@ 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')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
env:
@@ -1768,7 +1835,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Normalize provider allowlist
@@ -1834,11 +1900,10 @@ jobs:
run: bash scripts/ci-hydrate-live-auth.sh
- name: Log in to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Validate provider credentials
shell: bash
@@ -1883,6 +1948,7 @@ 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'))
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:
@@ -1911,7 +1977,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
@@ -2137,7 +2203,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Hydrate live auth/profile inputs
@@ -2204,6 +2269,7 @@ 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-'))
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:
@@ -2212,49 +2278,49 @@ jobs:
include:
- suite_id: live-gateway-docker
label: Docker live gateway OpenAI
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=300000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: beta minimum stable full
- suite_id: live-gateway-anthropic-docker
label: Docker live gateway Anthropic
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full
- suite_id: live-gateway-google-docker
label: Docker live gateway Google
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full
- suite_id: live-gateway-minimax-docker
label: Docker live gateway MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full
- suite_id: live-gateway-advisory-docker-deepseek-fireworks
suite_group: live-gateway-advisory-docker
label: Docker live gateway advisory DeepSeek/Fireworks
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
advisory: true
profiles: full
- suite_id: live-gateway-advisory-docker-opencode-openrouter
suite_group: live-gateway-advisory-docker
label: Docker live gateway advisory OpenCode/OpenRouter
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
advisory: true
profiles: full
- suite_id: live-gateway-advisory-docker-xai-zai
suite_group: live-gateway-advisory-docker
label: Docker live gateway advisory xAI/Z.ai
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
advisory: true
profiles: full
@@ -2354,7 +2420,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Hydrate live auth/profile inputs
@@ -2363,11 +2428,10 @@ jobs:
- name: Log in to GHCR
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Configure suite-specific env
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
@@ -2423,6 +2487,7 @@ 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')
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
@@ -2572,7 +2637,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Hydrate live auth/profile inputs

View File

@@ -20,6 +20,10 @@ on:
description: Successful Full Release Validation run id for this tag/SHA, required for real publish
required: false
type: string
release_publish_run_id:
description: Approved OpenClaw Release Publish workflow run id
required: false
type: string
npm_dist_tag:
description: npm dist-tag to publish to
required: true
@@ -31,13 +35,12 @@ on:
- latest
concurrency:
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', inputs.tag, inputs.npm_dist_tag) || github.ref }}
cancel-in-progress: false
group: ${{ github.event_name == 'workflow_dispatch' && inputs.preflight_only && format('openclaw-npm-release-{0}-{1}-preflight', inputs.tag, inputs.npm_dist_tag) || github.event_name == 'workflow_dispatch' && format('openclaw-npm-release-{0}-{1}-publish-{2}', inputs.tag, inputs.npm_dist_tag, github.run_id) || format('openclaw-npm-release-{0}', github.ref) }}
cancel-in-progress: ${{ github.event_name == 'workflow_dispatch' && inputs.preflight_only && inputs.npm_dist_tag == 'alpha' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
jobs:
# PLEASE DON'T ADD LONG-RUNNING OR FLAKY CHECKS TO THE npm RELEASE PATH.
@@ -88,11 +91,32 @@ 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:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Ensure version is not already published
@@ -191,7 +215,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: |
@@ -259,6 +283,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"
@@ -290,6 +319,7 @@ jobs:
);
NODE
echo "dir=$ARTIFACT_DIR" >> "$GITHUB_OUTPUT"
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
- name: Verify prepared npm tarball install
env:
@@ -312,6 +342,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:
@@ -319,26 +357,44 @@ 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:
actions: read
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:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
run: |
set -euo pipefail
if [[ -z "${PREFLIGHT_RUN_ID}" ]]; then
@@ -346,10 +402,40 @@ jobs:
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
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" == "beta" ]]; then
echo "::warning::Beta publish is proceeding from npm preflight only; full release validation remains required before stable/latest promotion."
else
echo "Real publish requires full_release_validation_run_id from a successful Full Release Validation run." >&2
exit 1
fi
fi
if [[ -z "${RELEASE_PUBLISH_RUN_ID// }" && "${GITHUB_ACTOR}" == "github-actions[bot]" ]]; then
echo "Workflow-dispatched real publish requires release_publish_run_id from the approved OpenClaw Release Publish workflow." >&2
exit 1
fi
- name: Validate release publish approval run
env:
GH_TOKEN: ${{ github.token }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
EXPECTED_WORKFLOW_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
if [[ -z "${RELEASE_PUBLISH_RUN_ID// }" ]]; then
if [[ "${GITHUB_ACTOR}" == "github-actions[bot]" ]]; then
echo "OpenClaw npm publish dispatched by another workflow must include release_publish_run_id." >&2
exit 1
fi
echo "Direct OpenClaw npm publish; relying on this workflow's npm-release environment approval."
exit 0
fi
if [[ "${GITHUB_ACTOR}" != "github-actions[bot]" ]]; then
echo "OpenClaw npm publish must be dispatched by the OpenClaw Release Publish workflow, not directly by ${GITHUB_ACTOR}." >&2
exit 1
fi
RUN_JSON="$(gh run view "$RELEASE_PUBLISH_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", "OpenClaw Release Publish"], ["headBranch", process.env.EXPECTED_WORKFLOW_BRANCH], ["event", "workflow_dispatch"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } if (run.status !== "in_progress") { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} must still be in_progress, got ${run.status ?? "<missing>"}.`); process.exit(1); } if (run.conclusion) { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} already concluded ${run.conclusion}.`); process.exit(1); } console.log(`Using release publish approval run ${process.env.RELEASE_PUBLISH_RUN_ID}: ${run.url}`);'
publish_openclaw_npm:
# KEEP THE REAL RELEASE/PUBLISH PATH ON A GITHUB-HOSTED RUNNER.
# npm trusted publishing + provenance requires this to stay on ubuntu-latest.
@@ -387,11 +473,32 @@ 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:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Ensure version is not already published
@@ -410,32 +517,76 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
EXPECTED_PREFLIGHT_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
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}`);'
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"], ["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} from ${run.headBranch}: ${run.url}`);'
- name: Verify full release validation run metadata
if: ${{ inputs.full_release_validation_run_id != '' }}
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}`);'
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"], ["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} from ${run.headBranch}: ${run.url}`);'
- name: Download prepared npm tarball
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: preflight-tarball
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
github-token: ${{ github.token }}
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
download_named_artifact() {
local artifact_name="$1"
for attempt in 1 2 3; do
if gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "${artifact_name}" \
--dir preflight-tarball; then
return 0
fi
if [[ "$attempt" != "3" ]]; then
echo "::warning::Artifact download for ${artifact_name} failed on attempt ${attempt}; retrying."
sleep $((attempt * 10))
fi
done
return 1
}
if download_named_artifact "${preferred_name}"; 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]}"
download_named_artifact "${fallback_name}"
echo "Downloaded fallback preflight artifact ${fallback_name}."
}
download_preflight_artifact
- name: Download full release validation manifest
if: ${{ inputs.full_release_validation_run_id != '' }}
uses: actions/download-artifact@v8
with:
name: full-release-validation-${{ inputs.full_release_validation_run_id }}
@@ -501,6 +652,7 @@ jobs:
fi
- name: Verify full release validation target
if: ${{ inputs.full_release_validation_run_id != '' }}
run: |
set -euo pipefail
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"

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
@@ -468,7 +468,7 @@ jobs:
- name: Upload Kova artifacts
if: ${{ always() && steps.lane.outputs.run == 'true' }}
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: openclaw-performance-${{ matrix.lane }}-${{ github.run_id }}-${{ github.run_attempt }}
path: |
@@ -561,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

@@ -78,15 +78,19 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for live Docker package checks; blank derives from release_package_spec or packs the selected ref
required: false
default: ""
type: string
concurrency:
group: openclaw-release-checks-${{ inputs.expected_sha || inputs.ref }}-${{ inputs.rerun_group }}
cancel-in-progress: false
cancel-in-progress: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL || 'openai/gpt-5.5' }}
jobs:
@@ -112,14 +116,23 @@ jobs:
qa_live_slack_enabled: ${{ steps.inputs.outputs.qa_live_slack_enabled }}
release_package_spec: ${{ steps.inputs.outputs.release_package_spec }}
package_acceptance_package_spec: ${{ steps.inputs.outputs.package_acceptance_package_spec }}
codex_plugin_spec: ${{ steps.inputs.outputs.codex_plugin_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
@@ -178,11 +191,21 @@ jobs:
working-directory: source
env:
RELEASE_REF: ${{ inputs.ref }}
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
SELECTED_SHA="$(git rev-parse HEAD)"
git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
git fetch --tags origin '+refs/tags/*:refs/tags/*'
git_fetch_with_checkout_auth() {
if git config --get-all http.https://github.com/.extraheader >/dev/null; then
git fetch "$@"
return
fi
local auth_header
auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')"
git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" fetch "$@"
}
git_fetch_with_checkout_auth --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
git_fetch_with_checkout_auth --tags origin '+refs/tags/*:refs/tags/*'
if git tag --points-at "${SELECTED_SHA}" | grep -Eq '^v'; then
exit 0
@@ -219,6 +242,35 @@ 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 }}
GITHUB_TOKEN: ${{ github.token }}
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_with_checkout_auth() {
if git config --get-all http.https://github.com/.extraheader >/dev/null; then
git fetch "$@"
return
fi
local auth_header
auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')"
git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" fetch "$@"
}
git_fetch_with_checkout_auth --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:
@@ -235,6 +287,7 @@ jobs:
RELEASE_QA_SLACK_LIVE_CI_ENABLED: ${{ vars.OPENCLAW_RELEASE_QA_SLACK_LIVE_CI_ENABLED || 'false' }}
RELEASE_PACKAGE_SPEC_INPUT: ${{ inputs.release_package_spec }}
RELEASE_PACKAGE_ACCEPTANCE_PACKAGE_SPEC_INPUT: ${{ inputs.package_acceptance_package_spec }}
RELEASE_CODEX_PLUGIN_SPEC_INPUT: ${{ inputs.codex_plugin_spec }}
run: |
set -euo pipefail
qa_live_matrix_enabled=true
@@ -280,6 +333,10 @@ jobs:
if [[ "$release_profile" == "full" ]]; then
run_release_soak=true
fi
codex_plugin_spec="$RELEASE_CODEX_PLUGIN_SPEC_INPUT"
if [[ -z "${codex_plugin_spec// }" && "$RELEASE_PACKAGE_SPEC_INPUT" =~ ^openclaw@(.+)$ ]]; then
codex_plugin_spec="npm:@openclaw/codex@${BASH_REMATCH[1]}"
fi
filter="$(printf '%s' "$RELEASE_LIVE_SUITE_FILTER_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ -n "${filter// }" ]]; then
@@ -360,6 +417,7 @@ jobs:
printf 'qa_live_slack_enabled=%s\n' "$qa_live_slack_enabled"
printf 'release_package_spec=%s\n' "$RELEASE_PACKAGE_SPEC_INPUT"
printf 'package_acceptance_package_spec=%s\n' "$RELEASE_PACKAGE_ACCEPTANCE_PACKAGE_SPEC_INPUT"
printf 'codex_plugin_spec=%s\n' "$codex_plugin_spec"
} >> "$GITHUB_OUTPUT"
- name: Summarize validated ref
@@ -376,6 +434,7 @@ jobs:
RELEASE_CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ steps.inputs.outputs.codex_plugin_spec }}
run: |
{
echo "## Release checks"
@@ -405,6 +464,11 @@ jobs:
else
echo "- Package Acceptance package spec: prepared release artifact"
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
else
echo "- Codex plugin spec: packed from selected ref"
fi
if [[ "$RUN_RELEASE_SOAK" == "true" ]]; then
echo "- This run will execute blocking release validation plus exhaustive live/Docker soak coverage."
else
@@ -430,7 +494,7 @@ jobs:
- name: Checkout trusted workflow ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ github.ref_name }}
fetch-depth: 0
@@ -442,7 +506,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "false"
@@ -507,6 +570,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 +579,7 @@ 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
@@ -538,6 +602,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
@@ -551,6 +616,7 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
@@ -603,6 +669,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
@@ -610,6 +677,7 @@ jobs:
include_live_suites: false
release_test_profile: ${{ needs.resolve_target.outputs.release_profile }}
package_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
codex_plugin_spec: ${{ needs.resolve_target.outputs.codex_plugin_spec }}
secrets: *live_e2e_release_secrets
package_acceptance_release_checks:
@@ -623,23 +691,25 @@ jobs:
pull-requests: read
uses: ./.github/workflows/package-acceptance.yml
with:
advisory: false
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' }}
artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
package_sha256: ${{ (needs.resolve_target.outputs.package_acceptance_package_spec == '' && needs.resolve_target.outputs.release_package_spec == '') && needs.prepare_release_package.outputs.package_sha256 || '' }}
suite_profile: custom
docker_lanes: doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update
docker_lanes: doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update plugin-binding-command-escape
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 }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
@@ -697,9 +767,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"
@@ -715,7 +785,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -723,7 +793,6 @@ jobs:
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
@@ -745,7 +814,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
@@ -763,7 +832,7 @@ jobs:
- name: Upload parity lane artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-parity-${{ matrix.lane }}-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -787,7 +856,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -795,11 +864,10 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Download parity lane artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
pattern: release-qa-parity-*-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -814,21 +882,194 @@ 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
- name: Upload parity artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-parity-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
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: true
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 }}
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: Run soak runtime parity tier
id: runtime_parity_soak_lane
if: ${{ always() && needs.resolve_target.outputs.run_release_soak == 'true' && 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 soak \
--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-soak"
- 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: Generate soak runtime parity report
if: ${{ always() && needs.resolve_target.outputs.run_release_soak == 'true' && steps.runtime_parity_soak_lane.outcome != 'skipped' && steps.runtime_parity_soak_lane.outcome != 'cancelled' }}
run: |
set -euo pipefail
summary=".artifacts/qa-e2e/runtime-parity-soak/qa-suite-summary.json"
if [[ ! -f "$summary" ]]; then
echo "No soak runtime parity summary was produced."
exit 0
fi
pnpm openclaw qa parity-report \
--repo-root . \
--runtime-axis \
--summary "$summary" \
--output-dir .artifacts/qa-e2e/runtime-parity-soak-report
- name: Upload runtime parity artifacts
if: always()
uses: actions/upload-artifact@v7
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: true
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 }}
install-bun: "true"
- name: Download runtime parity artifacts
uses: actions/download-artifact@v8
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@v7
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]
@@ -847,7 +1088,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -855,7 +1096,6 @@ jobs:
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
@@ -902,7 +1142,7 @@ jobs:
- name: Upload Matrix QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-live-matrix-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -927,7 +1167,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -935,7 +1175,6 @@ jobs:
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
@@ -998,7 +1237,7 @@ jobs:
- name: Upload Telegram QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-live-telegram-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -1023,7 +1262,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -1031,7 +1270,6 @@ jobs:
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
@@ -1094,7 +1332,7 @@ jobs:
- name: Upload Discord QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-live-discord-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -1108,6 +1346,9 @@ jobs:
continue-on-error: true
runs-on: ubuntu-24.04
timeout-minutes: 60
concurrency:
group: qa-live-whatsapp-shared
cancel-in-progress: false
permissions:
contents: read
pull-requests: read
@@ -1119,7 +1360,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -1127,7 +1368,6 @@ jobs:
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
@@ -1190,7 +1430,7 @@ jobs:
- name: Upload WhatsApp QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-live-whatsapp-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -1215,7 +1455,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
persist-credentials: true
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
@@ -1223,7 +1463,6 @@ jobs:
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
@@ -1286,7 +1525,7 @@ jobs:
- name: Upload Slack QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: release-qa-live-slack-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
@@ -1304,6 +1543,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
@@ -1316,9 +1557,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 }}" \
@@ -1328,6 +1575,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 }}" \
@@ -1337,6 +1586,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|package_acceptance_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

@@ -15,6 +15,10 @@ on:
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
@@ -67,7 +71,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
jobs:
resolve_release_target:
@@ -76,6 +79,7 @@ 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:
@@ -110,8 +114,12 @@ jobs:
echo "publish_openclaw_npm=true requires full_release_validation_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
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
@@ -131,14 +139,55 @@ jobs:
esac
- name: Download OpenClaw npm preflight manifest
id: preflight_artifact
if: ${{ inputs.publish_openclaw_npm }}
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: ${{ runner.temp }}/openclaw-npm-preflight-manifest
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
github-token: ${{ github.token }}
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}"
download_named_artifact() {
local artifact_name="$1"
for attempt in 1 2 3; do
if gh run download "${PREFLIGHT_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name "${artifact_name}" \
--dir "${preflight_dir}"; then
return 0
fi
if [[ "$attempt" != "3" ]]; then
echo "::warning::Artifact download for ${artifact_name} failed on attempt ${attempt}; retrying."
sleep $((attempt * 10))
fi
done
return 1
}
if download_named_artifact "${preferred_name}"; 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]}"
download_named_artifact "${fallback_name}"
echo "name=${fallback_name}" >> "$GITHUB_OUTPUT"
- name: Download full release validation manifest
if: ${{ inputs.publish_openclaw_npm }}
@@ -245,7 +294,10 @@ jobs:
exit 1
fi
- name: Validate release tag is reachable from main or release branch
- 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 \
@@ -259,7 +311,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
@@ -285,6 +347,7 @@ jobs:
needs: [resolve_release_target]
runs-on: ubuntu-latest
timeout-minutes: 60
environment: npm-release
steps:
- name: Checkout release SHA
uses: actions/checkout@v6
@@ -293,6 +356,11 @@ jobs:
fetch-depth: 1
persist-credentials: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Dispatch publish workflows
env:
GH_TOKEN: ${{ github.token }}
@@ -306,6 +374,9 @@ jobs:
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
@@ -314,7 +385,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
@@ -327,8 +401,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
@@ -349,6 +425,75 @@ 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 1
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 child release gate after parent release approval" >/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"
return 0
fi
return 1
}
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"
@@ -366,6 +511,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}" || true
last_state="$state"
fi
sleep 30
@@ -393,7 +540,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
}
@@ -412,6 +559,85 @@ jobs:
wait_run_pid="$!"
}
wait_for_job_success() {
local workflow="$1"
local run_id="$2"
local job_name="$3"
local jobs_json job_json run_status run_conclusion status conclusion url deadline
deadline=$((SECONDS + 900))
while true; do
jobs_json="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json status,conclusion,jobs)"
run_status="$(printf '%s' "$jobs_json" | jq -r '.status')"
run_conclusion="$(printf '%s' "$jobs_json" | jq -r '.conclusion // ""')"
job_json="$(printf '%s' "$jobs_json" | jq -c --arg name "$job_name" '.jobs[]? | select(.name == $name) | {status, conclusion, url}' | head -n 1)"
if [[ -n "$job_json" ]]; then
status="$(printf '%s' "$job_json" | jq -r '.status')"
conclusion="$(printf '%s' "$job_json" | jq -r '.conclusion // ""')"
url="$(printf '%s' "$job_json" | jq -r '.url // ""')"
if [[ "$status" == "completed" ]]; then
if [[ "$conclusion" == "success" || "$conclusion" == "skipped" ]]; then
echo "${workflow} ${job_name} ${conclusion}: ${url}"
echo "- ${workflow} ${job_name}: ${conclusion} (${url})" >> "$GITHUB_STEP_SUMMARY"
return 0
fi
echo "${workflow} ${job_name} failed: ${conclusion} ${url}" >&2
print_failed_run_summary "${run_id}"
return 1
fi
echo "${workflow} ${job_name} still ${status}: ${url}"
elif [[ "$run_status" == "completed" ]]; then
if [[ "$run_conclusion" == "success" ]]; then
echo "${workflow} completed before ${job_name} was needed."
echo "- ${workflow} ${job_name}: not needed" >> "$GITHUB_STEP_SUMMARY"
return 0
fi
echo "${workflow} completed before ${job_name} with ${run_conclusion}." >&2
print_failed_run_summary "${run_id}"
return 1
else
echo "${workflow} waiting for ${job_name} to start: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fi
if (( SECONDS >= deadline )); then
echo "${workflow} ${job_name} did not complete within 15 minutes." >&2
return 1
fi
sleep 10
done
}
approve_child_publish_environment() {
local workflow="$1"
local run_id="$2"
local run_json status conclusion deadline
deadline=$((SECONDS + 900))
while true; do
if approve_pending_deployments "${workflow}" "${run_id}"; then
echo "- ${workflow}: child environment gate approved" >> "$GITHUB_STEP_SUMMARY"
return 0
fi
run_json="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json status,conclusion,url)"
status="$(printf '%s' "$run_json" | jq -r '.status')"
conclusion="$(printf '%s' "$run_json" | jq -r '.conclusion // ""')"
if [[ "$status" == "completed" ]]; then
if [[ "$conclusion" == "success" ]]; then
echo "${workflow}: completed before child environment approval was needed"
return 0
fi
echo "${workflow}: completed before child environment approval with ${conclusion}" >&2
print_failed_run_summary "${run_id}"
return 1
fi
if (( SECONDS >= deadline )); then
echo "${workflow}: child environment approval was not available within 15 minutes." >&2
print_pending_deployments "${workflow}" "${run_id}"
return 1
fi
sleep 10
done
}
create_or_update_github_release() {
local release_version notes_version title notes_file changelog_file latest_arg prerelease_args
release_version="${RELEASE_TAG#v}"
@@ -466,17 +692,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
@@ -492,15 +719,125 @@ 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}"
--openclaw-npm-run "${openclaw_npm_run_id}"
--evidence-out "${evidence_path}"
)
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
verify_args+=(--plugin-clawhub-run "${plugin_clawhub_run_id}")
else
verify_args+=(--skip-clawhub)
fi
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"
}
append_release_proof_to_github_release() {
local release_version body_file notes_file tarball integrity telegram_line clawhub_line
release_version="${RELEASE_TAG#v}"
body_file="${RUNNER_TEMP}/release-body.md"
notes_file="${RUNNER_TEMP}/release-notes-with-proof.md"
tarball="$(npm view "openclaw@${release_version}" dist.tarball --json | jq -r '.')"
integrity="$(npm view "openclaw@${release_version}" dist.integrity --json | jq -r '.')"
gh release view "${RELEASE_TAG}" --repo "$GITHUB_REPOSITORY" --json body --jq .body > "${body_file}"
if [[ -n "${NPM_TELEGRAM_RUN_ID// }" ]]; then
telegram_line="- npm Telegram beta E2E: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${NPM_TELEGRAM_RUN_ID}"
else
telegram_line="- npm Telegram beta E2E: not supplied"
fi
if [[ "${WAIT_FOR_CLAWHUB}" == "true" ]]; then
clawhub_line="- plugin ClawHub publish: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${plugin_clawhub_run_id}"
else
clawhub_line="- plugin ClawHub publish: dispatched separately, not awaited by this proof: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${plugin_clawhub_run_id}"
fi
RELEASE_BODY_FILE="${body_file}" \
RELEASE_NOTES_FILE="${notes_file}" \
RELEASE_VERSION="${release_version}" \
RELEASE_TAG="${RELEASE_TAG}" \
RELEASE_REPO="${GITHUB_REPOSITORY}" \
RELEASE_TARBALL="${tarball}" \
RELEASE_INTEGRITY="${integrity}" \
RELEASE_PUBLISH_RUN_ID="${GITHUB_RUN_ID}" \
PREFLIGHT_RUN_ID="${PREFLIGHT_RUN_ID}" \
FULL_RELEASE_VALIDATION_RUN_ID="${FULL_RELEASE_VALIDATION_RUN_ID}" \
PLUGIN_NPM_RUN_ID="${plugin_npm_run_id}" \
OPENCLAW_NPM_RUN_ID="${openclaw_npm_run_id}" \
CLAWHUB_LINE="${clawhub_line}" \
TELEGRAM_LINE="${telegram_line}" \
node --input-type=module <<'NODE'
import { readFileSync, writeFileSync } from "node:fs";
const bodyFile = process.env.RELEASE_BODY_FILE;
const notesFile = process.env.RELEASE_NOTES_FILE;
if (!bodyFile || !notesFile) {
throw new Error("Missing release notes file paths.");
}
const body = readFileSync(bodyFile, "utf8").trimEnd();
const section = [
"### Release verification",
"",
`- npm package: https://www.npmjs.com/package/openclaw/v/${process.env.RELEASE_VERSION}`,
`- registry tarball: ${process.env.RELEASE_TARBALL}`,
`- integrity: \`${process.env.RELEASE_INTEGRITY}\``,
`- full release CI report: https://github.com/openclaw/releases-private/blob/main/evidence/${process.env.RELEASE_VERSION}/release-evidence.md`,
`- release publish: https://github.com/${process.env.RELEASE_REPO}/actions/runs/${process.env.RELEASE_PUBLISH_RUN_ID}`,
`- npm preflight: https://github.com/${process.env.RELEASE_REPO}/actions/runs/${process.env.PREFLIGHT_RUN_ID}`,
`- full release validation: https://github.com/${process.env.RELEASE_REPO}/actions/runs/${process.env.FULL_RELEASE_VALIDATION_RUN_ID}`,
`- plugin npm publish: https://github.com/${process.env.RELEASE_REPO}/actions/runs/${process.env.PLUGIN_NPM_RUN_ID}`,
process.env.CLAWHUB_LINE,
`- OpenClaw npm publish: https://github.com/${process.env.RELEASE_REPO}/actions/runs/${process.env.OPENCLAW_NPM_RUN_ID}`,
process.env.TELEGRAM_LINE,
].join("\n");
const withoutOldProof = body.replace(/\n?### Release verification\n[\s\S]*?(?=\n### |\n## |$)/, "");
writeFileSync(notesFile, `${withoutOldProof.trimEnd()}\n\n${section}\n`);
NODE
gh release edit "${RELEASE_TAG}" --repo "$GITHUB_REPOSITORY" --notes-file "${notes_file}"
echo "- Release proof: appended to GitHub release" >> "$GITHUB_STEP_SUMMARY"
}
{
echo "### Publish sequence"
echo
echo "- Workflow ref: \`${CHILD_WORKFLOW_REF}\`"
echo "- Release tag: \`${RELEASE_TAG}\`"
echo "- Release SHA: \`${TARGET_SHA}\`"
echo "- Release approval: this workflow job"
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"
else
echo "- OpenClaw npm publish: skipped by input"
fi
@@ -511,8 +848,8 @@ jobs:
fi
} >> "$GITHUB_STEP_SUMMARY"
npm_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
clawhub_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
npm_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}" -f release_publish_run_id="${GITHUB_RUN_ID}")
clawhub_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}" -f release_publish_run_id="${GITHUB_RUN_ID}")
if [[ -n "${PLUGINS}" ]]; then
npm_args+=(-f plugins="${PLUGINS}")
clawhub_args+=(-f plugins="${PLUGINS}")
@@ -538,6 +875,7 @@ jobs:
-f preflight_only=false \
-f preflight_run_id="${PREFLIGHT_RUN_ID}" \
-f full_release_validation_run_id="${FULL_RELEASE_VALIDATION_RUN_ID}" \
-f release_publish_run_id="${GITHUB_RUN_ID}" \
-f npm_dist_tag="${RELEASE_NPM_DIST_TAG}")"
echo "- OpenClaw npm run ID: \`${openclaw_npm_run_id}\`" >> "$GITHUB_STEP_SUMMARY"
else
@@ -552,7 +890,13 @@ jobs:
wait_for_run_background plugin-clawhub-release.yml "${plugin_clawhub_run_id}" "${clawhub_result}"
clawhub_pid="${wait_run_pid}"
else
echo "- plugin-clawhub-release.yml: not awaited (${plugin_clawhub_run_id})" >> "$GITHUB_STEP_SUMMARY"
wait_for_job_success plugin-clawhub-release.yml "${plugin_clawhub_run_id}" "Validate release publish approval"
if approve_child_publish_environment plugin-clawhub-release.yml "${plugin_clawhub_run_id}"; then
:
else
echo "- plugin-clawhub-release.yml: child environment gate not ready; publish was left dispatched (${plugin_clawhub_run_id})" >> "$GITHUB_STEP_SUMMARY"
fi
echo "- plugin-clawhub-release.yml: publish not awaited (${plugin_clawhub_run_id})" >> "$GITHUB_STEP_SUMMARY"
fi
openclaw_result=""
@@ -565,23 +909,40 @@ 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
append_release_proof_to_github_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

@@ -38,6 +38,7 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}

View File

@@ -62,7 +62,7 @@ jobs:
- name: Upload SARIF as workflow artifact
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: opengrep-full-sarif
path: .opengrep-out/precise.sarif

View File

@@ -92,7 +92,7 @@ jobs:
- name: Upload SARIF as workflow artifact
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: opengrep-pr-diff-sarif
path: .opengrep-out/precise.sarif

View File

@@ -17,6 +17,7 @@ on:
- npm
- ref
- url
- trusted-url
- artifact
package_ref:
description: Trusted package source ref when source=ref
@@ -29,12 +30,17 @@ on:
default: openclaw@beta
type: string
package_url:
description: HTTPS .tgz URL when source=url
description: HTTPS .tgz URL when source=url or source=trusted-url
required: false
default: ""
type: string
package_sha256:
description: Expected package SHA-256; required for source=url
description: Expected package SHA-256; required for source=url or source=trusted-url
required: false
default: ""
type: string
trusted_source_id:
description: Named trusted source policy when source=trusted-url
required: false
default: ""
type: string
@@ -93,15 +99,25 @@ 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
default: main
type: string
source:
description: "Package candidate source: npm, ref, url, or artifact"
description: "Package candidate source: npm, ref, url, trusted-url, or artifact"
required: true
type: string
package_ref:
@@ -115,12 +131,17 @@ on:
default: openclaw@beta
type: string
package_url:
description: HTTPS .tgz URL when source=url
description: HTTPS .tgz URL when source=url or source=trusted-url
required: false
default: ""
type: string
package_sha256:
description: Expected package SHA-256; required for source=url
description: Expected package SHA-256; required for source=url or source=trusted-url
required: false
default: ""
type: string
trusted_source_id:
description: Named trusted source policy when source=trusted-url
required: false
default: ""
type: string
@@ -170,6 +191,8 @@ on:
default: ""
type: string
secrets:
OPENCLAW_TRUSTED_PACKAGE_TOKEN:
required: false
OPENAI_API_KEY:
required: false
OPENAI_BASE_URL:
@@ -180,6 +203,8 @@ on:
required: false
ANTHROPIC_API_TOKEN:
required: false
FACTORY_API_KEY:
required: false
BYTEPLUS_API_KEY:
required: false
CEREBRAS_API_KEY:
@@ -278,7 +303,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
PACKAGE_ARTIFACT_NAME: package-under-test
jobs:
@@ -310,7 +334,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: ${{ inputs.source == 'ref' && 'true' || 'false' }}
install-deps: "false"
@@ -345,6 +368,8 @@ jobs:
PACKAGE_SPEC: ${{ inputs.package_spec }}
PACKAGE_URL: ${{ inputs.package_url }}
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
TRUSTED_SOURCE_ID: ${{ inputs.trusted_source_id }}
OPENCLAW_TRUSTED_PACKAGE_TOKEN: ${{ secrets.OPENCLAW_TRUSTED_PACKAGE_TOKEN }}
shell: bash
run: |
set -euo pipefail
@@ -359,6 +384,7 @@ jobs:
--package-spec "$PACKAGE_SPEC" \
--package-url "$PACKAGE_URL" \
--package-sha256 "$PACKAGE_SHA256" \
--trusted-source-id "$TRUSTED_SOURCE_ID" \
--artifact-dir "${artifact_dir:-.}" \
--output-dir .artifacts/docker-e2e-package \
--output-name openclaw-current.tgz \
@@ -480,6 +506,7 @@ jobs:
PACKAGE_SHA256: ${{ steps.resolve.outputs.sha256 }}
PACKAGE_VERSION: ${{ steps.resolve.outputs.package_version }}
PACKAGE_REF: ${{ inputs.package_ref }}
TRUSTED_SOURCE_ID: ${{ inputs.trusted_source_id }}
SOURCE: ${{ inputs.source }}
SUITE_PROFILE: ${{ inputs.suite_profile }}
WORKFLOW_REF: ${{ inputs.workflow_ref }}
@@ -496,6 +523,9 @@ jobs:
if [[ "${SOURCE}" == "ref" ]]; then
echo "- Package ref: \`${PACKAGE_REF}\`"
fi
if [[ "${SOURCE}" == "trusted-url" ]]; then
echo "- Trusted source: \`${TRUSTED_SOURCE_ID}\`"
fi
echo "- Version: \`${PACKAGE_VERSION}\`"
echo "- SHA-256: \`${PACKAGE_SHA256}\`"
echo "- Profile: \`${SUITE_PROFILE}\`"
@@ -504,11 +534,43 @@ jobs:
echo "- Published upgrade survivor scenarios: \`${PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS}\`"
} >> "$GITHUB_STEP_SUMMARY"
package_integrity:
name: Package integrity
needs: resolve_package
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout package workflow ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.workflow_ref }}
fetch-depth: 1
- name: Download package-under-test artifact
uses: actions/download-artifact@v8
with:
name: ${{ needs.resolve_package.outputs.package_artifact_name }}
path: .artifacts/docker-e2e-package
- name: Enforce public package integrity
env:
OPENCLAW_PACKAGE_TARBALL_CHECK_TIMINGS: "0"
shell: bash
run: |
set -euo pipefail
node scripts/check-openclaw-package-tarball.mjs .artifacts/docker-e2e-package/openclaw-current.tgz
docker_acceptance:
name: Docker product acceptance
needs: resolve_package
needs: [resolve_package, package_integrity]
permissions:
actions: read
contents: read
packages: write
pull-requests: read
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' }}
@@ -526,6 +588,7 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
@@ -569,10 +632,11 @@ jobs:
package_telegram:
name: Telegram package acceptance
needs: resolve_package
needs: [resolve_package, package_integrity]
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 }}
@@ -586,7 +650,7 @@ jobs:
summary:
name: Verify package acceptance
needs: [resolve_package, docker_acceptance, package_telegram]
needs: [resolve_package, package_integrity, docker_acceptance, package_telegram]
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
@@ -594,20 +658,27 @@ jobs:
- name: Verify package acceptance results
env:
DOCKER_RESULT: ${{ needs.docker_acceptance.result }}
PACKAGE_INTEGRITY_RESULT: ${{ needs.package_integrity.result }}
PACKAGE_TELEGRAM_RESULT: ${{ needs.package_telegram.result }}
RESOLVE_RESULT: ${{ needs.resolve_package.result }}
shell: bash
run: |
set -euo pipefail
advisory="${{ inputs.advisory }}"
failed=0
for item in \
"resolve_package=${RESOLVE_RESULT}" \
"package_integrity=${PACKAGE_INTEGRITY_RESULT}" \
"docker_acceptance=${DOCKER_RESULT}" \
"package_telegram=${PACKAGE_TELEGRAM_RESULT}"
do
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,10 +16,14 @@ 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
release_publish_run_id:
description: Approved OpenClaw Release Publish workflow run id
required: false
type: string
concurrency:
group: plugin-clawhub-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
@@ -28,7 +32,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
CLAWHUB_REGISTRY: "https://clawhub.ai"
CLAWHUB_REPOSITORY: "openclaw/clawhub"
# Pinned to a reviewed ClawHub commit so release behavior stays reproducible.
@@ -57,7 +60,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Resolve checked-out ref
@@ -82,7 +84,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 +97,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,12 +179,56 @@ 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:
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
run: node --import tsx scripts/plugin-clawhub-owner-preflight.ts .local/plugin-clawhub-release-plan.json
validate_release_publish_approval:
name: Validate release publish approval
needs: preview_plugins_clawhub
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- name: Validate release publish approval run
env:
GH_TOKEN: ${{ github.token }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
EXPECTED_WORKFLOW_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
if [[ -z "${RELEASE_PUBLISH_RUN_ID// }" ]]; then
if [[ "${GITHUB_ACTOR}" == "github-actions[bot]" ]]; then
echo "Plugin ClawHub publish dispatched by another workflow must include release_publish_run_id." >&2
exit 1
fi
echo "Direct Plugin ClawHub Release dispatch; relying on this workflow's clawhub-plugin-release environment approval."
exit 0
fi
if [[ "${GITHUB_ACTOR}" != "github-actions[bot]" ]]; then
echo "Plugin ClawHub publish must be dispatched by the OpenClaw Release Publish workflow, not directly by ${GITHUB_ACTOR}." >&2
exit 1
fi
RUN_JSON="$(gh run view "$RELEASE_PUBLISH_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", "OpenClaw Release Publish"], ["headBranch", process.env.EXPECTED_WORKFLOW_BRANCH], ["event", "workflow_dispatch"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } if (run.status !== "in_progress") { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} must still be in_progress, got ${run.status ?? "<missing>"}.`); process.exit(1); } if (run.conclusion) { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} already concluded ${run.conclusion}.`); process.exit(1); } console.log(`Using release publish approval run ${process.env.RELEASE_PUBLISH_RUN_ID}: ${run.url}`);'
preview_plugin_pack:
needs: preview_plugins_clawhub
if: needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
@@ -207,7 +262,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "true"
@@ -267,11 +321,12 @@ jobs:
run: bash scripts/plugin-clawhub-publish.sh --dry-run "${PACKAGE_DIR}"
publish_plugins_clawhub:
needs: [preview_plugins_clawhub, preview_plugin_pack]
needs: [preview_plugins_clawhub, preview_plugin_pack, validate_release_publish_approval]
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
environment: clawhub-plugin-release
permissions:
actions: read
contents: read
id-token: write
strategy:
@@ -301,7 +356,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "true"

View File

@@ -25,13 +25,17 @@ 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:
description: Comma-separated plugin package names to publish when publish_scope=selected
required: false
type: string
release_publish_run_id:
description: Approved OpenClaw Release Publish workflow run id
required: false
type: string
concurrency:
group: plugin-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
@@ -40,7 +44,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
PNPM_VERSION: "11.0.8"
jobs:
preview_plugins_npm:
@@ -64,14 +67,15 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Resolve checked-out ref
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 +89,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 +162,50 @@ 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
validate_release_publish_approval:
name: Validate release publish approval
needs: preview_plugins_npm
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_npm.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- name: Validate release publish approval run
env:
GH_TOKEN: ${{ github.token }}
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
EXPECTED_WORKFLOW_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
if [[ -z "${RELEASE_PUBLISH_RUN_ID// }" ]]; then
if [[ "${GITHUB_ACTOR}" == "github-actions[bot]" ]]; then
echo "Plugin npm publish dispatched by another workflow must include release_publish_run_id." >&2
exit 1
fi
echo "Direct Plugin NPM Release dispatch; relying on this workflow's npm-release environment approval."
exit 0
fi
if [[ "${GITHUB_ACTOR}" != "github-actions[bot]" ]]; then
echo "Plugin npm publish must be dispatched by the OpenClaw Release Publish workflow, not directly by ${GITHUB_ACTOR}." >&2
exit 1
fi
RUN_JSON="$(gh run view "$RELEASE_PUBLISH_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", "OpenClaw Release Publish"], ["headBranch", process.env.EXPECTED_WORKFLOW_BRANCH], ["event", "workflow_dispatch"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } if (run.status !== "in_progress") { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} must still be in_progress, got ${run.status ?? "<missing>"}.`); process.exit(1); } if (run.conclusion) { console.error(`Referenced release publish run ${process.env.RELEASE_PUBLISH_RUN_ID} already concluded ${run.conclusion}.`); process.exit(1); } console.log(`Using release publish approval run ${process.env.RELEASE_PUBLISH_RUN_ID}: ${run.url}`);'
preview_plugin_pack:
needs: preview_plugins_npm
if: needs.preview_plugins_npm.outputs.has_candidates == 'true'
@@ -173,7 +228,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Preview publish command
@@ -183,11 +237,12 @@ jobs:
run: bash scripts/plugin-npm-publish.sh --pack-dry-run "${{ matrix.plugin.packageDir }}"
publish_plugins_npm:
needs: [preview_plugins_npm, preview_plugin_pack]
needs: [preview_plugins_npm, preview_plugin_pack, validate_release_publish_approval]
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_npm.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
environment: npm-release
permissions:
actions: read
contents: read
id-token: write
strategy:
@@ -206,7 +261,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Ensure version is not already published

View File

@@ -52,7 +52,7 @@ jobs:
ref: ${{ inputs.target_ref }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Build plugin prerelease manifest
@@ -221,7 +221,7 @@ jobs:
ref: ${{ needs.preflight.outputs.checkout_revision }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Setup Node environment
@@ -257,7 +257,7 @@ jobs:
ref: ${{ needs.preflight.outputs.checkout_revision }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Setup Node environment
@@ -330,7 +330,7 @@ jobs:
ref: ${{ needs.preflight.outputs.checkout_revision }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Setup Node environment
@@ -362,7 +362,7 @@ jobs:
ref: ${{ needs.preflight.outputs.checkout_revision }}
fetch-depth: 1
fetch-tags: false
persist-credentials: false
persist-credentials: true
submodules: false
- name: Setup Node environment

View File

@@ -51,7 +51,6 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "11.0.8"
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL || 'openai/gpt-5.5' }}
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
@@ -60,13 +59,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 +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_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 }}
@@ -174,10 +181,11 @@ jobs:
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 OpenAI candidate lane
@@ -188,7 +196,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,28 +205,117 @@ 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
- name: Upload parity artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-parity-${{ github.run_id }}-${{ github.run_attempt }}
path: .artifacts/qa-e2e/
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 }}
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@v7
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]
@@ -238,7 +335,6 @@ jobs:
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
@@ -254,6 +350,8 @@ jobs:
fi
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
- name: Run Matrix live lane
@@ -287,7 +385,7 @@ jobs:
- name: Upload Matrix QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-live-matrix-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
@@ -322,7 +420,6 @@ jobs:
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
@@ -338,6 +435,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
@@ -370,7 +469,7 @@ jobs:
- name: Upload Matrix QA shard artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-live-matrix-${{ matrix.profile }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
@@ -395,7 +494,6 @@ jobs:
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
@@ -420,6 +518,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
@@ -463,7 +563,7 @@ jobs:
- name: Upload Telegram QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-live-telegram-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
@@ -488,7 +588,6 @@ jobs:
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
@@ -513,6 +612,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 +648,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 \
@@ -556,7 +657,7 @@ jobs:
- name: Upload Discord QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-live-discord-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
@@ -568,6 +669,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
@@ -581,7 +685,6 @@ jobs:
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
@@ -606,6 +709,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
@@ -649,7 +754,7 @@ jobs:
- name: Upload WhatsApp QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-live-whatsapp-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
@@ -674,7 +779,6 @@ jobs:
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
@@ -699,6 +803,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
@@ -742,7 +848,7 @@ jobs:
- name: Upload Slack QA artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: qa-live-slack-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}

View File

@@ -18,6 +18,7 @@ jobs:
name: Real behavior proof
permissions:
contents: read
issues: read
pull-requests: read
runs-on: ubuntu-24.04
steps:
@@ -25,5 +26,25 @@ jobs:
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
permission-issues: read
permission-members: read
- uses: actions/create-github-app-token@v3
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
continue-on-error: true
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
permission-issues: read
permission-members: read
- name: Check real behavior proof
env:
GH_APP_TOKEN: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
GITHUB_TOKEN: ${{ github.token }}
run: node scripts/github/real-behavior-proof-check.mjs

41
.github/workflows/tui-pty.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: TUI PTY
on:
pull_request:
paths:
- "src/tui/**"
- "scripts/dev/tui-pty-test-watch.ts"
- "scripts/test-projects.test-support.mjs"
- "package.json"
- "pnpm-lock.yaml"
- "test/scripts/test-projects.test.ts"
- "test/vitest/vitest.test-shards.mjs"
- "test/vitest/vitest.tui-pty.config.ts"
- ".github/workflows/tui-pty.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
tui-pty:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Run TUI PTY tests
run: timeout 120s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts

View File

@@ -76,11 +76,9 @@ jobs:
- name: install.sh in Docker
run: |
docker run --rm \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \
node:24-bookworm-slim \
bash -lc 'bash /tmp/install.sh --no-prompt --no-onboard --version latest && openclaw --version'
bash -lc 'bash /tmp/install.sh --version latest && openclaw --version'
- name: install-cli.sh in Docker
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:

8
.gitignore vendored
View File

@@ -108,7 +108,9 @@ USER.md
.vscode/
# local tooling
.antigravitycli/
.serena/
.crabbox/
# local QA evidence mirrors; CI publishes canonical Mantis files as Actions artifacts
mantis/
@@ -122,6 +124,8 @@ mantis/
!.agents/skills/crabbox/**
!.agents/skills/clawdtributor/
!.agents/skills/clawdtributor/**
!.agents/skills/control-ui-e2e/
!.agents/skills/control-ui-e2e/**
!.agents/skills/gitcrawl/
!.agents/skills/gitcrawl/**
!.agents/skills/openclaw-docs/**
@@ -131,6 +135,8 @@ mantis/
!.agents/skills/openclaw-debugging/**
!.agents/skills/openclaw-ghsa-maintainer/
!.agents/skills/openclaw-ghsa-maintainer/**
!.agents/skills/openclaw-landable-bug-sweep/
!.agents/skills/openclaw-landable-bug-sweep/**
!.agents/skills/openclaw-parallels-smoke/
!.agents/skills/openclaw-parallels-smoke/**
!.agents/skills/openclaw-pr-maintainer/
@@ -159,6 +165,8 @@ mantis/
!.agents/skills/security-triage/**
!.agents/skills/tag-duplicate-prs-issues/
!.agents/skills/tag-duplicate-prs-issues/**
!.agents/skills/autoreview/
!.agents/skills/autoreview/**
# Agent credentials and memory (NEVER COMMIT)
/memory/

View File

@@ -8,6 +8,7 @@
},
"rules": {
"curly": "error",
"eslint/no-underscore-dangle": "error",
"eslint-plugin-unicorn/prefer-array-find": "error",
"eslint/no-array-constructor": "error",
"eslint/no-await-in-loop": "off",

View File

@@ -17,12 +17,33 @@ Skills own workflows; root owns hard policy and routing.
- New channel/plugin/app/doc surface: update `.github/labeler.yml` + GH labels.
- New `AGENTS.md`: add sibling `CLAUDE.md` symlink; edit `AGENTS.md` only.
## ClawSweeper Review Policy
- OpenClaw-specific review rules live here; generic ClawSweeper prompts stay repo-agnostic.
- ClawSweeper-owned schema, labels, close reasons, protected-label gates, maintainer-item gates, and mutation rules live in `openclaw/clawsweeper`.
- Review workers read this full root `AGENTS.md` before judging; no reliance on search snippets, `head`, partial ranges, local excerpts, or truncated copies. Then read every scoped `AGENTS.md` that owns touched paths.
- Optional integrations, providers, channels, skill bundles, MCP surfaces, and service workflows route to plugins, ClawHub, or owner repos when current seams suffice. Keep core items for missing core/plugin APIs, bundled regressions, security/core hardening, or maintainer product decisions.
- Plugin APIs, provider routing, auth/session state, persisted preferences, config loading, migrations, setup, startup checks, and fallback behavior are compatibility/upgrade-sensitive. Treat config breaks, removed fallbacks, fail-closed changes, or new operator action as merge risk even with green CI.
- Review whole decision surfaces, not only the touched runtime, provider, channel, harness, plugin seam, or context path. Check sibling Codex/Pi-style runtimes, provider/model routing, channel delivery, gateway/protocol, plugin SDK, and context-management paths when relevant.
- One-sided fixes need sibling-surface proof, an explanation for why siblings are unaffected, or explicit follow-up work.
- User-facing `fix`, `feat`, and `perf` changes need `CHANGELOG.md` before landing; contributor PR authors are not blocked solely on maintainer-owned changelog work. Never request thanks for bot/forbidden handles: `@openclaw`, `@clawsweeper`, `@codex`, `@steipete`.
- Public ClawSweeper comments prefer `https://docs.openclaw.ai/...` when a public docs page exists; structured evidence still cites repo files, lines, SHAs.
- Findings need current source, shipped/current behavior, tests/CI evidence, and dependency contract proof when dependency-backed behavior is involved. Validation is judged against touched and sibling surfaces plus this file's commands; real behavior proof matters for user-visible changes, with Telegram/Desktop proof for Telegram-visible behavior when feasible.
- Prefer findings for concrete behavior regressions, missing changed-surface proof, owner-boundary violations, security/API contract issues, or docs/config mismatches.
- Do not file findings for repo policy preference when changed code follows the relevant scoped guide and no user-visible, runtime, security, or maintainer-risk impact is shown.
## Map
- Core TS: `src/`, `ui/`, `packages/`; plugins: `extensions/`; SDK: `src/plugin-sdk/*`; channels: `src/channels/*`; loader: `src/plugins/*`; protocol: `src/gateway/protocol/*`; docs/apps: `docs/`, `apps/`.
- Installers: sibling `../openclaw.ai`.
- Scoped guides: `extensions/`, `src/{plugin-sdk,channels,plugins,gateway,gateway/protocol,agents}/`, `test/helpers*/`, `docs/`, `ui/`, `scripts/`.
## Docs
- Source docs: `docs/**`; publish repo: `openclaw/docs`; host: `https://docs.openclaw.ai`.
- Flow: source -> `docs-sync-publish.yml` -> mirror build -> R2 -> Worker router.
- Docs AI: `openclaw/ask-molty`; see its `AGENTS.md`.
## Architecture
- Core stays plugin-agnostic. No bundled ids/defaults/policy in core when manifest/registry/capability contracts work.
@@ -35,18 +56,27 @@ Skills own workflows; root owns hard policy and routing.
- 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: default to clean bounded refactor, not smallest patch. Move ownership to right boundary; delete stale abstractions, duplicate policy, dead branches, wrappers, fallback stacks.
- Lean code is a goal. No internal shims, aliases, legacy names, broad fallbacks, or defensive branches just to reduce diff or handle unrealistic edge cases.
- Handle real production states, shipped upgrade paths, security boundaries, and dependency contracts. Public/hostile/observed malformed input gets care; hypothetical malformed input does not.
- Public plugin SDK/API is the compat exception. New API first, old path only via named compat/deprecation metadata, docs, warnings when useful, tests for old+new, planned removal.
- Migrate internal/bundled callers to modern API in the same change. Do not let internal compat become permanent architecture.
- 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.
- Gateway/plugin metadata is process-stable: installs, manifests, catalogs, generated paths, bundled metadata. Changes require restart or explicit owner reload/install/doctor flow.
- Runtime hot paths: no freshness polling (`stat`/`realpath`/JSON reread/hash). Reuse current snapshots, install records, discovery, lookup tables, root scopes, resolved paths.
- Process-local metadata caches ok when lifecycle-owned and bounded/single-slot. Freshness exceptions need named owner + tests.
- 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.
- Runtime: Node 22.19+; Node 24 recommended. 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`.
@@ -63,19 +93,22 @@ Skills own workflows; root owns hard policy and routing.
## Validation
- Use `$openclaw-testing` for test/CI choice and `$crabbox` for remote/full/E2E proof.
- 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 `$codex-review` until no accepted/actionable findings remain, unless equivalent manual review already done, trivial/docs-only, or user opts out.
- 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.
- Prompt snapshots: CI truth is Linux Node 24. If macOS local passes but CI drifts, reproduce/generate in Linux before rerun.
## GitHub / PRs
- Use `$openclaw-pr-maintainer` immediately for maintainer-side OpenClaw issue/PR review, triage, duplicates, labels, comments, close, land, or evidence. Contributor PR creation/refresh follows the requested contributor workflow; linked refs alone do not require maintainer archive tooling.
- Pasted GitHub issue/PR: first `git status -sb`; if dirty, yell; then `git push` + `git pull --ff-only`.
- PR refs: `gh pr view/diff` or `gh api`, not web search. Prefer `gitcrawl` for maintainer discovery; missing/stale `gitcrawl` falls through to live `gh`, not contributor setup. Verify live with `gh` before mutation.
- Bare issue/PR URL/number means review/report in chat. Suggest comment/close/merge when appropriate; mutate only when asked.
- 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.
@@ -103,6 +136,10 @@ Skills own workflows; root owns hard policy and routing.
- No `@ts-nocheck`. Lint suppressions only intentional + explained.
- External boundaries: prefer `zod` or existing schema helpers.
- Runtime branching: discriminated unions/closed codes over freeform strings. Avoid semantic sentinels (`?? 0`, empty object/string).
- Formatter-friendly shape: when oxfmt explodes an expression vertically, extract named booleans, payloads, or small helpers. Do not change width or use format-ignore for local compactness.
- Calls should be boring: complex decisions happen above; call args/object fields are names, literals, or simple property reads.
- Prefer early returns over nested condition pyramids. Split code into gather -> normalize -> decide -> act.
- Use named intermediates only for domain meaning or readability; avoid temp-variable soup.
- Dynamic import: no static+dynamic import for same prod module. Use `*.runtime.ts` lazy boundary. After edits: `pnpm build`; check `[INEFFECTIVE_DYNAMIC_IMPORT]`.
- Cycles: keep `pnpm check:import-cycles` + architecture/madge green.
- Classes: no prototype mixins/mutations. Prefer inheritance/composition. Tests prefer per-instance stubs.
@@ -148,6 +185,7 @@ Skills own workflows; root owns hard policy and routing.
- Never commit real phone numbers, videos, credentials, live config.
- Secrets: channel/provider creds in `~/.openclaw/credentials/`; model auth profiles in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`.
- Dependency patches/overrides/vendor changes need explicit approval. `pnpm-workspace.yaml` patched dependencies use exact versions only.
- Lockfiles/shrinkwrap are security surface: review `pnpm-lock.yaml`, `npm-shrinkwrap.json`, `package-lock.json`; root/plugin npm packages ship shrinkwrap, not package-lock.
- Carbon pins owner-only: do not change `@buape/carbon` unless Shadow (`@thewilloftheshadow`, verified by `gh`) asks.
- Releases/publish/version bumps need explicit approval. Use `$openclaw-release-maintainer`.
- GHSA/advisories: `$openclaw-ghsa-maintainer` / `$security-triage`. Secret scanning: `$openclaw-secret-scanning-maintainer`.
@@ -159,6 +197,7 @@ Skills own workflows; root owns hard policy and routing.
- "restart iOS/Android apps" = rebuild/reinstall/relaunch, not kill/launch.
- SwiftUI: Observation (`@Observable`, `@Bindable`) over new `ObservableObject`.
- Mac gateway: dev watch = `pnpm gateway:watch`; managed installs = `openclaw gateway restart/status --deep`; logs = `./scripts/clawlog.sh`. No launchd/ad-hoc tmux.
- Mac app permission testing: stable app path + real signing identity required. No `--no-sign`, `SIGN_IDENTITY=-`, or raw debug binary; TCC prompts/listing won't stick.
- Version bump surfaces live in `$openclaw-release-maintainer`.
- Parallels: `$openclaw-parallels-smoke`; Discord roundtrip: `$parallels-discord-roundtrip`.
- Crabbox/WebVNC human demos: keep remote desktop visible/windowed; no fullscreen remote browser unless video/capture-style output.

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,7 @@ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY openclaw.mjs ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs ./scripts/
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs scripts/prepare-git-hooks.mjs ./scripts/
COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs
COPY --from=workspace-deps /out/packages/ ./packages/
@@ -116,19 +116,22 @@ ENV OPENCLAW_PREFER_PNPM=1
RUN pnpm_config_verify_deps_before_run=false pnpm ui:build
RUN pnpm_config_verify_deps_before_run=false pnpm qa:lab:build
# Prune dev dependencies and strip build-only metadata before copying
# runtime assets into the final image.
# Prune dev dependencies, omitted plugin runtime packages, and build-only
# metadata before copying runtime assets into the final image.
FROM build AS runtime-assets
ARG OPENCLAW_EXTENSIONS
ARG OPENCLAW_BUNDLED_PLUGIN_DIR
# BuildKit cache mounts are not part of cached layers; seed tarballs for the
# installed prod graph in the same step that runs offline prune.
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm list --prod --depth Infinity --json | node scripts/list-prod-store-packages.mjs | xargs -r pnpm store add && \
CI=true pnpm prune --prod \
--config.offline=true \
--config.supportedArchitectures.os=linux \
--config.supportedArchitectures.cpu="$(node -p 'process.arch')" \
--config.supportedArchitectures.libc=glibc && \
OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" OPENCLAW_BUNDLED_PLUGIN_DIR="$OPENCLAW_BUNDLED_PLUGIN_DIR" node scripts/prune-docker-plugin-dist.mjs && \
node scripts/postinstall-bundled-plugins.mjs && \
OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" node scripts/prune-docker-plugin-dist.mjs && \
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete && \
node scripts/check-package-dist-imports.mjs /app
@@ -198,13 +201,29 @@ RUN install -d -m 0755 "$COREPACK_HOME" && \
chmod -R a+rX "$COREPACK_HOME"
# Install additional system packages needed by your skills or extensions.
# Example: docker build --build-arg OPENCLAW_DOCKER_APT_PACKAGES="python3 wget" .
# Example: docker build --build-arg OPENCLAW_IMAGE_APT_PACKAGES="python3 wget" .
# Legacy alias: OPENCLAW_DOCKER_APT_PACKAGES is still accepted as a fallback.
ARG OPENCLAW_IMAGE_APT_PACKAGES
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
packages="${OPENCLAW_IMAGE_APT_PACKAGES-$OPENCLAW_DOCKER_APT_PACKAGES}"; \
if [ -n "$packages" ]; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $packages; \
fi
# Install additional Python packages needed by your plugins or skills.
# Example: docker build --build-arg OPENCLAW_IMAGE_PIP_PACKAGES="requests humanize" .
ARG OPENCLAW_IMAGE_PIP_PACKAGES=""
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
if [ -n "$OPENCLAW_IMAGE_PIP_PACKAGES" ]; then \
if ! python3 -m pip --version >/dev/null 2>&1; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends python3-pip; \
fi && \
python3 -m pip install --no-cache-dir --break-system-packages $OPENCLAW_IMAGE_PIP_PACKAGES; \
fi
# Optionally install Chromium and Xvfb for browser automation.
@@ -266,10 +285,15 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
&& chmod 755 /app/openclaw.mjs
# Pre-create the default state dir so first-run Docker named volumes mounted
# here inherit node ownership instead of root-owned state.
RUN install -d -m 0700 -o node -g node /home/node/.openclaw && \
stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700'
# Pre-create default named-volume mount points so first-run Docker volumes copy
# node ownership from the image instead of starting as root-owned directories.
RUN install -d -m 0700 -o node -g node \
/home/node/.openclaw \
/home/node/.openclaw/workspace \
/home/node/.config/openclaw && \
stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' && \
stat -c '%U:%G %a' /home/node/.openclaw/workspace | grep -qx 'node:node 700' && \
stat -c '%U:%G %a' /home/node/.config/openclaw | grep -qx 'node:node 700'
ENV NODE_ENV=production
@@ -293,4 +317,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

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Peter Steinberger
Copyright (c) 2026 OpenClaw Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -92,11 +92,11 @@ Works with npm, pnpm, or bun.
- **[OpenAI](https://openai.com/)** (ChatGPT/Codex)
Model note: while many providers and models are supported, prefer a current flagship model from the provider you trust and already use. See [Onboarding](https://docs.openclaw.ai/start/onboarding).
Model note: while many providers and models are supported, prefer a current flagship model from the provider you trust and already use. See [Onboarding](https://docs.openclaw.ai/start/wizard).
## Install (recommended)
Runtime: **Node 24 (recommended) or Node 22.16+**.
Runtime: **Node 24 (recommended) or Node 22.19+**.
```bash
npm install -g openclaw@latest
@@ -109,15 +109,27 @@ OpenClaw Onboard installs the Gateway daemon (launchd/systemd user service) so i
## Quick start (TL;DR)
Runtime: **Node 24 (recommended) or Node 22.16+**.
Runtime: **Node 24 (recommended) or Node 22.19+**.
Full beginner guide (auth, pairing, channels): [Getting started](https://docs.openclaw.ai/start/getting-started)
Recommended daemon mode:
```bash
openclaw onboard --install-daemon
openclaw gateway status
```
Foreground/debug mode:
```bash
openclaw gateway stop
openclaw gateway --port 18789 --verbose
```
Send a test message or ask the assistant after either startup mode is running:
```bash
# Send a message
openclaw message send --target +1234567890 --message "Hello from OpenClaw"
@@ -133,7 +145,8 @@ Models config + CLI: [Models](https://docs.openclaw.ai/concepts/models). Auth pr
OpenClaw connects to real messaging surfaces. Treat inbound DMs as **untrusted input**.
Full security guide: [Security](https://docs.openclaw.ai/gateway/security)
Full security guide: [Security](https://docs.openclaw.ai/gateway/security).
Before remote exposure, use the [Gateway exposure runbook](https://docs.openclaw.ai/gateway/security/exposure-runbook).
Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack:
@@ -159,7 +172,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- Default: tools run on the host for the `main` session, so the agent has full access when it is just you.
- Group/channel safety: set `agents.defaults.sandbox.mode: "non-main"` to run non-`main` sessions inside sandboxes. Docker is the default sandbox backend; SSH and OpenShell backends are also available.
- Typical sandbox default: allow `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`; deny `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`.
- Before exposing anything remotely, read [Security](https://docs.openclaw.ai/gateway/security), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing), and [Configuration](https://docs.openclaw.ai/gateway/configuration).
- Before exposing anything remotely, read [Security](https://docs.openclaw.ai/gateway/security), [Gateway exposure runbook](https://docs.openclaw.ai/gateway/security/exposure-runbook), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing), and [Configuration](https://docs.openclaw.ai/gateway/configuration).
## Operator quick refs
@@ -173,7 +186,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- New here: [Getting started](https://docs.openclaw.ai/start/getting-started), [Onboarding](https://docs.openclaw.ai/start/wizard), [Updating](https://docs.openclaw.ai/install/updating)
- Channel setup: [Channels index](https://docs.openclaw.ai/channels), [WhatsApp](https://docs.openclaw.ai/channels/whatsapp), [Telegram](https://docs.openclaw.ai/channels/telegram), [Discord](https://docs.openclaw.ai/channels/discord), [Slack](https://docs.openclaw.ai/channels/slack)
- Apps + nodes: [macOS](https://docs.openclaw.ai/platforms/macos), [iOS](https://docs.openclaw.ai/platforms/ios), [Android](https://docs.openclaw.ai/platforms/android), [Nodes](https://docs.openclaw.ai/nodes)
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing)
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Exposure runbook](https://docs.openclaw.ai/gateway/security/exposure-runbook), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing)
- Remote + web: [Gateway](https://docs.openclaw.ai/gateway), [Remote access](https://docs.openclaw.ai/gateway/remote), [Tailscale](https://docs.openclaw.ai/gateway/tailscale), [Web surfaces](https://docs.openclaw.ai/web)
- Tools + automation: [Tools](https://docs.openclaw.ai/tools), [Skills](https://docs.openclaw.ai/tools/skills), [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs), [Webhooks](https://docs.openclaw.ai/automation/webhook), [Gmail Pub/Sub](https://docs.openclaw.ai/automation/gmail-pubsub)
- Internals: [Architecture](https://docs.openclaw.ai/concepts/architecture), [Agent](https://docs.openclaw.ai/concepts/agent), [Session model](https://docs.openclaw.ai/concepts/session), [Gateway protocol](https://docs.openclaw.ai/reference/rpc)

View File

@@ -312,7 +312,7 @@ OpenClaw's web interface (Gateway Control UI + HTTP endpoints) is intended for *
### Node.js Version
OpenClaw requires **Node.js 22.16.0 or later** (LTS). This version includes important security patches:
OpenClaw requires **Node.js 22.19.0 or later** (LTS). Node 24 is the recommended default runtime for new installs. The minimum version includes important security patches:
- CVE-2025-59466: async_hooks DoS vulnerability
- CVE-2026-21636: Permission model bypass vulnerability
@@ -320,7 +320,7 @@ OpenClaw requires **Node.js 22.16.0 or later** (LTS). This version includes impo
Verify your Node.js version:
```bash
node --version # Should be v22.16.0 or later
node --version # Should be v22.19.0 or later
```
### Docker Security

File diff suppressed because it is too large Load Diff

View File

@@ -209,15 +209,16 @@ Why these matter:
- Google Play treats SMS and Call Log access as highly restricted. In most cases, Play only allows them for the default SMS app, default Phone app, default Assistant, or a narrow policy exception.
- Review usually involves a `Permissions Declaration Form`, policy justification, and demo video evidence in Play Console.
- If we want a Play-safe build, these should be the first permissions removed behind a dedicated product flavor / variant.
- The Play build removes these behind the `play` flavor.
- Photo library access is also removed from the Play build. Use third-party builds for `photos.latest`.
Current OpenClaw Android implication:
- APK / sideload build can keep SMS and Call Log features.
- Google Play build should exclude SMS send/search and Call Log search unless the product is intentionally positioned and approved as a default-handler exception case.
- APK / sideload build can keep SMS, Call Log, and recent-photo features.
- Google Play build excludes SMS send/search, Call Log search, and recent-photo access unless the product is intentionally positioned and approved under the relevant policy exception.
- The repo now ships this split as Android product flavors:
- `play`: removes `READ_SMS`, `SEND_SMS`, and `READ_CALL_LOG`, and hides SMS / Call Log surfaces in onboarding, settings, and advertised node capabilities.
- `thirdParty`: keeps the full permission set and the existing SMS / Call Log functionality.
- `play`: removes `READ_SMS`, `SEND_SMS`, `READ_CALL_LOG`, `READ_MEDIA_IMAGES`, `READ_MEDIA_VISUAL_USER_SELECTED`, and `READ_EXTERNAL_STORAGE`; hides SMS, Call Log, and Photos surfaces in onboarding, settings, and advertised node capabilities.
- `thirdParty`: keeps the full permission set and the existing SMS / Call Log / Photos functionality.
Policy links:
@@ -252,12 +253,13 @@ Pre-req checklist:
5) Grant runtime permissions for capabilities you expect to pass (camera/mic/location/notification listener/location, etc.).
6) No interactive system dialogs should be pending before test start.
7) Canvas host is enabled and reachable from the device (do not run gateway with `OPENCLAW_SKIP_CANVAS_HOST=1`; startup logs should include `canvas host mounted at .../__openclaw__/`).
8) Local operator test client pairing is approved. If first run fails with `pairing required`, approve latest pending device pairing request, then rerun:
8) Local operator test client pairing is approved. If first run fails with `pairing required`, preview the latest pending request, approve the printed request ID, then rerun:
9) For A2UI checks, keep the app on **Screen** tab; the node now auto-refreshes canvas capability once on first A2UI reachability failure (TTL-safe retry).
```bash
openclaw devices list
openclaw devices approve --latest
openclaw devices approve --latest # preview only; copy the requestId from output
openclaw devices approve <requestId>
```
Run:
@@ -283,7 +285,7 @@ What it does:
Common failure quick-fixes:
- `pairing required` before tests start:
- approve pending device pairing (`openclaw devices approve --latest`) and rerun.
- list pending requests (`openclaw devices list`), then approve with the exact ID (`openclaw devices approve <requestId>`) and rerun.
- `A2UI host not reachable` / `A2UI_HOST_NOT_CONFIGURED`:
- ensure the Canvas plugin host is running and reachable, keep the app on the **Screen** tab. The app refreshes the Canvas plugin surface URL once before failing; if it still fails, reconnect app and rerun.
- `NODE_BACKGROUND_UNAVAILABLE: canvas unavailable`:

View File

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

View File

@@ -5,6 +5,7 @@ import ai.openclaw.app.chat.ChatPendingToolCall
import ai.openclaw.app.chat.ChatSessionEntry
import ai.openclaw.app.chat.OutgoingAttachment
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewayUpdateAvailableSummary
import ai.openclaw.app.node.CameraCaptureManager
import ai.openclaw.app.node.CanvasController
import ai.openclaw.app.node.SmsManager
@@ -31,6 +32,8 @@ class MainViewModel(
private var foreground = true
private val _requestedHomeDestination = MutableStateFlow<HomeDestination?>(null)
val requestedHomeDestination: StateFlow<HomeDestination?> = _requestedHomeDestination
private val _startOnboardingAtGatewaySetup = MutableStateFlow(false)
val startOnboardingAtGatewaySetup: StateFlow<Boolean> = _startOnboardingAtGatewaySetup
private val _chatDraft = MutableStateFlow<String?>(null)
val chatDraft: StateFlow<String?> = _chatDraft
private val _pendingAssistantAutoSend = MutableStateFlow<String?>(null)
@@ -81,6 +84,40 @@ class MainViewModel(
val statusText: StateFlow<String> = runtimeState(initial = "Offline") { it.statusText }
val serverName: StateFlow<String?> = runtimeState(initial = null) { it.serverName }
val remoteAddress: StateFlow<String?> = runtimeState(initial = null) { it.remoteAddress }
val gatewayVersion: StateFlow<String?> = runtimeState(initial = null) { it.gatewayVersion }
val gatewayUpdateAvailable: StateFlow<GatewayUpdateAvailableSummary?> = runtimeState(initial = null) { it.gatewayUpdateAvailable }
val modelCatalog: StateFlow<List<GatewayModelSummary>> = runtimeState(initial = emptyList()) { it.modelCatalog }
val modelAuthProviders: StateFlow<List<GatewayModelProviderSummary>> = runtimeState(initial = emptyList()) { it.modelAuthProviders }
val modelCatalogRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.modelCatalogRefreshing }
val modelCatalogErrorText: StateFlow<String?> = runtimeState(initial = null) { it.modelCatalogErrorText }
val gatewayDefaultAgentId: StateFlow<String?> = runtimeState(initial = null) { it.gatewayDefaultAgentId }
val gatewayAgents: StateFlow<List<GatewayAgentSummary>> = runtimeState(initial = emptyList()) { it.gatewayAgents }
val cronStatus: StateFlow<GatewayCronStatus> = runtimeState(initial = GatewayCronStatus(enabled = false, jobs = 0, nextWakeAtMs = null)) { it.cronStatus }
val cronJobs: StateFlow<List<GatewayCronJobSummary>> = runtimeState(initial = emptyList()) { it.cronJobs }
val cronRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.cronRefreshing }
val cronErrorText: StateFlow<String?> = runtimeState(initial = null) { it.cronErrorText }
val usageSummary: StateFlow<GatewayUsageSummary> = runtimeState(initial = GatewayUsageSummary(updatedAtMs = null, providers = emptyList())) { it.usageSummary }
val usageRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.usageRefreshing }
val usageErrorText: StateFlow<String?> = runtimeState(initial = null) { it.usageErrorText }
val skillsSummary: StateFlow<GatewaySkillsSummary> = runtimeState(initial = GatewaySkillsSummary(skills = emptyList())) { it.skillsSummary }
val skillsRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.skillsRefreshing }
val skillsErrorText: StateFlow<String?> = runtimeState(initial = null) { it.skillsErrorText }
val nodesDevicesSummary: StateFlow<GatewayNodesDevicesSummary> =
runtimeState(initial = GatewayNodesDevicesSummary(nodes = emptyList(), pendingDevices = emptyList(), pairedDevices = emptyList())) { it.nodesDevicesSummary }
val nodesDevicesRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.nodesDevicesRefreshing }
val nodesDevicesErrorText: StateFlow<String?> = runtimeState(initial = null) { it.nodesDevicesErrorText }
val channelsSummary: StateFlow<GatewayChannelsSummary> =
runtimeState(initial = GatewayChannelsSummary(channels = emptyList())) { it.channelsSummary }
val channelsRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.channelsRefreshing }
val channelsErrorText: StateFlow<String?> = runtimeState(initial = null) { it.channelsErrorText }
val dreamingSummary: StateFlow<GatewayDreamingSummary> =
runtimeState(initial = GatewayDreamingSummary()) { it.dreamingSummary }
val dreamingRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.dreamingRefreshing }
val dreamingErrorText: StateFlow<String?> = runtimeState(initial = null) { it.dreamingErrorText }
val healthLogsSummary: StateFlow<GatewayHealthLogsSummary> =
runtimeState(initial = GatewayHealthLogsSummary()) { it.healthLogsSummary }
val healthLogsRefreshing: StateFlow<Boolean> = runtimeState(initial = false) { it.healthLogsRefreshing }
val healthLogsErrorText: StateFlow<String?> = runtimeState(initial = null) { it.healthLogsErrorText }
val pendingGatewayTrust: StateFlow<NodeRuntime.GatewayTrustPrompt?> = runtimeState(initial = null) { it.pendingGatewayTrust }
val seamColorArgb: StateFlow<Long> = runtimeState(initial = 0xFF0EA5E9) { it.seamColorArgb }
val mainSessionKey: StateFlow<String> = runtimeState(initial = "main") { it.mainSessionKey }
@@ -118,10 +155,13 @@ class MainViewModel(
val talkModeListening: StateFlow<Boolean> = runtimeState(initial = false) { it.talkModeListening }
val talkModeSpeaking: StateFlow<Boolean> = runtimeState(initial = false) { it.talkModeSpeaking }
val talkModeStatusText: StateFlow<String> = runtimeState(initial = "Off") { it.talkModeStatusText }
val talkModeConversation: StateFlow<List<VoiceConversationEntry>> =
runtimeState(initial = emptyList()) { it.talkModeConversation }
val chatSessionKey: StateFlow<String> = runtimeState(initial = "main") { it.chatSessionKey }
val chatSessionId: StateFlow<String?> = runtimeState(initial = null) { it.chatSessionId }
val chatMessages: StateFlow<List<ChatMessage>> = runtimeState(initial = emptyList()) { it.chatMessages }
val chatHistoryLoading: StateFlow<Boolean> = runtimeState(initial = false) { it.chatHistoryLoading }
val chatError: StateFlow<String?> = runtimeState(initial = null) { it.chatError }
val chatHealthOk: StateFlow<Boolean> = runtimeState(initial = false) { it.chatHealthOk }
val chatThinkingLevel: StateFlow<String> = runtimeState(initial = "off") { it.chatThinkingLevel }
@@ -225,6 +265,17 @@ class MainViewModel(
prefs.setOnboardingCompleted(value)
}
fun pairNewGateway() {
runtimeRef.value?.disconnect()
resetGatewaySetupAuth()
_startOnboardingAtGatewaySetup.value = true
prefs.setOnboardingCompleted(false)
}
fun clearGatewaySetupStartRequest() {
_startOnboardingAtGatewaySetup.value = false
}
fun setCanvasDebugStatusEnabled(value: Boolean) {
prefs.setCanvasDebugStatusEnabled(value)
}
@@ -291,6 +342,10 @@ class MainViewModel(
ensureRuntime().setMicEnabled(enabled)
}
fun cancelMicCapture() {
ensureRuntime().cancelMicCapture()
}
fun setTalkModeEnabled(enabled: Boolean) {
ensureRuntime().setTalkModeEnabled(enabled)
}
@@ -353,6 +408,42 @@ class MainViewModel(
ensureRuntime().refreshHomeCanvasOverviewIfConnected()
}
fun refreshModelCatalog() {
ensureRuntime().refreshModelCatalog()
}
fun refreshAgents() {
ensureRuntime().refreshAgents()
}
fun refreshCronJobs() {
ensureRuntime().refreshCronJobs()
}
fun refreshUsage() {
ensureRuntime().refreshUsage()
}
fun refreshSkills() {
ensureRuntime().refreshSkills()
}
fun refreshNodesDevices() {
ensureRuntime().refreshNodesDevices()
}
fun refreshChannels() {
ensureRuntime().refreshChannels()
}
fun refreshDreaming() {
ensureRuntime().refreshDreaming()
}
fun refreshHealthLogs() {
ensureRuntime().refreshHealthLogs()
}
fun loadChat(sessionKey: String) {
ensureRuntime().loadChat(sessionKey)
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,80 +17,148 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
class PermissionRequester(
class PermissionRequester internal constructor(
private val activity: ComponentActivity,
launcherFactory: ((Map<String, Boolean>) -> Unit) -> ActivityResultLauncher<Array<String>>,
) {
private val mutex = Mutex()
private var pending: CompletableDeferred<Map<String, Boolean>>? = null
private val mainHandler = Handler(Looper.getMainLooper())
private data class PendingPermissionRequest(
val deferred: CompletableDeferred<Map<String, Boolean>>,
var timedOut: Boolean = false,
)
private val launcher: ActivityResultLauncher<Array<String>> =
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
val p = pending
pending = null
p?.complete(result)
}
private class PermissionRequestSlot(
val launcher: ActivityResultLauncher<Array<String>>,
var request: PendingPermissionRequest? = null,
)
constructor(activity: ComponentActivity) : this(
activity = activity,
launcherFactory = { callback ->
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions(), callback)
},
)
private val mutex = Mutex()
private val requestSlotsLock = Any()
private val mainHandler = Handler(Looper.getMainLooper())
private val launchers = List(4) { createPermissionRequestSlot(launcherFactory) }
suspend fun requestIfMissing(
permissions: List<String>,
timeoutMs: Long = 20_000,
): Map<String, Boolean> =
mutex.withLock {
val missing =
permissions.filter { perm ->
ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED
): Map<String, Boolean> {
return mutex.withLock {
while (true) {
val missing =
permissions.filter { perm ->
ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED
}
if (missing.isEmpty()) {
return permissions.associateWith { true }
}
if (missing.isEmpty()) {
return permissions.associateWith { true }
}
val needsRationale =
missing.any { ActivityCompat.shouldShowRequestPermissionRationale(activity, it) }
if (needsRationale) {
val proceed = showRationaleDialog(missing)
if (!proceed) {
return permissions.associateWith { perm ->
ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
val needsRationale =
missing.any { ActivityCompat.shouldShowRequestPermissionRationale(activity, it) }
if (needsRationale) {
val proceed = showRationaleDialog(missing)
if (!proceed) {
return permissions.associateWith { perm ->
ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
}
}
}
}
val deferred = CompletableDeferred<Map<String, Boolean>>()
pending = deferred
withContext(Dispatchers.Main) {
launcher.launch(missing.toTypedArray())
}
val result =
withContext(Dispatchers.Default) {
kotlinx.coroutines.withTimeout(timeoutMs) { deferred.await() }
val deferred = CompletableDeferred<Map<String, Boolean>>()
val request = PendingPermissionRequest(deferred)
val slot = reservePermissionRequestSlot(request)
try {
withContext(Dispatchers.Main) {
slot.launcher.launch(missing.toTypedArray())
}
} catch (err: Throwable) {
clearPermissionRequestSlot(slot, request)
throw err
}
// Merge: if something was already granted, treat it as granted even if launcher omitted it.
val merged =
permissions.associateWith { perm ->
val nowGranted =
ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
result[perm] == true || nowGranted
val result =
try {
withTimeout(timeoutMs) { deferred.await() }
} catch (err: TimeoutCancellationException) {
request.timedOut = true
throw err
}
val merged =
permissions.associateWith { perm ->
val nowGranted =
ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
result[perm] == true || nowGranted
}
val denied =
merged.filterValues { !it }.keys.filter {
!ActivityCompat.shouldShowRequestPermissionRationale(activity, it)
}
if (denied.isNotEmpty()) {
showSettingsDialog(denied)
}
val denied =
merged.filterValues { !it }.keys.filter {
!ActivityCompat.shouldShowRequestPermissionRationale(activity, it)
}
if (denied.isNotEmpty()) {
showSettingsDialog(denied)
return merged
}
return merged
error("unreachable")
}
}
private fun createPermissionRequestSlot(
launcherFactory: ((Map<String, Boolean>) -> Unit) -> ActivityResultLauncher<Array<String>>,
): PermissionRequestSlot {
var slot: PermissionRequestSlot? = null
val launcher = launcherFactory { result -> completePermissionRequest(checkNotNull(slot), result) }
val created = PermissionRequestSlot(launcher)
slot = created
return created
}
private fun reservePermissionRequestSlot(request: PendingPermissionRequest): PermissionRequestSlot =
synchronized(requestSlotsLock) {
val slot = launchers.firstOrNull { it.request == null } ?: error("permission request launcher busy")
slot.request = request
slot
}
private fun completePermissionRequest(
slot: PermissionRequestSlot,
result: Map<String, Boolean>,
) {
val request =
synchronized(requestSlotsLock) {
slot.request.also {
slot.request = null
}
} ?: return
if (request.timedOut) return
request.deferred.complete(result)
}
private fun clearPermissionRequestSlot(
slot: PermissionRequestSlot,
request: PendingPermissionRequest,
) {
synchronized(requestSlotsLock) {
if (slot.request === request) {
slot.request = null
}
}
}
private suspend fun showRationaleDialog(permissions: List<String>): Boolean =
withContext(Dispatchers.Main) {

View File

@@ -17,12 +17,12 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong
class ChatController(
private val scope: CoroutineScope,
private val session: GatewaySession,
private val json: Json,
private val supportsChatSubscribe: Boolean,
) {
private var appliedMainSessionKey = "main"
private val _sessionKey = MutableStateFlow("main")
@@ -34,6 +34,9 @@ class ChatController(
private val _messages = MutableStateFlow<List<ChatMessage>>(emptyList())
val messages: StateFlow<List<ChatMessage>> = _messages.asStateFlow()
private val _historyLoading = MutableStateFlow(false)
val historyLoading: StateFlow<Boolean> = _historyLoading.asStateFlow()
private val _errorText = MutableStateFlow<String?>(null)
val errorText: StateFlow<String?> = _errorText.asStateFlow()
@@ -58,25 +61,29 @@ class ChatController(
private val pendingRuns = mutableSetOf<String>()
private val pendingRunTimeoutJobs = ConcurrentHashMap<String, Job>()
private val optimisticMessagesByRunId = LinkedHashMap<String, ChatMessage>()
private val pendingRunTimeoutMs = 120_000L
private val historyLoadGeneration = AtomicLong(0)
private var lastHealthPollAtMs: Long? = null
fun onDisconnected(message: String) {
_healthOk.value = false
// Not an error; keep connection status in the UI pill.
_errorText.value = null
clearPendingRuns()
pendingToolCallsById.clear()
publishPendingToolCalls()
_streamingAssistantText.value = null
_historyLoading.value = false
_sessionId.value = null
}
fun load(sessionKey: String) {
val key = normalizeRequestedSessionKey(sessionKey)
_sessionKey.value = key
scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
val generation = beginHistoryLoad(key, clearMessages = key != _sessionKey.value)
scope.launch {
bootstrap(sessionKey = key, generation = generation, forceHealth = true, refreshSessions = true)
}
}
fun applyMainSessionKey(mainSessionKey: String) {
@@ -90,12 +97,23 @@ class ChatController(
)
appliedMainSessionKey = nextState.appliedMainSessionKey
if (_sessionKey.value == nextState.currentSessionKey) return
_sessionKey.value = nextState.currentSessionKey
scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
val generation = beginHistoryLoad(nextState.currentSessionKey, clearMessages = true)
scope.launch {
bootstrap(
sessionKey = nextState.currentSessionKey,
generation = generation,
forceHealth = true,
refreshSessions = true,
)
}
}
fun refresh() {
scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
val key = normalizeRequestedSessionKey(_sessionKey.value)
val generation = beginHistoryLoad(key, clearMessages = false)
scope.launch {
bootstrap(sessionKey = key, generation = generation, forceHealth = true, refreshSessions = true)
}
}
fun refreshSessions(limit: Int? = null) {
@@ -112,10 +130,30 @@ class ChatController(
val key = normalizeRequestedSessionKey(sessionKey)
if (key.isEmpty()) return
if (key == _sessionKey.value) return
val generation = beginHistoryLoad(key, clearMessages = true)
scope.launch {
bootstrap(sessionKey = key, generation = generation, forceHealth = true, refreshSessions = false)
}
}
private fun beginHistoryLoad(
key: String,
clearMessages: Boolean,
): Long {
val generation = historyLoadGeneration.incrementAndGet()
_sessionKey.value = key
// Keep the thread switch path lean: history + health are needed immediately,
// but the session list is usually unchanged and can refresh on explicit pull-to-refresh.
scope.launch { bootstrap(forceHealth = true, refreshSessions = false) }
_errorText.value = null
_healthOk.value = false
clearPendingRuns()
pendingToolCallsById.clear()
publishPendingToolCalls()
_streamingAssistantText.value = null
_sessionId.value = null
_historyLoading.value = true
if (clearMessages) {
_messages.value = emptyList()
}
return generation
}
private fun normalizeRequestedSessionKey(sessionKey: String): String {
@@ -171,14 +209,15 @@ class ChatController(
)
}
}
_messages.value =
_messages.value +
val optimisticMessage =
ChatMessage(
id = UUID.randomUUID().toString(),
role = "user",
content = userContent,
timestampMs = System.currentTimeMillis(),
)
optimisticMessagesByRunId[runId] = optimisticMessage
_messages.value = _messages.value + optimisticMessage
armPendingRunTimeout(runId)
synchronized(pendingRuns) {
@@ -218,6 +257,7 @@ class ChatController(
val res = session.request("chat.send", params.toString())
val actualRunId = parseRunId(res) ?: runId
if (actualRunId != runId) {
optimisticMessagesByRunId[actualRunId] = optimisticMessagesByRunId.remove(runId) ?: optimisticMessage
clearPendingRun(runId)
armPendingRunTimeout(actualRunId)
synchronized(pendingRuns) {
@@ -228,6 +268,7 @@ class ChatController(
true
} catch (err: Throwable) {
clearPendingRun(runId)
removeOptimisticMessage(runId)
_errorText.value = err.message
false
}
@@ -283,27 +324,22 @@ class ChatController(
}
private suspend fun bootstrap(
sessionKey: String,
generation: Long,
forceHealth: Boolean,
refreshSessions: Boolean,
) {
_errorText.value = null
_healthOk.value = false
clearPendingRuns()
pendingToolCallsById.clear()
publishPendingToolCalls()
_streamingAssistantText.value = null
_sessionId.value = null
val key = _sessionKey.value
try {
if (supportsChatSubscribe) {
session.sendNodeEvent("chat.subscribe", """{"sessionKey":"$key"}""")
}
val historyJson = session.request("chat.history", """{"sessionKey":"$key"}""")
val history = parseHistory(historyJson, sessionKey = key, previousMessages = _messages.value)
_messages.value = history.messages
val historyJson =
session.request(
"chat.history",
buildJsonObject { put("sessionKey", JsonPrimitive(sessionKey)) }.toString(),
)
if (!isCurrentHistoryLoad(sessionKey, _sessionKey.value, generation, historyLoadGeneration.get())) return
val history = parseHistory(historyJson, sessionKey = sessionKey, previousMessages = _messages.value)
_messages.value = mergeOptimisticMessages(incoming = history.messages, optimistic = optimisticMessagesByRunId.values)
_sessionId.value = history.sessionId
_historyLoading.value = false
history.thinkingLevel
?.trim()
?.takeIf { it.isNotEmpty() }
@@ -314,7 +350,9 @@ class ChatController(
fetchSessions(limit = 50)
}
} catch (err: Throwable) {
if (!isCurrentHistoryLoad(sessionKey, _sessionKey.value, generation, historyLoadGeneration.get())) return
_errorText.value = err.message
_historyLoading.value = false
}
}
@@ -369,16 +407,42 @@ class ChatController(
if (state == "error") {
_errorText.value = payload["errorMessage"].asStringOrNull() ?: "Chat failed"
}
if (runId != null) clearPendingRun(runId) else clearPendingRuns()
if (runId != null) {
clearPendingRun(runId)
optimisticMessagesByRunId.remove(runId)
} else {
clearPendingRuns()
optimisticMessagesByRunId.clear()
}
pendingToolCallsById.clear()
publishPendingToolCalls()
_streamingAssistantText.value = null
scope.launch {
try {
val currentSessionKey = _sessionKey.value
val currentGeneration = historyLoadGeneration.get()
val historyJson =
session.request("chat.history", """{"sessionKey":"${_sessionKey.value}"}""")
val history = parseHistory(historyJson, sessionKey = _sessionKey.value, previousMessages = _messages.value)
_messages.value = history.messages
session.request(
"chat.history",
buildJsonObject { put("sessionKey", JsonPrimitive(currentSessionKey)) }.toString(),
)
if (
!isCurrentHistoryLoad(
currentSessionKey,
_sessionKey.value,
currentGeneration,
historyLoadGeneration.get(),
)
) {
return@launch
}
val history =
parseHistory(
historyJson,
sessionKey = currentSessionKey,
previousMessages = _messages.value,
)
_messages.value = mergeOptimisticMessages(incoming = history.messages, optimistic = optimisticMessagesByRunId.values)
_sessionId.value = history.sessionId
history.thinkingLevel
?.trim()
@@ -471,6 +535,7 @@ class ChatController(
}
if (!stillPending) return@launch
clearPendingRun(runId)
removeOptimisticMessage(runId)
_errorText.value = "Timed out waiting for a reply; try again or refresh."
}
}
@@ -488,12 +553,18 @@ class ChatController(
job.cancel()
}
pendingRunTimeoutJobs.clear()
optimisticMessagesByRunId.clear()
synchronized(pendingRuns) {
pendingRuns.clear()
_pendingRunCount.value = 0
}
}
private fun removeOptimisticMessage(runId: String) {
val message = optimisticMessagesByRunId.remove(runId) ?: return
_messages.value = _messages.value.filterNot { it.id == message.id }
}
private fun parseHistory(
historyJson: String,
sessionKey: String,
@@ -508,7 +579,7 @@ class ChatController(
array.mapNotNull { item ->
val obj = item.asObjectOrNull() ?: return@mapNotNull null
val role = obj["role"].asStringOrNull() ?: return@mapNotNull null
val content = obj["content"].asArrayOrNull()?.mapNotNull(::parseMessageContent) ?: emptyList()
val content = obj["content"].asArrayOrNull()?.mapNotNull(::parseChatMessageContent) ?: emptyList()
val ts = obj["timestamp"].asLongOrNull()
ChatMessage(
id = UUID.randomUUID().toString(),
@@ -526,21 +597,6 @@ class ChatController(
)
}
private fun parseMessageContent(el: JsonElement): ChatMessageContent? {
val obj = el.asObjectOrNull() ?: return null
val type = obj["type"].asStringOrNull() ?: "text"
return if (type == "text") {
ChatMessageContent(type = "text", text = obj["text"].asStringOrNull())
} else {
ChatMessageContent(
type = type,
mimeType = obj["mimeType"].asStringOrNull(),
fileName = obj["fileName"].asStringOrNull(),
base64 = obj["content"].asStringOrNull(),
)
}
}
private fun parseSessions(jsonString: String): List<ChatSessionEntry> {
val root = json.parseToJsonElement(jsonString).asObjectOrNull() ?: return emptyList()
val sessions = root["sessions"].asArrayOrNull() ?: return emptyList()
@@ -574,6 +630,34 @@ class ChatController(
}
}
internal fun isCurrentHistoryLoad(
requestedSessionKey: String,
currentSessionKey: String,
requestGeneration: Long,
activeGeneration: Long,
): Boolean = requestedSessionKey == currentSessionKey && requestGeneration == activeGeneration
internal fun parseChatMessageContent(el: JsonElement): ChatMessageContent? {
val obj = el.asObjectOrNull() ?: return null
return when (obj["type"].asStringOrNull() ?: "text") {
"text", "input_text", "output_text" ->
ChatMessageContent(
type = "text",
text = obj["text"].asStringOrNull() ?: obj["content"].asStringOrNull(),
)
"image" ->
ChatMessageContent(
type = "image",
mimeType = obj["mimeType"].asStringOrNull(),
fileName = obj["fileName"].asStringOrNull(),
base64 = obj["content"].asStringOrNull()?.takeIf { it.isNotBlank() },
)
else -> null
}
}
internal data class MainSessionState(
val currentSessionKey: String,
val appliedMainSessionKey: String,
@@ -620,11 +704,54 @@ internal fun reconcileMessageIds(
}
}
internal fun mergeOptimisticMessages(
incoming: List<ChatMessage>,
optimistic: Collection<ChatMessage>,
): List<ChatMessage> {
if (optimistic.isEmpty()) return incoming
val unmatchedIncoming = incoming.toMutableList()
val missingOptimistic =
optimistic.filter { message ->
val matchIndex =
unmatchedIncoming.indexOfFirst { incomingMessage ->
incomingMessageConsumesOptimistic(incomingMessage, message)
}
if (matchIndex >= 0) {
unmatchedIncoming.removeAt(matchIndex)
false
} else {
true
}
}
if (missingOptimistic.isEmpty()) return incoming
return (incoming + missingOptimistic).sortedWith(compareBy<ChatMessage> { it.timestampMs ?: Long.MAX_VALUE }.thenBy { it.id })
}
internal fun messageIdentityKey(message: ChatMessage): String? {
val contentKey = messageContentIdentityKey(message) ?: return null
val timestamp = message.timestampMs?.toString().orEmpty()
if (timestamp.isEmpty() && contentKey.isEmpty()) return null
return listOf(contentKey, timestamp).joinToString(separator = "|")
}
private fun optimisticMessageIdentityKey(message: ChatMessage): String? = messageContentIdentityKey(message)
private fun incomingMessageConsumesOptimistic(
incoming: ChatMessage,
optimistic: ChatMessage,
): Boolean {
if (optimisticMessageIdentityKey(incoming) != optimisticMessageIdentityKey(optimistic)) return false
val incomingTimestamp = incoming.timestampMs ?: return false
val optimisticTimestamp = optimistic.timestampMs ?: return true
return incomingTimestamp >= optimisticTimestamp
}
private fun messageContentIdentityKey(message: ChatMessage): String? {
val role = message.role.trim().lowercase()
if (role.isEmpty()) return null
val timestamp = message.timestampMs?.toString().orEmpty()
val contentFingerprint =
message.content.joinToString(separator = "\u001E") { part ->
listOf(
@@ -642,8 +769,7 @@ internal fun messageIdentityKey(message: ChatMessage): String? {
).joinToString(separator = "\u001F")
}
if (timestamp.isEmpty() && contentFingerprint.isEmpty()) return null
return listOf(role, timestamp, contentFingerprint).joinToString(separator = "|")
return listOf(role, contentFingerprint).joinToString(separator = "|")
}
private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject

View File

@@ -1,12 +1,14 @@
package ai.openclaw.app.gateway
import android.util.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@@ -68,12 +70,27 @@ data class GatewayConnectErrorDetails(
val reason: String? = null,
)
data class GatewayHelloSummary(
val serverName: String?,
val remoteAddress: String?,
val serverVersion: String?,
val mainSessionKey: String?,
val updateAvailable: GatewayUpdateAvailableSummary?,
)
data class GatewayUpdateAvailableSummary(
val currentVersion: String?,
val latestVersion: String?,
val channel: String?,
)
private data class SelectedConnectAuth(
val authToken: String?,
val authBootstrapToken: String?,
val authDeviceToken: String?,
val authPassword: String?,
val signatureToken: String?,
val storedScopes: List<String>,
val authSource: GatewayConnectAuthSource,
val attemptedDeviceTokenRetry: Boolean,
)
@@ -86,7 +103,7 @@ class GatewaySession(
private val scope: CoroutineScope,
private val identityStore: DeviceIdentityStore,
private val deviceAuthStore: DeviceAuthTokenStore,
private val onConnected: (serverName: String?, remoteAddress: String?, mainSessionKey: String?) -> Unit,
private val onConnected: (GatewayHelloSummary) -> Unit,
private val onDisconnected: (message: String) -> Unit,
private val onEvent: (event: String, payloadJson: String?) -> Unit,
private val onInvoke: (suspend (InvokeRequest) -> InvokeResult)? = null,
@@ -149,7 +166,10 @@ class GatewaySession(
val tls: GatewayTlsParams?,
)
private var desired: DesiredConnection? = null
private val lifecycleLock = Any()
@Volatile private var desired: DesiredConnection? = null
private var job: Job? = null
@Volatile private var currentConnection: Connection? = null
@@ -168,26 +188,39 @@ class GatewaySession(
options: GatewayConnectOptions,
tls: GatewayTlsParams? = null,
) {
desired = DesiredConnection(endpoint, token, bootstrapToken, password, options, tls)
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
if (job == null) {
job = scope.launch(Dispatchers.IO) { runLoop() }
val connectionToClose: Connection?
synchronized(lifecycleLock) {
desired = DesiredConnection(endpoint, token, bootstrapToken, password, options, tls)
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
connectionToClose = currentConnection
if (job?.isActive != true) {
job = scope.launch(Dispatchers.IO) { runLoop() }
}
}
connectionToClose?.closeQuietly()
}
fun disconnect() {
desired = null
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
currentConnection?.closeQuietly()
scope.launch(Dispatchers.IO) {
job?.cancelAndJoin()
val jobToCancel: Job?
val connectionToClose: Connection?
synchronized(lifecycleLock) {
desired = null
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
connectionToClose = currentConnection
jobToCancel = job
job = null
pluginSurfaceUrls = emptyMap()
mainSessionKey = null
}
connectionToClose?.closeQuietly()
scope.launch(Dispatchers.IO) {
jobToCancel?.cancelAndJoin()
if (desired == null) {
pluginSurfaceUrls = emptyMap()
mainSessionKey = null
}
onDisconnected("Offline")
}
}
@@ -316,6 +349,22 @@ class GatewaySession(
return RpcResult(ok = res.ok, payloadJson = res.payloadJson, error = res.error)
}
suspend fun sendRequestFrame(
method: String,
paramsJson: String?,
timeoutMs: Long = 15_000,
onError: (ErrorShape) -> Unit = {},
) {
val conn = currentConnection ?: throw IllegalStateException("not connected")
val params =
if (paramsJson.isNullOrBlank()) {
null
} else {
json.parseToJsonElement(paramsJson)
}
conn.sendRequestFrame(method = method, params = params, timeoutMs = timeoutMs, onError = onError)
}
private data class RpcResponse(
val id: String,
val ok: Boolean,
@@ -338,6 +387,22 @@ class GatewaySession(
private val client: OkHttpClient = buildClient()
private var socket: WebSocket? = null
private val loggerTag = "OpenClawGateway"
private val incomingMessages = Channel<String>(Channel.UNLIMITED)
private val messagePumpJob =
scope.launch(Dispatchers.IO) {
for (text in incomingMessages) {
try {
handleMessage(text)
} catch (err: CancellationException) {
throw err
} catch (err: Throwable) {
Log.w(
loggerTag,
"gateway message handling failed: ${err.message ?: err::class.java.simpleName}",
)
}
}
}
val remoteAddress: String = formatGatewayAuthority(endpoint.host, endpoint.port)
@@ -360,14 +425,12 @@ class GatewaySession(
val id = UUID.randomUUID().toString()
val deferred = CompletableDeferred<RpcResponse>()
pending[id] = deferred
val frame =
buildJsonObject {
put("type", JsonPrimitive("req"))
put("id", JsonPrimitive(id))
put("method", JsonPrimitive(method))
if (params != null) put("params", params)
}
sendJson(frame)
try {
sendJson(buildRequestFrame(id = id, method = method, params = params))
} catch (err: Throwable) {
pending.remove(id)
throw err
}
return try {
withTimeout(timeoutMs) { deferred.await() }
} catch (err: TimeoutCancellationException) {
@@ -376,17 +439,66 @@ class GatewaySession(
}
}
suspend fun sendRequestFrame(
method: String,
params: JsonElement?,
timeoutMs: Long,
onError: (ErrorShape) -> Unit,
) {
val id = UUID.randomUUID().toString()
val deferred = CompletableDeferred<RpcResponse>()
pending[id] = deferred
try {
sendJson(buildRequestFrame(id = id, method = method, params = params))
} catch (err: Throwable) {
pending.remove(id)
throw err
}
scope.launch(Dispatchers.IO) {
val response =
try {
withTimeout(timeoutMs) { deferred.await() }
} catch (_: TimeoutCancellationException) {
pending.remove(id)
onError(ErrorShape("UNAVAILABLE", "request timeout"))
return@launch
}
if (!response.ok) {
onError(response.error ?: ErrorShape("UNAVAILABLE", "request failed"))
}
}
}
suspend fun sendJson(obj: JsonObject) {
val jsonString = obj.toString()
writeLock.withLock {
socket?.send(jsonString)
if (socket?.send(jsonString) != true) {
throw IllegalStateException("gateway send failed")
}
}
}
private fun buildRequestFrame(
id: String,
method: String,
params: JsonElement?,
): JsonObject =
buildJsonObject {
put("type", JsonPrimitive("req"))
put("id", JsonPrimitive(id))
put("method", JsonPrimitive(method))
if (params != null) put("params", params)
}
suspend fun awaitClose() = closedDeferred.await()
fun closeQuietly() {
if (isClosed.compareAndSet(false, true)) {
incomingMessages.close()
messagePumpJob.cancel()
if (!connectDeferred.isCompleted) {
connectDeferred.completeExceptionally(IllegalStateException("Gateway closed"))
}
socket?.close(1000, "bye")
socket = null
closedDeferred.complete(Unit)
@@ -431,7 +543,7 @@ class GatewaySession(
webSocket: WebSocket,
text: String,
) {
scope.launch { handleMessage(text) }
incomingMessages.trySend(text)
}
override fun onFailure(
@@ -443,6 +555,7 @@ class GatewaySession(
connectDeferred.completeExceptionally(t)
}
if (isClosed.compareAndSet(false, true)) {
incomingMessages.close()
failPending()
closedDeferred.complete(Unit)
onDisconnected("Gateway error: ${t.message ?: t::class.java.simpleName}")
@@ -458,6 +571,7 @@ class GatewaySession(
connectDeferred.completeExceptionally(IllegalStateException("Gateway closed: $reason"))
}
if (isClosed.compareAndSet(false, true)) {
incomingMessages.close()
failPending()
closedDeferred.complete(Unit)
onDisconnected("Gateway closed: $reason")
@@ -467,7 +581,8 @@ class GatewaySession(
private suspend fun sendConnect(connectNonce: String) {
val identity = identityStore.loadOrCreate()
val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role)?.trim()
val storedEntry = deviceAuthStore.loadEntry(identity.deviceId, options.role)
val storedToken = storedEntry?.token?.trim()
val selectedAuth =
selectConnectAuth(
endpoint = endpoint,
@@ -477,6 +592,7 @@ class GatewaySession(
explicitBootstrapToken = bootstrapToken?.trim()?.takeIf { it.isNotEmpty() },
explicitPassword = password?.trim()?.takeIf { it.isNotEmpty() },
storedToken = storedToken?.takeIf { it.isNotEmpty() },
storedScopes = storedEntry?.scopes.orEmpty(),
)
if (selectedAuth.attemptedDeviceTokenRetry) {
pendingDeviceTokenRetry = false
@@ -531,7 +647,6 @@ class GatewaySession(
setOf(
"operator.approvals",
"operator.read",
"operator.talk.secrets",
"operator.write",
)
scopes.filter { allowedOperatorScopes.contains(it) }.distinct().sorted()
@@ -574,7 +689,9 @@ class GatewaySession(
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
val serverName = obj["server"].asObjectOrNull()?.get("host").asStringOrNull()
val server = obj["server"].asObjectOrNull()
val serverName = server?.get("host").asStringOrNull()
val serverVersion = server?.get("version").asStringOrNull()
val authObj = obj["auth"].asObjectOrNull()
val deviceToken = authObj?.get("deviceToken").asStringOrNull()
val authRole = authObj?.get("role").asStringOrNull() ?: options.role
@@ -612,13 +729,33 @@ class GatewaySession(
?.let { normalized -> surface to normalized }
} ?: emptyList()
pluginSurfaceUrls = normalizedPluginSurfaceUrls.toMap()
val snapshot = obj["snapshot"].asObjectOrNull()
val sessionDefaults =
obj["snapshot"]
.asObjectOrNull()
snapshot
?.get("sessionDefaults")
.asObjectOrNull()
mainSessionKey = sessionDefaults?.get("mainSessionKey").asStringOrNull()
onConnected(serverName, remoteAddress, mainSessionKey)
onConnected(
GatewayHelloSummary(
serverName = serverName,
remoteAddress = remoteAddress,
serverVersion = serverVersion,
mainSessionKey = mainSessionKey,
updateAvailable = parseUpdateAvailable(snapshot?.get("updateAvailable").asObjectOrNull()),
),
)
}
private fun parseUpdateAvailable(value: JsonObject?): GatewayUpdateAvailableSummary? {
if (value == null) return null
val latestVersion = value["latestVersion"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val currentVersion = value["currentVersion"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val channel = value["channel"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
return GatewayUpdateAvailableSummary(
currentVersion = currentVersion,
latestVersion = latestVersion,
channel = channel,
)
}
private fun buildConnectParams(
@@ -658,6 +795,7 @@ class GatewaySession(
else -> null
}
val connectScopes = resolveConnectScopes(selectedAuth)
val signedAtMs = System.currentTimeMillis()
val payload =
DeviceAuthPayload.buildV3(
@@ -665,7 +803,7 @@ class GatewaySession(
clientId = client.id,
clientMode = client.mode,
role = options.role,
scopes = options.scopes,
scopes = connectScopes,
signedAtMs = signedAtMs,
token = selectedAuth.signatureToken,
nonce = connectNonce,
@@ -704,7 +842,7 @@ class GatewaySession(
)
}
put("role", JsonPrimitive(options.role))
if (options.scopes.isNotEmpty()) put("scopes", JsonArray(options.scopes.map(::JsonPrimitive)))
if (connectScopes.isNotEmpty()) put("scopes", JsonArray(connectScopes.map(::JsonPrimitive)))
authJson?.let { put("auth", it) }
deviceJson?.let { put("device", it) }
put("locale", JsonPrimitive(locale))
@@ -714,6 +852,16 @@ class GatewaySession(
}
}
private fun resolveConnectScopes(selectedAuth: SelectedConnectAuth): List<String> {
if (selectedAuth.authSource == GatewayConnectAuthSource.BOOTSTRAP_TOKEN) {
return filteredBootstrapHandoffScopes(options.role, options.scopes).orEmpty()
}
if (selectedAuth.authSource == GatewayConnectAuthSource.DEVICE_TOKEN && selectedAuth.storedScopes.isNotEmpty()) {
return selectedAuth.storedScopes
}
return options.scopes
}
private suspend fun handleMessage(text: String) {
val frame = json.parseToJsonElement(text).asObjectOrNull() ?: return
when (frame["type"].asStringOrNull()) {
@@ -905,9 +1053,11 @@ class GatewaySession(
conn.connect()
conn.awaitClose()
} finally {
currentConnection = null
pluginSurfaceUrls = emptyMap()
mainSessionKey = null
if (currentConnection === conn) {
currentConnection = null
pluginSurfaceUrls = emptyMap()
mainSessionKey = null
}
}
}
@@ -985,6 +1135,7 @@ class GatewaySession(
explicitBootstrapToken: String?,
explicitPassword: String?,
storedToken: String?,
storedScopes: List<String>,
): SelectedConnectAuth {
val shouldUseDeviceRetryToken =
pendingDeviceTokenRetry &&
@@ -1018,6 +1169,7 @@ class GatewaySession(
authDeviceToken = authDeviceToken,
authPassword = explicitPassword,
signatureToken = authToken ?: authBootstrapToken,
storedScopes = storedScopes,
authSource = authSource,
attemptedDeviceTokenRetry = shouldUseDeviceRetryToken,
)
@@ -1090,8 +1242,10 @@ internal fun shouldPauseGatewayReconnectAfterAuthFailure(
role?.trim() == "node" &&
scopes.isEmpty() &&
error.details.reason == "not-paired" &&
(error.details.pauseReconnect == false ||
error.details.recommendedNextStep == "wait_then_retry")
(
error.details.pauseReconnect == false ||
error.details.recommendedNextStep == "wait_then_retry"
)
)
"AUTH_TOKEN_MISMATCH" -> deviceTokenRetryBudgetUsed && !pendingDeviceTokenRetry
else -> false

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

@@ -22,6 +22,7 @@ class ConnectionManager(
private val readSmsAvailable: () -> Boolean,
private val smsSearchPossible: () -> Boolean,
private val callLogAvailable: () -> Boolean,
private val photosAvailable: () -> Boolean,
private val hasRecordAudioPermission: () -> Boolean,
private val manualTls: () -> Boolean,
) {
@@ -96,6 +97,7 @@ class ConnectionManager(
readSmsAvailable = readSmsAvailable(),
smsSearchPossible = smsSearchPossible(),
callLogAvailable = callLogAvailable(),
photosAvailable = photosAvailable(),
voiceWakeEnabled = voiceWakeMode() != VoiceWakeMode.Off && hasRecordAudioPermission(),
motionActivityAvailable = motionActivityAvailable(),
motionPedometerAvailable = motionPedometerAvailable(),
@@ -160,7 +162,12 @@ class ConnectionManager(
fun buildOperatorConnectOptions(): GatewayConnectOptions =
GatewayConnectOptions(
role = "operator",
scopes = listOf("operator.read", "operator.write", "operator.talk.secrets"),
scopes =
listOf(
"operator.approvals",
"operator.read",
"operator.write",
),
caps = emptyList(),
commands = emptyList(),
permissions = emptyMap(),

View File

@@ -28,6 +28,7 @@ class DeviceHandler(
private val appContext: Context,
private val smsEnabled: Boolean = SensitiveFeatureConfig.smsEnabled,
private val callLogEnabled: Boolean = SensitiveFeatureConfig.callLogEnabled,
private val photosEnabled: Boolean = SensitiveFeatureConfig.photosEnabled,
) {
companion object {
internal fun hasAnySmsCapability(
@@ -150,7 +151,9 @@ class DeviceHandler(
val smsReadGranted = hasPermission(Manifest.permission.READ_SMS)
val notificationAccess = DeviceNotificationListenerService.isAccessEnabled(appContext)
val photosGranted =
if (Build.VERSION.SDK_INT >= 33) {
if (!photosEnabled) {
false
} else if (Build.VERSION.SDK_INT >= 33) {
hasPermission(Manifest.permission.READ_MEDIA_IMAGES)
} else {
hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
@@ -248,7 +251,7 @@ class DeviceHandler(
"photos",
permissionStateJson(
granted = photosGranted,
promptableWhenDenied = true,
promptableWhenDenied = photosEnabled,
),
)
put(

View File

@@ -23,6 +23,7 @@ data class NodeRuntimeFlags(
val readSmsAvailable: Boolean,
val smsSearchPossible: Boolean,
val callLogAvailable: Boolean,
val photosAvailable: Boolean,
val voiceWakeEnabled: Boolean,
val motionActivityAvailable: Boolean,
val motionPedometerAvailable: Boolean,
@@ -37,6 +38,7 @@ enum class InvokeCommandAvailability {
ReadSmsAvailable,
RequestableSmsSearchAvailable,
CallLogAvailable,
PhotosAvailable,
MotionActivityAvailable,
MotionPedometerAvailable,
DebugBuild,
@@ -48,6 +50,7 @@ enum class NodeCapabilityAvailability {
LocationEnabled,
SmsAvailable,
CallLogAvailable,
PhotosAvailable,
VoiceWakeEnabled,
MotionAvailable,
}
@@ -87,7 +90,10 @@ object InvokeCommandRegistry {
name = OpenClawCapability.Location.rawValue,
availability = NodeCapabilityAvailability.LocationEnabled,
),
NodeCapabilitySpec(name = OpenClawCapability.Photos.rawValue),
NodeCapabilitySpec(
name = OpenClawCapability.Photos.rawValue,
availability = NodeCapabilityAvailability.PhotosAvailable,
),
NodeCapabilitySpec(name = OpenClawCapability.Contacts.rawValue),
NodeCapabilitySpec(name = OpenClawCapability.Calendar.rawValue),
NodeCapabilitySpec(
@@ -188,6 +194,7 @@ object InvokeCommandRegistry {
),
InvokeCommandSpec(
name = OpenClawPhotosCommand.Latest.rawValue,
availability = InvokeCommandAvailability.PhotosAvailable,
),
InvokeCommandSpec(
name = OpenClawContactsCommand.Search.rawValue,
@@ -244,6 +251,7 @@ object InvokeCommandRegistry {
NodeCapabilityAvailability.LocationEnabled -> flags.locationEnabled
NodeCapabilityAvailability.SmsAvailable -> flags.sendSmsAvailable || flags.readSmsAvailable
NodeCapabilityAvailability.CallLogAvailable -> flags.callLogAvailable
NodeCapabilityAvailability.PhotosAvailable -> flags.photosAvailable
NodeCapabilityAvailability.VoiceWakeEnabled -> flags.voiceWakeEnabled
NodeCapabilityAvailability.MotionAvailable -> flags.motionActivityAvailable || flags.motionPedometerAvailable
}
@@ -260,6 +268,7 @@ object InvokeCommandRegistry {
InvokeCommandAvailability.ReadSmsAvailable -> flags.readSmsAvailable
InvokeCommandAvailability.RequestableSmsSearchAvailable -> flags.smsSearchPossible
InvokeCommandAvailability.CallLogAvailable -> flags.callLogAvailable
InvokeCommandAvailability.PhotosAvailable -> flags.photosAvailable
InvokeCommandAvailability.MotionActivityAvailable -> flags.motionActivityAvailable
InvokeCommandAvailability.MotionPedometerAvailable -> flags.motionPedometerAvailable
InvokeCommandAvailability.DebugBuild -> flags.debugBuild

View File

@@ -77,6 +77,7 @@ class InvokeDispatcher(
private val smsFeatureEnabled: () -> Boolean,
private val smsTelephonyAvailable: () -> Boolean,
private val callLogAvailable: () -> Boolean,
private val photosAvailable: () -> Boolean,
private val debugBuild: () -> Boolean,
private val onCanvasA2uiPush: () -> Unit,
private val onCanvasA2uiReset: () -> Unit,
@@ -325,6 +326,15 @@ class InvokeDispatcher(
message = "CALL_LOG_UNAVAILABLE: call log not available on this build",
)
}
InvokeCommandAvailability.PhotosAvailable ->
if (photosAvailable()) {
null
} else {
GatewaySession.InvokeResult.error(
code = "PHOTOS_UNAVAILABLE",
message = "PHOTOS_UNAVAILABLE: photos not available on this build",
)
}
InvokeCommandAvailability.DebugBuild ->
if (debugBuild()) {
null

View File

@@ -27,28 +27,23 @@ internal object JpegSizeLimiter {
require(initialWidth > 0 && initialHeight > 0) { "Invalid image size" }
require(maxBytes > 0) { "Invalid maxBytes" }
val clampedStartQuality = startQuality.coerceIn(minQuality, 100)
var width = initialWidth
var height = initialHeight
val clampedStartQuality = startQuality.coerceIn(minQuality, 100)
var best =
JpegSizeLimiterResult(
bytes = encode(width, height, clampedStartQuality),
width = width,
height = height,
quality = clampedStartQuality,
)
if (best.bytes.size <= maxBytes) return best
var best: JpegSizeLimiterResult? = null
repeat(maxScaleAttempts) {
repeat(maxScaleAttempts + 1) { scaleAttempt ->
var quality = clampedStartQuality
repeat(maxQualityAttempts) {
val bytes = encode(width, height, quality)
best = JpegSizeLimiterResult(bytes = bytes, width = width, height = height, quality = quality)
val attempt = JpegSizeLimiterResult(bytes = bytes, width = width, height = height, quality = quality)
best = attempt
if (bytes.size <= maxBytes) return best
if (quality <= minQuality) return@repeat
quality = max(minQuality, (quality * 0.75).roundToInt())
}
if (scaleAttempt == maxScaleAttempts) return@repeat
val minScale = (minSize.toDouble() / min(width, height).toDouble()).coerceAtMost(1.0)
val nextScale = max(scaleStep, minScale)
val nextWidth = max(minSize, (width * nextScale).roundToInt())
@@ -58,10 +53,11 @@ internal object JpegSizeLimiter {
height = min(nextHeight, height)
}
if (best.bytes.size > maxBytes) {
throw IllegalStateException("CAMERA_TOO_LARGE: ${best.bytes.size} bytes > $maxBytes bytes")
val failed = checkNotNull(best)
if (failed.bytes.size > maxBytes) {
throw IllegalStateException("CAMERA_TOO_LARGE: ${failed.bytes.size} bytes > $maxBytes bytes")
}
return best
return failed
}
}

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