58 Commits
Author SHA1 Message Date
Masato HoshinoandGitHub 6fb3aae404 fix(gateway): stop the unmanaged gateway named by its lock (#111378)
`openclaw gateway stop` resolved the unmanaged fallback port from config and
discovered pids through lsof only. On hosts without lsof, and whenever the
gateway runs on a port other than the configured one, discovery came back empty
and the command reported `Gateway service disabled.` with `ok:true` and exit 0
while the gateway kept serving.

The gateway lock already holds the verified owner pid and port. Restart learned
to read the lock port in #105241 to keep an unmanaged restart honest when the
configured port drifts; stop's fallback never did. Read the lock identity once in
the not-loaded fallback, use its port for discovery, and signal its owner when no
listener is found. Verified listeners still win when lsof is available.

Signalling still goes through `signalVerifiedGatewayPidSync`, which re-reads argv
immediately before SIGTERM, and lock identities are only returned after a
liveness and start-time or argv check, so dead, recycled, port-less and
non-gateway lock owners are refused and the command stays `not-loaded`.

Closes #72948
2026-07-19 10:20:31 -07:00
bbaaac4b44 fix(ui): preserve non-minute cron intervals on edit (#110034)
The Control UI rounded any every-schedule interval that is not a whole
number of minutes up to the next minute when reading a job into the edit
form (parseEverySchedule ceil): 30s and 90s and 4m6s all became a whole
number of minutes. Because addCronJob always rebuilds the schedule into
the cron.update patch, any save — including metadata-only edits that
never touch the schedule — silently rewrote the cadence (e.g. 30s → 60s,
90s → 120s) and re-anchored the job. CLI, API, and docs accept these
intervals, and the UI itself can create them, so the editor corrupted
cadences supported everywhere else.

Add a Seconds interval unit and read everyMs back into the largest unit
that divides it exactly (days → hours → minutes → seconds), rendering
sub-second remainders as exact decimal seconds built with BigInt
quotient/remainder. Every integer millisecond up to
Number.MAX_SAFE_INTEGER now round-trips losslessly, so a resave with the
same everyMs preserves the gateway's anchor inheritance. Gateway,
protocol, storage, and cron runtime are unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 03:34:31 +01:00
cc0a977dd5 fix(memory-wiki): prevent source ingest content loss during concurrent vault writes (#104209)
* fix(memory-wiki): serialize source ingest with vault mutations

* test(memory-wiki): make ingest lock proof deterministic

* test(memory-wiki): type queue spy receiver

* test(memory-wiki): keep queue spy lint-safe

* style(memory-wiki): format ingest lock proof

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-18 01:47:01 +01:00
45ab24dac4 fix: queued MEDIA attachments survive delivery retries (#108969)
* fix: queued MEDIA attachments survive delivery retries

Local media carried in a legacy MEDIA: text directive was invisible to
queue staging (which reads only structured mediaUrl(s)), so the raw
directive was persisted with no queue-owned copy and a retry read the
already-deleted producer path — burning the retry budget and dropping the
attachment plus its text.

Project each source payload's effective media through the canonical
createOutboundPayloadPlan before staging and fold directive-derived local
sources into the queue copy's structured fields, so the existing spool
takes custody. The custody copy anchors both mediaUrl and mediaUrls to the
effective set so the staged copy overrides the preserved in-text directive
on replay; the rendered-batch plan is recomputed from the deduplicated
effective media to avoid count inflation. Raw pre-hook text, the copy-free
live send, the same media capability, and sensitive-media fail-closed are
all unchanged. No schema/config/TTL change.

Extends the ownership invariant from #108501 / #108502 to the legacy
MEDIA: text-directive carrier.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(outbound): localize queue media custody

Trim the direct helper matrix in favor of end-to-end queue recovery proof.

Co-authored-by: Masato Hoshino <g515hoshino@gmail.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 23:15:08 -07:00
fa9c3209d3 fix: queued voice replies are lost when the delivery queue retries them (#108502)
* fix(outbound): keep queued TTS/local media alive past its producer process

Outbound TTS synthesizes into a producer-owned temp and the raw path is
persisted into the durable delivery queue with no copy. The temp is removed
when its producer exits (and by a 5-minute timer well inside the queue's
~22-minute retry budget), so a deferred delivery replayed by a fresh recovery
process reads a path whose owner already deleted it: the send burns all five
retries on a missing file and the voice is never delivered.

Give the queue its own copy. Before a deliverable row is published, each
authorized local source is read through the same media access the live send
uses and copied into a process-generation-owned artifact root outside the
media TTL sweep; only the queue payload is rewritten, so the live send stays
copy-free on the original path. Custody is proven by owner identity rather
than by a marker file or lease table: artifacts are grouped under
<pid>-<processStartTime>-<nonce>, and reclaim touches a generation only when
its owner is provably gone. A live owner, an unverifiable owner, or a
recycled PID whose start time no longer matches are all handled without
deleting media that a pending row still needs. Wall-clock age is never an
ownership signal.

Ordering is durable-state-first: rows commit before artifacts are unlinked,
so a crash between the two leaves an orphan for reclaim rather than a
delivery that lost its media. Sensitive media reaches neither the spool nor
the row, so required sends fail closed and best-effort sends go live-only
instead of persisting a row that cannot replay.

No schema change, no public SDK surface, no lease/owner table.

* fix(outbound): resolve queue media owner identity on every platform

Generation ownership read process start time through the Linux/Darwin lock
reader only, so on Windows every generation stayed unverifiable and its
artifacts were never reclaimed. gateway-lock already paired that reader with
the Windows CIM/WMIC creation-time reader behind its own private helper, so
the pairing was duplicated policy waiting to drift.

Move the pair behind one owner-identity helper and route both callers through
it. No new process inspection: Windows still resolves through
readWindowsProcessStartTimeSync, Linux/Darwin still through
getFileLockProcessStartTime, and neither platform's behavior changes.

Also cover the production crash boundary end to end: a real child process
stages media, commits the row, and is killed before dispatch; a fresh process
then reclaims the dead generation while retaining the artifact its pending row
still needs, reads the same bytes after the producer's source is deleted, and
drops the artifact once the row is acked.

* fix(outbound): close reclaim race and partial-stage leak in queue media

Two defects found by external review of the previous commits.

Reclaim built its retain set before proving generation ownership, so a
short-lived producer (`openclaw message send`) that staged, committed its row,
and exited while a sweep was running would have its artifact judged against a
snapshot taken before that commit: the sweep saw a dead owner, missed the new
reference, and unlinked media a pending row still needed. The retain set is now
read after each death proof. That is sufficient rather than merely narrower: a
process only ever stages into its own generation, so once an owner is proven
dead no further references to it can appear, and everything it committed before
exiting is already durable.

Staging also leaked on partial failure. When a later source in an entry failed,
earlier copies were already published, no row would ever reference them, and
reclaim deliberately never touches a live owner's generation, so they
accumulated for the process lifetime. Failed staging now releases what it
published. Per-payload media staging is sequential for the same reason: a
concurrent copy could land after the cleanup ran and orphan itself anyway,
and one source at a time also bounds peak memory.

* fix(outbound): resolve queue media access exactly as the live send does

Queue staging read media through params.mediaAccess directly, while the live
send resolves an agent-scoped capability first. The two gates therefore
disagreed: agent-generated media under the agent workspace is reachable only
through the agent-scoped roots, so staging rejected sources the send itself
would have delivered. A required send failed outright, and a best-effort send
silently lost its write-ahead row for exactly the media class this change is
meant to protect.

Resolve the capability once and share it, so staging can neither reject media
the send would deliver nor read more than the send may. The live send now
routes through the same helper rather than keeping a second copy of the
argument list.

The spool test previously fabricated an explicit localRoots and placeholder
bytes, which hid the gap; it now uses the production shape (no explicit media
access, source in the OpenClaw temp root that TTS writes to) and real
buffer-verifiable audio, because host-local sends are content-verified.

* test(outbound): cover agent-workspace media that only the scoped capability reads

The existing staging test used a source in the OpenClaw temp root, which both
the raw and the agent-scoped capability permit, so it could not tell the two
apart and would have passed against the defect it was meant to guard.

Add a case whose source lives in an agent workspace at
<state>/workspace-<agentId>. The unscoped default roots reject that path by
construction, so a durable row exists only when staging resolves the same
agent-scoped capability the live send resolves. Reverting staging to the raw
media access, or bypassing the shared resolver, fails this test.

* test(outbound): pin agent-workspace staging to the agent-scoped capability

The regression case admitted its source two ways that had nothing to do with
agent scoping: the state dir sat inside the OpenClaw temp root, which is itself
a default media root, and parent-root expansion grants any declared source
directory. Either would keep the test green after the agent identity was
dropped from the resolver.

Move the state dir outside the temp root and set tools.fs.workspaceOnly, which
suppresses source-derived root expansion. The agent workspace is then reachable
only through the agent-scoped capability, so removing the agent identity, the
shared resolver, or reverting to the raw media access each fail the test.

* test(gateway): pin queue media retention across retry backoff

* fix(outbound): retain queued media for retries

Co-authored-by: Masato Hoshino <g515hoshino@gmail.com>

* fix(outbound): retain queued media for retries

Co-authored-by: Masato Hoshino <g515hoshino@gmail.com>

* fix(outbound): retain queued media for retries

Co-authored-by: Masato Hoshino <g515hoshino@gmail.com>

* docs(changelog): avoid queued TTS merge conflict

Co-authored-by: Masato Hoshino <g515hoshino@gmail.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
2026-07-16 01:43:53 -07:00
Masato HoshinoandGitHub 2a69149566 fix(clickclack): sanitize outbound assistant text (#103142) 2026-07-10 17:52:37 -07:00
fbc73ca1b7 fix(outbound): publish conversation bindings after SQLite commit (#101023)
* fix(outbound): restore current-conversation binding map when persist fails

The generic current-conversation binding mutators (bind/touch/unbind/
prune-on-read) changed the process-wide in-memory map before calling
writePersistedBindings(), with no rollback. A throwing SQLite write left
the map ahead of disk; because bindingsLoaded is a one-time flag, the
diverged bindings were served until restart, routing inbound messages to
wrong or deleted session targets while the caller already saw the throw.

Add persistBindingsOrRestore(): snapshot the map before mutation and, on
write failure, restore it and rethrow. Covers all six write sites. Mirrors
the cron rollback precedent (#99960, src/cron/service/ops.ts persistOrRestore).

Fault-injection tests assert the map reverts for each path.

* fix(outbound): publish conversation bindings after SQLite commit

* chore(outbound): align binding files with current main

* fix(outbound): publish conversation bindings after SQLite commit

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
2026-07-10 03:13:27 +01:00
34248e101f fix(gateway): bound sessions.usage all-agent session discovery concurrency (#102013)
* fix(gateway): bound sessions.usage all-agent session discovery concurrency

* refactor(gateway): centralize usage agent tasks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-09 15:23:56 +01:00
Masato HoshinoandGitHub a49567cdfd fix(qqbot): treat inbound attachment content types case-insensitively (#102753)
* fix(qqbot): treat inbound attachment content types case-insensitively

* test(qqbot): use distinct download paths per image; note voice sentinel in comment
2026-07-09 05:05:34 -07:00
c9ec548e6f fix(browser): preserve strict CDP discovery policy on target-list lookup (#102328)
* fix(browser): preserve strict CDP discovery policy on target-list lookup

findPageByTargetIdViaTargetList fetched the CDP /json/list endpoint without
passing an ssrfPolicy argument, so the caller's discovery policy was dropped at
the fetch layer while the sibling tryTerminateExecutionViaCdp scoped it. Build
the scoped control policy with scopeCdpPolicyToConfiguredEndpoint and pass it to
fetchJson, matching the sibling lookup path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(browser): tighten target-list CDP policy test comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(browser): consolidate target-list SSRF coverage

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-09 12:04:39 +01:00
c0b6b846ac fix(acp): record cancelled background turns as cancelled, not succeeded (#101245)
* fix(acp): record cancelled background turns as cancelled, not succeeded

A cancelled parented ACP turn resolves without throwing (only failed turns
throw), so the turn-runner success path recorded the detached task as
succeeded. Thread the terminal status out of consumeAcpTurnStream and branch
at the runner; widen markBackgroundTaskTerminal to accept cancelled, routed
through the existing failTaskRunByRunId sink. The runTurn fallback infers
cancellation from done.stopReason, mirroring the acpx adapter.

* fix(acp): narrow cancelled task status handling

Co-authored-by: masatohoshino <g515hoshino@gmail.com>

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-06 19:32:40 -07:00
6522dbf66b fix(imessage): handle stdout/stderr stream errors in the RPC client child (#101084)
* fix(imessage): handle stdout/stderr stream errors in the RPC client child

A dead imsg RPC helper can emit an async error on any of its stdio streams.
On a raw stream an unhandled 'error' event throws and surfaces as an
uncaughtException, crashing the gateway. #75438 added this guard for stdin
but left stdout/stderr — on the same long-lived child — unguarded.

Route stdout/stderr stream errors through the existing failAll path via a
shared failFromStreamError helper, mirroring the stdin handler. Add a
regression test that emits 'error' on each stream and asserts the child does
not throw and in-flight requests reject cleanly.

* fix(imessage): terminate failed RPC transports

* test(imessage): exercise real stream failure

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-07 03:15:27 +01:00
b6c7a77d3f fix(discord): handle ffmpeg stderr stream errors in voice playback (#101088)
* fix(discord): handle ffmpeg stderr stream errors in voice playback

createDiscordOpusPlaybackStream guards ffmpeg stdout and stdin against raw
stream 'error' events (an unhandled stream error throws and crashes the
gateway via uncaughtException), but the stderr stream on the same child was
left unguarded. Add the symmetric stderr error handler, routing it through
opusStream.destroy like stdout. Add a regression test covering stdout and
stderr stream errors.

* refactor(discord): unify ffmpeg stream errors

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-06 19:43:10 +01:00
f859d78b61 fix(line): surface partial delivery when rich or media send fails alongside text (#100996)
* fix(line): surface partial delivery when rich or media send fails alongside text

* fix(line): harden partial delivery result

* test(line): preserve frozen error type

* chore: keep release changelog owner-only

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-06 19:21:29 +01:00
c437f4af9d feat(doctor): warn when cron delivery targets inactive channel (#98184)
* feat(doctor): warn when cron delivery targets inactive channel

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

* fix(doctor): ignore disabled cron delivery targets

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-06 16:40:20 +00:00
f36d170bc6 fix(agents): retry transient filesystem races when reading workspace bootstrap files (#100910)
* fix(agents): retry transient filesystem races when reading workspace bootstrap files

* fix(agents): retry transient boundary resolution

---------

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-07-06 16:12:04 +00:00
Masato HoshinoandGitHub a327cec143 fix(discord): surface failed bulk reaction removals instead of false success (#90038) 2026-07-06 16:21:53 +01:00
e7158a581d Surface config hot-reload watcher status in health (#99267)
* fix(gateway): surface config hot-reload watcher status in health

Thread the config reloader's existing hotReloadStatus() accessor through
the managed reloader handle (previously only stop() survived the handoff)
and into openclaw health as an optional configReload.hotReloadStatus
field, so operators can tell when the watcher has permanently disabled
itself after exhausting retries instead of silently running with stale
config.

* fix(gateway): keep configReload.hotReloadStatus fresh on cached health responses

Thread getConfigReloaderHotReloadStatus through GatewayRequestContext
(mirroring the existing getEventLoopHealth pattern) so the health RPC's
cache-hit merge path picks up a live watcher disable within the same
request instead of waiting up to HEALTH_REFRESH_INTERVAL_MS for the
background refresh to catch up.

* fix(health): move config reload status type to leaf contract

Move GatewayHotReloadStatus out of config-reload.ts into a leaf
config-reload-status.types.ts so health/request-context/runtime-handles
callers no longer pull the full config-reload implementation into the
shared server-method type graph (ClawSweeper-flagged architecture cycle).

* test(gateway): update config reloader fixture

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-06 01:48:54 -07:00
Masato HoshinoGitHubopenclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
310c2f58b9 improve(cron): show consecutive failure count and last error in cron CLI output (#99602)
* improve(cron): show consecutive failure count and last error in cron list/show

* improve(cron): show consecutive failure count and last error in cron CLI output

* fix(clownfish): address review for live-pr-inventory-20260706T074223-002 (1)

---------

Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-07-06 01:44:51 -07:00
Masato HoshinoandGitHub e7ca90e3af fix(hooks): flag hook event names that no core trigger emits (#99456)
* fix(hooks): flag hook event names that no trigger site emits

* docs(hooks): clarify bare family subscriptions in unknown-event note

* fix(hooks): word unknown-event diagnostics around core-emitted keys

* fix(hooks): apply unknown-event advisory to legacy config handlers too
2026-07-06 01:30:13 -07:00
Masato HoshinoandGitHub 9b0c3abbca improve(status): group disabled plugins by reason in /status plugins (#99598) 2026-07-06 01:22:42 -07:00
Masato HoshinoandGitHub ef1c83274d improve(health): surface dead-lettered delivery queue entries (#99842)
* improve(health): surface dead-lettered delivery queue entries

Deliveries that exhaust retries are moved to the failed status in the
SQLite delivery queue for diagnostics, but no health surface ever read
them back: openclaw health stayed all-green while messages sat
dead-lettered, visible only in gateway logs. Add a per-queue failed
count accessor and report dead-lettered entries in the health snapshot
(JSON field plus a warning line), following the existing plugin and
context-engine health section pattern. Observer-only: no retry, purge,
or behavior change.

* test(health): cover snapshot and CLI dead-letter reporting

Add getHealthSnapshot coverage against an isolated state dir, an
openclaw health text-output test for the warning line, exact
oldest-failure timestamp assertions via fake timers, and a debug log
when the delivery-queue health read fails.

* fix(health): recompute dead-letter counts for cached gateway responses

The gateway health handler can serve a cached snapshot for up to a
refresh interval, so a delivery dead-lettered after the cache was
filled stayed hidden from openclaw health. Recompute the delivery
queue summary in mergeCachedHealthRuntimeState alongside the existing
context-engine and model-pricing live merges, and cover the cached
handler path with a regression test.
2026-07-06 01:22:16 -07:00
Masato HoshinoandGitHub 09b5cdc17f improve(doctor): warn when cron jobs keep failing consecutive runs (#99606)
* improve(doctor): warn when cron jobs keep failing consecutive runs

* improve(doctor): message restart-interrupted runs in the chronic cron failure advisory
2026-07-06 05:40:55 +00:00
ff759b3f1a fix(outbound): report honest message delivery status (#99928)
* fix(outbound): report delivery status in best-effort message send results

Best-effort sends (forced on for implicit message_tool_only source
replies) previously collapsed failed and partial_failed durable send
results into a success-shaped MessageSendResult, so agents saw a
normal-looking envelope while delivery had actually failed. Surface
deliveryStatus, a formatted error, sentBeforeError, and per-payload
outcomes without changing throw semantics.

* fix(outbound): centralize message delivery outcomes

Co-authored-by: masatohoshino <g515hoshino@gmail.com>

* docs(changelog): split aggregate entries from code landing

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 07:18:03 -07:00
cbb920c7d9 fix(qqbot): channel status keeps reporting connected after the gateway websocket dies (#100127)
* fix(qqbot): publish disconnected channel status when the gateway closes or gives up

The QQBot channel only ever set connected: true (onReady/onResumed);
no close path updated the status, so a fatal close (bot banned or
offline, 4914/4915) or reconnect exhaustion left channels.status
claiming a live connection forever, and the channel-health monitor
never saw connected=false for a dead gateway.

Thread an onDisconnected callback from GatewayConnection.handleClose
(fatal and pre-reconnect branches) and the reconnect-exhaustion path
through the engine/bridge layers into channel.ts, which now records
connected: false and, for fatal closes, the close reason as lastError.
The running flag stays owned by the gateway lifecycle store, matching
the sibling channel convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(qqbot): import ChannelAccountSnapshot from the channel-contract subpath

The monolithic openclaw/plugin-sdk root entry is a legacy surface;
plugin-sdk contract guardrails and extension boundary checks require
focused subpath imports in bundled plugin sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(qqbot): ignore stale socket closes from superseded gateway connections

A server-driven RECONNECT / INVALID_SESSION tears the old socket down
and brings up a replacement; the old socket's close event can arrive
after the replacement is live. Reacting to it again would tear down the
new socket and regress the connected status, so the close handler now
ignores closes from sockets that are no longer current.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(qqbot): harden disconnect status recovery

* fix(qqbot): report opcode-driven reconnects

* fix(qqbot): keep fatal disconnect health visible

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-05 04:18:01 -07:00
153ee2abba fix(subagents): killed subagent runs stay running in the task list (#99806)
* fix(subagents): reconcile killed task outcomes

Co-authored-by: masatohoshino <g515hoshino@gmail.com>

* fix(subagents): retain reconciliation marker narrowing

* fix(subagents): honor generations in latest-run views

* test(subagents): align reconciliation fixtures

* fix(subagents): keep steer recovery best effort

* style(tasks): use type-only runtime contract import

* refactor(subagents): keep generation ordering leaf-only

* fix(subagents): sanitize persisted task owner ids

* fix(subagents): preserve failed replay lifecycle reason

* fix(agents): clear killed lifecycle timeout grace

* fix(tasks): preserve canonical subagent outcomes

* fix(agents): continue requester-wide cancellation

* fix(agents): fence yield revival races

* test(agents): mock detached task lookup

* fix: fence subagent cleanup deletion

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-05 02:17:10 -07:00
fe3215cbb0 fix(cron): roll back live scheduler state when persisting add/update/remove fails (#99960)
* fix(cron): roll back live store when persist fails during add/update/remove

* fix(cron): restore catch-up deferral markers on failed persists

* fix(cron): defer rollback-sensitive notifications

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-04 12:34:02 -07:00
Masato HoshinoandGitHub 153fed790a fix(gateway): declare the dev agent required by the gateway e2e session key (#99520) 2026-07-03 05:30:09 -07:00
40d4e32110 fix(android): show specific gateway auth-recovery reason instead of generic label (#98698)
* fix(android): distinguish gateway auth-recovery reasons instead of one generic label

gatewaySummary()/gatewayStatusLabel() collapsed every auth failure (expired
setup QR, missing/invalid token, missing/invalid password, stale device
identity) into "Authentication needed", discarding the already-computed
MainViewModel.gatewayConnectionProblem signal consumed elsewhere in the app
(OnboardingFlow's recoveryGatewayAuthDetail). Thread it into both status
labels via a shared gatewayAuthNeededSummary() helper so the Overview and
Settings screens tell the user which recovery action applies.

Fixes #98046

* test(android): lock gateway auth-needed labels to the real unauthorized message format

Verified against src/gateway/server/ws-connection/auth-messages.ts:
formatGatewayAuthFailureMessage always returns strings starting with
"unauthorized", which contains the "auth" substring the status-text gate
checks for. Add tests using the exact real-world message text (not just
synthetic "auth failed" strings) so this stays regression-locked.

* fix(android): widen the auth-needed status gate to cover device-identity failures

CONTROL_UI_DEVICE_IDENTITY_REQUIRED/DEVICE_IDENTITY_REQUIRED real gateway
messages ("device identity required", "control ui requires device
identity...") don't contain "auth", so the status.contains("auth") gate
never reached their specific label. Add "device identity" as an additional
substring the gate checks for those two codes, with regression tests using
the exact real message text (message-handler.ts).

Verified via GitHub-style codex review round 2 (round 1's "unauthorized
doesn't contain auth" claim was checked against source and rejected as
false — "unauthorized" does contain "auth" — but this second, narrower
finding about device-identity codes held up under the same source check).

* fix(android): centralize gateway auth status labels

* fix(android): correlate gateway connection diagnostics

* chore(android): align native i18n inventory after rebase

* fix(android): preserve retryable pairing guidance

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-02 06:50:26 -07:00
e845a26fd6 fix(agents): fail fast with attributable reason after MCP stdio session dies mid-run (#98738)
* fix(agents): fail fast with attributable reason after MCP stdio session dies mid-run

Wires MCP Client onclose/onerror during bundle-mcp session creation so a
crashed/exited server flips session.connected instead of staying stale.
Next tool/resource/prompt call throws a domain-specific 'is disconnected'
error immediately instead of surfacing the SDK's generic 'Not connected'.
A disconnected reused session is retired and rebuilt fresh on the next
catalog pass rather than reused, since the SDK chains onclose/onerror
cumulatively on repeat connect() and the stdio transport never clears its
read buffer on an unexpected exit.

* fix(agents): retire a reused MCP session that dies mid-refresh, not just pre-refresh

codex review found: the catch-path retirement in getCatalog()'s per-server
task only covered two cases (fresh session that never connected this pass,
and a non-reused session that failed for any reason) - a reused session
that was healthy when this pass started but disconnects mid-refresh (child
process dies between ensureSessionConnected() returning and
listAllToolsBestEffort() finishing) hit neither branch, so onclose flipped
connected=false but the dead session object stayed in the map until the
next catalog rebuild happened to notice it. Added the missing branch plus
a regression test that kills the child process mid tools/list on a reused
session and asserts the session is purged from the map within the same
refresh (not just fail-fast on tool calls, which already worked).

* fix(agents): retire a dead reused MCP session even across overlapping catalog generations

codex round-2 review found: the mid-refresh retirement branch still
skipped when sharedWithNewerGeneration was true, which protects a
still-alive session another generation is actively using - but once
onclose flips session.connected=false, the transport is dead for every
generation sharing that object, so that guard no longer applies.
Simplified to a single !session.connected branch that always retires
(retireSessionIfCurrent already no-ops safely if a newer generation
replaced the map entry), and dropped the now-write-only
connectedForCatalog local it replaced.

* test(scripts): allow MCP SDK callback suppressions

* fix(agents): retire closed MCP sessions

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-01 22:55:15 -07:00
Masato HoshinoandGitHub 8e28e88387 fix(gateway): distinguish reachable gateway from failed status probe (#98183)
* fix(gateway): distinguish reachable gateway from failed status probe

* fix(status): gate owns-port RPC recovery guidance on no stale gateway PIDs

inspectGatewayRestart can set health.healthy from bare reachability after
ownership attribution failed, while still returning a non-empty
staleGatewayPids. Printing owns-port recovery guidance in that case
contradicted the dedicated stale-PID diagnostic below it. Require
staleGatewayPids to be empty before treating healthy as owns-port proof.
2026-07-01 17:04:31 -07:00
Masato HoshinoandGitHub 5ada3acb5a feat(doctor): warn about in-flight cron jobs
Squashed from PR #98620 after updating branch with current main and passing CI.
2026-07-01 06:41:37 -07:00
Masato HoshinoandGitHub 76f9b6d837 fix(gateway): surface systemd start-limit exhaustion (#98291)
* fix(gateway): surface systemd start-limit exhaustion

* fix(gateway): avoid start-limit false positive for exit 78
2026-07-01 01:01:15 -07:00
Masato HoshinoandGitHub 0a9708ed0f fix(status): surface unregistered memory embedding providers (#97968) 2026-07-01 00:46:49 -07:00
2c7e989686 fix(doctor): recover legacy cron archive across devices (#98217)
* fix(doctor): recover legacy cron archive across devices

* fix(doctor): harden legacy cron archive retries

* fix(doctor): canonicalize shipped cron retry shape

* fix(doctor): persist legacy cron migration receipts

---------

Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-07-01 06:39:11 +01:00
5c4e478df4 fix(slack): expose sender bot status in context (#97822)
* fix(slack): expose sender bot status in context

* fix(slack): expose sender bot status in context

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-30 10:29:42 -07:00
37341a7032 fix(googlechat): expose sender bot status in context (#97825)
* fix(googlechat): expose sender bot status in context

* fix(googlechat): expose sender bot status in context

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-30 10:28:47 -07:00
984f5a51ca fix(discord): expose sender bot status in context (#97824)
* fix(discord): expose sender bot status in context

* fix(discord): expose sender bot status in context

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-30 10:27:53 -07:00
Masato HoshinoandGitHub a75431c586 fix(agents): classify Anthropic orphaned tool-use replay errors (#98163) 2026-06-30 09:57:14 -07:00
888f399499 fix(status): surface should-run plugin drift (#97878)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:46:36 -07:00
be0c40bad2 fix(commitments): preserve extraction batch on transient failure (#89817)
A drained extraction batch is spliced off the queue before the extractor
runs. On a non-terminal failure the batch was never restored, so those
items were silently lost and never retried. Restore the batch to the
front of the queue (original order) on non-terminal errors and rethrow so
the caller still logs; terminal model/auth errors keep the existing
cooldown drop/stop behavior. Also ensure the single-slot debounce
schedules a drain from the overflow branch, so a queue left full by a
restored batch still gets retried instead of being stuck.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:10:41 -07:00
Masato HoshinoandGitHub c026546063 fix(signal): sanitize internal tool-trace lines from outbound text (#97360)
Wrap the signal outbound sanitizeText hook with sanitizeAssistantVisibleText so assistant internal tool-trace scaffolding is stripped before delivery, matching the sibling channel fixes under #90684 (Telegram #95774, Google Chat #95084, IRC #97214).
2026-06-28 12:14:45 -07:00
Masato HoshinoandGitHub cd6d0f9b00 fix(slack): sanitize internal tool-trace lines from outbound text (#97367)
Wrap the slack outbound sanitizeText hook with sanitizeAssistantVisibleText so assistant internal tool-trace scaffolding is stripped before delivery, matching the sibling channel fixes under #90684 (Telegram #95774, Google Chat #95084, IRC #97214).
2026-06-28 12:14:35 -07:00
Masato HoshinoandGitHub 25490d4c42 fix(matrix): sanitize internal tool-trace lines from outbound text (#97372)
Wrap the matrix outbound sanitizeText hook with sanitizeAssistantVisibleText so assistant internal tool-trace scaffolding is stripped before delivery, matching the sibling channel fixes under #90684 (Telegram #95774, Google Chat #95084, IRC #97214).
2026-06-28 11:58:36 -07:00
949b1af433 fix(status): distinguish runtime-loaded plugins from installed inventory (#97479)
* fix(status): distinguish runtime-loaded plugins from installed inventory

/status plugins merged disk-scan plugin records with the active runtime
registry, so the detailed Loaded: list could include plugins that were
installed/config-enabled but never loaded at runtime. Record the runtime-
loaded plugin ids on the health snapshot, carry them through the merge, and
make Loaded: reflect runtime-confirmed plugins; show installed/discovered
plugins that are not active as a neutral "Installed (not active)" inventory
line rather than an error.

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

* fix(status): include pinned runtime registries in loaded ids

Build runtimeLoadedPluginIds from all live runtime registry surfaces via
collectLivePluginRegistries() (active plus any pinned channel / http-route /
session-extension registry) and render Loaded: from that id set directly, so a
plugin live only through a pinned surface still counts as loaded instead of
being dropped or misreported as "Installed (not active)".

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:44:56 -07:00
ddedf13190 fix(irc): sanitize internal tool-trace lines from outbound text (#97214)
* fix(irc): sanitize internal tool-trace lines from outbound text

* fix(irc): sanitize internal tool-trace lines from outbound text

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-27 08:34:38 -07:00
Masato HoshinoandGitHub 965d1fff3f fix(providers): strip cache-boundary marker from non-Anthropic prompts (#89716) 2026-06-22 19:14:31 +00:00
masatohoshinoandVincent Koc 085ff95fcf fix(update): carry effective git channel into post-core finalize
Address ClawSweeper P1 (Carry the effective git channel into finalize):
an unconfigured git/source update runs the core update on the git/dev channel
(runGatewayUpdate: opts.channel ?? "dev"), but the finalizer received no channel
and fell back to the stable package channel, so plugin convergence could resolve
official plugins on the wrong channel.

Mirror the CLI post-core resume's effective/requested channel split: the RPC
finalize path now passes the effective channel (configChannel ?? DEFAULT_GIT_CHANNEL)
to update finalize via OPENCLAW_UPDATE_EFFECTIVE_CHANNEL (convergence-only), never
as --channel. update finalize uses it as a convergence fallback but never persists
update.channel unless the user actually requested one.
2026-06-18 18:46:18 +08:00
masatohoshinoandVincent Koc 07c29c20d6 fix(update): forward finalize --channel only when configured
Codex follow-up: defaulting the channel to dev and passing --channel made
update finalize persist update.channel into openclaw.json (persistRequestedUpdateChannel
treats any --channel as an explicit request). Only forward --channel when the
caller has a configured channel so the finalizer never writes a channel the user
did not request; when omitted it converges on the stored/default channel and the
reconcile still resolves a host-compatible version. Keeps the per-step vs
whole-process timeout decoupling.
2026-06-18 18:46:18 +08:00
masatohoshinoandVincent Koc 6214762461 fix(update): match git channel + decouple finalize process timeout
Address codex PR-review findings:
- Default the post-core finalize channel to the git/dev channel (matching
  runGatewayUpdate's git default) instead of letting update finalize fall back to
  the stable package channel, so official plugins converge on the same channel as
  the core update for default source updates.
- Decouple the finalizer's whole-process spawn timeout from the per-step
  --timeout so a valid multi-step finalize is not killed prematurely and falsely
  reported as post-core-plugin-finalize-failed.
2026-06-18 18:46:18 +08:00
masatohoshinoandVincent Koc 1b251a6af1 fix(update): harden post-core finalize (service-env strip, no-op skip)
- Strip gateway service identity (OPENCLAW_SERVICE_MARKER/KIND/PID) from the
  finalizer child so it is not mistaken for the managed service, matching the
  CLI post-core spawn.
- Skip finalize for no-op git updates (unchanged SHA and version), mirroring the
  CLI resume gate, to avoid an unnecessary doctor/convergence run.
2026-06-18 18:46:18 +08:00
masatohoshinoandVincent Koc 90d385cb93 fix(update): resume official plugin convergence after gateway git update
The gateway update.run RPC updated git/source installs via runGatewayUpdate
but, unlike the openclaw update CLI, never resumed the post-core plugin
convergence that runGatewayUpdate's doctor pass defers. As a result a
git/source core update would restart on the new core with official managed
plugins still pinned to versions built against removed core APIs.

Spawn the rebuilt binary's update finalize entrypoint after a successful
git update so official plugins reconcile to a host-compatible version, and
block the restart if convergence fails (mirroring the CLI).
2026-06-18 18:46:18 +08:00
3ef02ca818 fix(plugins): reuse current metadata snapshot in provider hot paths
Refactor provider metadata lookup so hot paths consult the current process snapshot before falling back to a metadata load.

Centralize provider metadata lookup in the provider runtime and update the focused tests/mocks that exercise embedded-agent and provider loading paths.

Verification:
- node scripts/run-vitest.mjs src/plugins/providers.runtime.consult-current-snapshot.test.ts
- node scripts/run-vitest.mjs src/agents/embedded-agent-runner/run/attempt.cwd-split.test.ts
- node scripts/run-vitest.mjs src/plugins/providers.test.ts
- autoreview --mode branch --base origin/main
- CPU profile loop: current-snapshot resolve 0.459 us/call vs warm direct metadata load 131.493 us/call
- GitHub CI on 728bd53510

Co-authored-by: masatohoshino <g515hoshino@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 17:48:58 +01:00
Masato HoshinoandGitHub 313d6ae1b3 fix(whatsapp): strip control characters from outbound document fileName (#77114)
Merged via squash.

Prepared head SHA: 5417a8ee2c
Co-authored-by: masatohoshino <246810661+masatohoshino@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-05-28 01:17:52 -03:00
Masato HoshinoGitHubclawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>takhoffman
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
Masato HoshinoandGitHub 016f5ae862 test(plugins): cover dead-PID stale runtime-deps lock removal
Adds focused regression coverage for dead owner PID runtime-deps install locks so stale lock recovery remains PID-first and does not wait on age when the recorded owner process is gone.

Co-authored-by: masatohoshino <g515hoshino@gmail.com>
2026-04-29 12:51:14 +01:00
Masato HoshinoandGitHub 517801282a fix(matrix): pin event-helpers import to canonical matrix-js-sdk subpath (refs #50477) (#68498)
Merged via squash.

Prepared head SHA: 32e08e4d8e
Co-authored-by: masatohoshino <246810661+masatohoshino@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-19 15:35:34 -04:00
9449e54f4f feat(line): add outbound media support for image, video, and audio
pnpm install --frozen-lockfile
pnpm build
pnpm check
pnpm vitest run extensions/line/src/channel.sendPayload.test.ts extensions/line/src/send.test.ts extensions/line/src/outbound-media.test.ts

Co-authored-by: masatohoshino <246810661+masatohoshino@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-28 20:51:16 -05:00