* style: restore exec approval e2e formatting
* fix(install): trap SIGINT so Ctrl+C exits cleanly during upgrade doctor
Three changes to fix the install script's Ctrl+C handling:
1. Add INT/TERM signal traps that clean up temp files and exit with
the correct signal exit codes (130 for SIGINT, 143 for SIGTERM).
2. Preserve signal exit codes (>128) through run_quiet_step so the
doctor path can distinguish user cancellation from normal errors.
Non-signal failures still return 1, preserving existing caller
semantics for all other installer steps.
3. Fix guardCancel in onboard-helpers.ts: exit(0) changed to exit(1)
so Clack prompt cancellation (Escape/Ctrl+C) is treated as failure,
not success. This prevents the installer from continuing with plugin
updates after the user explicitly cancelled.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* fix(install): abort dashboard launch on doctor cancellation
When a user cancels the interactive upgrade-doctor prompt (Clack
cancellation exits 1, SIGINT exits 130), clear should_open_dashboard
so the installer does not launch a dead dashboard after an incomplete
upgrade.
Also propagate non-zero exit from run_doctor() so the non-interactive
upgrade path correctly skips dashboard launch on failure.
* fix: guard every run_doctor caller and add focused tests
The existing-config path called run_doctor without checking its return
value, so a failed or cancelled doctor would still launch the dashboard.
Now both run_doctor call sites guard the return value with if-then.
Adds focused tests verifying: every run_doctor caller is guarded,
dashboard flag is cleared on doctor failure, signal exit codes
propagate through run_quiet_step, and SIGINT (exit 130) triggers
abort_install_int.
* retrigger proof check
* fix: exit 130 on Clack cancellation so installer treats it as SIGINT
guardCancel now exits with 130 (SIGINT convention) instead of 1. When
the user presses Ctrl+C at an interactive doctor prompt, the installer
sees doctor_exit=130 and calls abort_install_int, aborting cleanly
instead of continuing after exit 1.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* fix: narrow exit 130 to doctor-prompter path only
Revert guardCancel to exit 0 by default (matching main) and pass
exit code 130 only from doctor-prompter where the installer needs
to distinguish user cancellation from normal failures.
This preserves the existing cancellation behavior for configure,
wizard, gateway, and daemon prompts while keeping the SIGINT
convention for the installer's doctor subprocess.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
---------
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Summary:
- Merged feat(tencent): add Tencent Hy3 provider (TokenHub and TokenPlan) after ClawSweeper review.
Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(tencent): preserve TokenHub auth compatibility
- Included post-review commit in the final squash: refactor(tencent): unify TokenPlan env/flag naming with TokenHub
- Included post-review commit in the final squash: docs: refresh Tencent provider docs metadata
- Included post-review commit in the final squash: fix: allow TokenPlan provider config overlays
- Included post-review commit in the final squash: docs: dedupe Tencent provider glossary labels
- Included post-review commit in the final squash: fix(tencent): repair TokenHub model defaults
Validation:
- ClawSweeper review passed for head 30c9fc130f.
- Required merge gates passed before the squash merge.
Prepared head SHA: 30c9fc130f
Review: https://github.com/openclaw/openclaw/pull/99076#issuecomment-4888527271
Co-authored-by: leisang <leisang@tencent.com>
Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
The relevant-mainline-drift check in scripts/pr merge-verify hard-failed
any landing whose prepared head predated a sibling merge, serializing all
agent landings behind a fresh CI cycle per merged PR. Required checks
still gate at the prepared head and GitHub mergeable state still blocks
real conflicts. OPENCLAW_PR_STRICT_DRIFT=1 restores the previous hard
gate for release-critical flows.
* feat(ios): read-only offline cache for chat sessions and transcripts
Cache-first cold open for the iOS chat tab: the last known transcript and
session list render immediately from a local SQLite cache, then live gateway
history replaces them wholesale through the existing reconciliation path.
When the gateway is unreachable, recent sessions and transcripts stay
browsable read-only; sending remains gated by the existing connection state.
- New OpenClawChatTranscriptCache protocol seam plus SQLite-backed store in
OpenClawChatUI (raw SQLite3, no new dependencies), scoped per gateway
stableID so transcripts never leak across paired gateways.
- Bounds: 50 sessions, 50 transcripts, 200 messages per session; text rows
only (attachment/binary payloads and tool arguments are stripped).
- Disposable cache: schema versioned via user_version; any open/schema/decode
mismatch drops and rebuilds silently. File protection
completeUntilFirstUserAuthentication on iOS.
- View model pre-paints from cache only until a live response applies
(hasAppliedLiveHistory/hasAppliedLiveSessions guards); write-through is
chained and off the render path.
- iOS wiring keys the chat view model identity on transport mode plus cache
gateway ID so switching paired gateways rebuilds the view model.
Part of #100194
* fix(ios): harden offline transcript cache
* fix(ios): expose offline cached sessions
* fix(ios): isolate transcript caches by gateway
* chore(i18n): sync native inventory
* fix(ios): allow cache extension to replace messages
* fix(diffs): share SSR preloads and repair language-pack hydration
Render viewer and file documents from a single @pierre/diffs SSR preload
per file (mode=both previously ran the full diff+highlight pipeline
twice; 651ms -> 303ms on an 8-file patch), apply the file-mode font bump
as a document-level override, and keep hydration payloads
variant-faithful.
Fix the language-pack runtime downgrading pack-only languages to plain
text at hydration by defining a per-target build flag and forwarding it
to payload normalization.
Also: case-insensitive language hints, identical before/after
short-circuit with details.changed, patch input failures classified as
tool input errors, canonical config values now win over deprecated
aliases, hash-pinned viewer runtime served immutable, truthful
browser-vs-render errors, timing-safe artifact token compare, unref
idle browser timer.
* docs(changelog): link diffs rendering entry to PR
* test(diffs): narrow manifest validation results before value access
* test(tooling): allowlist diffs viewer-client define suppression
* fix(cron): reject sub-millisecond durations
* fix(skill-workshop): preserve proposal terminal newline
Preserve proposal_content exactly at the agent tool boundary and make
renderProposalMarkdown defensively emit a terminal newline.
Add focused regressions for the tool write path and markdown renderer.
* fix(skill-workshop): reject blank raw proposal content
* fix: treat empty-string optional integer tool params as unset
Optional positive-integer tool params (e.g. Telegram replyTo/threadId)
threw ToolInputError when a tool-calling model populated them with an
empty-string or whitespace-only default. Those defaults carry no value,
so readPositiveIntegerParam/readNonNegativeIntegerParam now treat a
blank string as unset (undefined) instead of throwing, while still
rejecting genuinely invalid present values (0, "42.5", "-3"). This
prevents silent message-delivery failures when models emit empty
routing-param defaults. Adds unit tests covering blank vs invalid.
* fix(gateway): log start session persistence failures
The gateway session-lifecycle "start" event persistence
(persistGatewaySessionLifecycleEvent, which surfaces write failures via
requireWriteSuccess) was fired as void ...catch(() => undefined), swallowing
the rejection with zero logging. A failed start-marker write silently dropped
the run's start record from restart-recovery accounting, with no
operator-visible trace.
The sibling terminal-phase catch already logs this since #97839; the start
path was the unfixed sibling. Mirror that fix: log the swallowed start-phase
persistence failure with the same redacted message shape via formatForLog,
keeping the fire-and-forget semantics unchanged. Adds a focused regression
test asserting the log fires on start-persist rejection.
* fix(memory): report close-time pending work failures
* fix(shared): return "" from sliceUtf16Safe when end <= start, matching native .slice
sliceUtf16Safe silently swapped reversed bounds (to < from) instead of
returning "" like String.prototype.slice, creating a subtle footgun for
callers with dynamic start/end pairs.
Caller scan across src/, extensions/, and packages/ confirmed no production
code relies on the old swap behavior — all callers use (text, 0, N) or
(text, -N) forms only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(plugins): require plugin manifest in npm verifier
* fix(android): propagate CancellationException in CameraHandler catch blocks
Catch (err: Throwable) swallows kotlinx.coroutines.CancellationException,
breaking structured concurrency when the coroutine scope is cancelled
during camera operations (handleList/handleSnap/handleClip).
Add CancellationException rethrow before each Throwable catch to match
the existing pattern used in GatewaySession and TalkModeManager.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(android): propagate CancellationException past GatewaySession invoke boundary
* chore: sync native i18n inventory after gateway session line shift
* fix(agents): prevent native hook relay bridge race condition on renew and registration
Remove synchronous bridge record write after server.listen() that races
before the TCP server binds, and guard renew handler with server.listening
check to prevent stale relay registrations.
Closes#98650
* fix: harden small reliability fixes
Co-authored-by: cxbAsDev <cxbAsDev@users.noreply.github.com>
* docs(agents): explain buffered LSP spawn failures
* docs(agents): clarify LSP spawn timeout invariant
---------
Co-authored-by: qingminlong <qing.minlong@xydigit.com>
Co-authored-by: anyech <anyech@gmail.com>
Co-authored-by: snotty <snotty@users.noreply.github.com>
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
Co-authored-by: lin-hongkuan <lin-hongkuan@users.noreply.github.com>
Co-authored-by: simon-w <weng.qimeng@xydigit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: 宇宙熊Yzx <53250620+849261680@users.noreply.github.com>
Co-authored-by: xialonglee <li.xialong@xydigit.com>
Co-authored-by: nankingjing <1079826437@qq.com>
Co-authored-by: cxbAsDev <cxbAsDev@users.noreply.github.com>
* fix(tui): show last tool-error summary on aborted runs
When a run ends while looping on tool-call validation errors the TUI showed
only "run aborted". Carry the last tool failure on the terminal lifecycle
metadata and render a sanitized one-line summary on the abort line, e.g.
"run aborted: edit tool validation failed: edits: must have required properties edits".
Argument dumps are stripped; with no summary it stays "run aborted".
Refs #90982
* fix(tui): secure abort validation diagnostics
* docs(changelog): move TUI fix to unreleased
* docs(changelog): aggregate TUI fixes
* fix(tui): scope abort validation diagnostics
* docs(changelog): restore unreleased spacing
* fix(tui): scope abort validation diagnostics
* fix(tui): scope abort validation diagnostics
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(scripts): serialize pr prepare gates and add remote testbox test gate
Concurrent scripts/pr gate runs across .worktrees queued on the shared
heavy-check lock mid-test: the queued run's children hit the 10-minute
lock timeout while its unlocked build stage piled CPU load onto the
holder's vitest shards, which then stalled past the 120s no-output
watchdog and were SIGTERMed with zero real test failures (observed
landing PRs #99935/#100026 on a loaded maintainer Mac).
- scripts/pr-gates-lock.mjs holds the shared heavy-check lock for the
whole local gate block; gate stages inherit the existing *_LOCK_HELD
child contract, so concurrent gate runs now queue as whole units
before their first command.
- OPENCLAW_PR_GATES_REMOTE=testbox runs the full-suite pnpm test gate on
a Blacksmith Testbox via scripts/crabbox-wrapper.mjs (same delegation
as check:changed). The tbx_ lease id and Actions run URL from the
crabbox --timing-json report are recorded in .local/gates.env
(REMOTE_GATES_*) and .local/prep.md. Local remains the default;
pnpm build/check stay local.
Formatting verified with the primary checkout's oxfmt (hook bypassed:
linked worktree has no hydrated node_modules).
* fix(scripts): refresh the gate stamp when lease-retry gates rerun for a rebased head
The lease-retry path reran build/check/test for the rebased prep head but
left .local/gates.env describing the pre-push head, so prep.md/prep.env
attributed stale evidence (including the new remote testbox lease id) to
the pushed commit. Extract write_gates_env_stamp as the single stamp
writer, rewrite the stamp from the retry path for all modes, and re-source
gates.env in prepare_push after the push settles.
Found by autoreview (codex/gpt-5.5); formatting verified with the primary
checkout's oxfmt (hook bypassed: linked worktree has no node_modules).
* fix(scripts): harden remote PR gate evidence
* fix(scripts): serialize complete gate setup
* fix(scripts): clear stale docs gate proof
* ci: guard gateway protocol event coverage for iOS/Android clients (#100198)
Adds scripts/check-protocol-event-coverage.mjs, which derives the
server->client event catalog from GATEWAY_EVENTS in
src/gateway/server-methods-list.ts, extracts the events each mobile app
handles from Swift/Kotlin dispatch sites, and fails on gateway events no
client handles unless allowlisted with a reason in
scripts/protocol-event-coverage.allowlist.json. Wired as
pnpm check:protocol-coverage in the CI guards shard.
* fix(ci): scope Kotlin event extraction to handle*Event dispatch functions (#100198)
Bare event == "..." literals in predicate helpers outside the dispatch
path (gatewayEventInvalidatesNodesDevices in NodeRuntime.kt, which has no
production caller) counted as Android coverage, silently masking that
node.pair.requested/resolved have no live handler. Kotlin extraction now
only reads when(event) labels and event comparisons inside fun
handle*Event(...) bodies; node.pair.* moved to the Android allowlist with
a truthful reason. Swift extraction stays tree-wide because consumption
there always reads .event off a received EventFrame.
* fix(android): remove dead node pairing event helper
* fix(ci): preserve protocol allowlist parse errors
* test(ci): align tooling import plan
Externalized provider plugins (qwen, moonshot, zai, deepseek, groq, cerebras, chutes) are excluded from dist packaging, so their manifest-declared endpoint classes were invisible to installed gateways and built source checkouts: DashScope/Moonshot-class base URLs classified as custom, breaking image prompt placement, streaming-usage and developer-role compat defaults, and deterministically failing image.test.ts / model-compat DashScope cases after pnpm build. The official external provider catalog now mirrors each plugin's providerEndpoints and provider attribution appends catalog metadata after installed/bundled manifests (first match wins; repo-bundled catalog only, hosted feeds never influence classification). Includes a catalog-manifest mirror contract test and a dist-simulation regression test.