Commit graph

17700 commits

Author SHA1 Message Date
Omar Shahine
9bebf7ac43
fix(imessage): bold approval prompt labels in poll mode (#116221)
* fix(imessage): bold approval prompt labels in poll mode

#113193 added bold headers and labels to the approval reaction prompt, but
iMessage only shows that copy when tapbacks own the controls. On any
poll-capable bridge the details message is built from `manualFallbackPayload`,
the legacy unstyled builder, so every label (`Title:`, `Tool:`, `Host:`,
`CWD:`, `Full id:`, ...) reaches Messages as flat text. Native polls are the
default on a bridge-v2 host, so in practice #85954 still reproduced after it
was closed.

Add `nativeControlsPayload` to `ApprovalReactionPendingContent`: the same rich
copy as `reactionPayload` minus the tapback hint, for channels whose native
controls already own the decision surface. iMessage poll mode now renders it,
so both control paths deliver identical styled copy.

`imsg poll send --question` has no attributed-body channel, so the poll
question keeps the marker-free rendering of that same text; otherwise the
balloon would show literal asterisks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Byq7UrC7ZpMHxoFXcddsa

* refactor(plugin-sdk): carry native-controls prompt copy as text

The iMessage poll path reads exactly one thing from the native-controls
payload: its text. Replace the ReplyPayload field with nativeControlsText so
buildApprovalReactionPendingContent stops running the metadata/session-key
builder for fields no caller reads, and the '?? ""' sentinel goes away.

Relative to main the SDK change stays additive: neither field exists in any
release; nativeControlsPayload only ever existed on this branch.

* refactor(plugin-sdk): expose native-controls prompt text as a builder, not a type field

ClawSweeper flagged that requiring a new member on the shipped
ApprovalReactionPendingContent type is source-incompatible for external
producers that hand-construct it. The hint-free copy does not need to ride
the type at all: export buildApprovalNativeControlsPromptText and let the
iMessage handler call it at payload-build time.

ApprovalReactionPendingContent is now byte-identical to the shipped shape;
the SDK change is a single additive function export. Signal/WhatsApp test
fixtures revert to their original form.

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 09:23:47 -07:00
Vincent Koc
e96b9d2cd0
improve(ui): verify llama.cpp model setup 2026-07-30 23:41:58 +08:00
Vincent Koc
90a22b4f50
chore(test): run browser copilot E2E in CI (#116407)
* test(browser): run copilot E2E in CI

* test(browser): stabilize copilot E2E synchronization

* test(browser): wake extension workers deterministically

* test(ci): route browser copilot command changes
2026-07-30 23:20:34 +08:00
Vincent Koc
ca1328dddb
Merge pull request #116285 from openclaw/fix/control-ui-session-dedupe
* origin/fix/control-ui-session-dedupe:
  chore: leave release notes to release flow
  fix(ui): deduplicate gateway-native session hosts
2026-07-30 22:53:53 +08:00
Shakker
ea82059e02
fix: satisfy ClickClack lint checks (#115484) 2026-07-30 15:42:18 +01:00
Shakker
7f35fa8ab7
fix: publish top-level ClickClack mention config (#115484) 2026-07-30 15:42:18 +01:00
Shakker
e95fe13eb2
perf: use normalized ClickClack group lookups 2026-07-30 15:42:18 +01:00
Shakker
f44fdec2b9
fix: preserve inherited ClickClack group policy 2026-07-30 15:42:18 +01:00
Shakker
c0f8288daa
fix: route ClickClack discussion mentions correctly 2026-07-30 15:42:18 +01:00
Shakker
154e67b1e2
fix: recognize ClickClack handle mentions 2026-07-30 15:42:17 +01:00
Jacqueline Henriksen
ba9e9fec13
fix: gate ClickClack group replies on mentions 2026-07-30 15:42:17 +01:00
Vincent Koc
5504f1e9a1
test(reef): remove false-green live guard smoke (#116393) 2026-07-30 20:16:28 +08:00
Vincent Koc
3e7ecb3aea
test(comfy): evaluate live guards after setup (#116388) 2026-07-30 19:58:35 +08:00
Vincent Koc
f6d5066bb3
test(discord): align progress timing expectations (#116377) 2026-07-30 19:11:01 +08:00
Dallin Romney
424c36a2d5
fix(qa): prevent Slack cleanup from stalling profile runs (#116361)
* fix(qa): release Slack leases after gateway stop

* fix(qa): preserve async Slack cleanup contract

* fix(qa): expose post-gateway adapter cleanup
2026-07-30 19:05:49 +08:00
Ayaan Zaidi
c4b82609b7
fix(agents): compare shell tool names case-insensitively and stop re-parsing tool labels
Progress-draft lines recovered their detail by string-slicing the label formatToolAggregate had just rendered, and three sites decided "is this a shell tool?" against lowercase spellings only. The Claude CLI sends "Bash", so those checks missed, the slice returned nothing, and every CLI tool call rendered a line with no detail: Telegram printed the icon twice and the tool-summary payload could no longer merge, leaving a second id-less line per call.

formatToolAggregateParts now returns the label with the detail it composed, so a line cannot disagree with its own text, and one predicate owns the shell question across all four sites. Proven live on the Claude CLI backend: two lines with a doubled icon became one.
2026-07-30 19:52:08 +09:00
Jesse Merhi
f1ee2a3098
fix(diagnostics-otel): agent turns split into one-span traces instead of a trace tree (#116246)
* fix(diagnostics-otel): keep parent span links across long turns

Retained trusted span contexts translate a completed lifecycle span's
diagnostic span id to the real OTel span context. That table was evicted
5s after run.completed, so children of a longer turn missed their parent
and OTel minted a fresh trace id for each one, silently splitting a turn
into single-span traces.

Retention is now identity-based and lives for the service lifetime,
bounded by MAX_RETAINED_TRUSTED_SPAN_CONTEXTS, which removes the timing
race and the drain/timeout cleanup subsystem.

Also nests openclaw.exec under its run: the exec event now carries a
trusted trace context and the recorder resolves the ambient run scope.

* fix(diagnostics-otel): key retained span contexts by diagnostic trace id

Retained lifecycle span contexts were stored under the span's OTel trace id
but looked up with the event's diagnostic trace id. Those are different id
spaces, so every post-completion parent lookup missed and each straggler
span started a brand-new trace.

The mocked suite could not catch this: its tracer reports the same trace id
the test feeds in, collapsing both id spaces into one value. Adds a boundary
test that runs the real OTel SDK and OTLP/protobuf exporter against a local
receiver and asserts on exported span bytes.

* fix(diagnostics-otel): harden span-linking contract after review

Review findings on the parent-span linking fix:

- completeTrackedLifecycleSpan now takes the DiagnosticTraceContext instead of a
  bare trace id beside a span id. Two adjacent hex strings let a transposition
  compile clean and silently reinstate split traces; DiagnosticTraceContext is
  the only shape an OTel SpanContext cannot satisfy (traceFlags string vs number).
- internalOrTrustedTraceContext composes normalizedTrustedTraceContext rather
  than spelling the trust predicate a third time.
- Corrected comments that claimed OTel ids never equal diagnostic ids. Spans
  parented from an upstream traceparent do adopt the diagnostic trace id; the
  removed guard only ever misfired on root lifecycle spans.
- Exec comment now states the real parent: the openclaw harness opens no run
  scope, so exec nests under openclaw.harness.run.

Test gaps closed:

- The core emitter switch was untested; reverting it kept every test green.
  The pty-fallback test now asserts the emitted metadata.
- The boundary test covered only run.completed of three lifecycle owners; it now
  covers harness.run.completed and message.processed too, each with a straggler.
- Dropped the service-context cast for the typed helper, which caught a bogus
  context.assembled field, and moved teardown into afterEach so a failed
  assertion cannot leak the real NodeSDK across files.
- Scoped the one-trace assertion to this turn's spans; faked Date so a lazy
  Date.now expiry cannot pass.

Docs: exec and tool spans now listed as staying on an upstream request trace.

* fix(diagnostics-otel): keep late children on the trace on every terminator

Native review found three correctness gaps in the previous commit:

- Exec spans could be exported naming a parent span id nothing ever emits. The
  ambient lookup fell through to a remote-parent fallback built from diagnostic
  ids, which is a regression from the previous clean-root behavior and breaks
  waterfalls and parent-id-keyed backends. Exec now resolves only spans this
  process exported and stays a root on a miss; message spans keep the remote
  fallback because their context can come from an inbound traceparent.
- harness.run.error never retained its span context, so an aborted turn still
  split. For the openclaw harness that span is the only ancestor a late child
  has, and aborted turns emit no run.completed.
- completeTrackedLifecycleSpan no longer takes a redundant spanId.

The boundary test now runs through the OPENCLAW_OTEL_PRELOADED seam with an
in-memory exporter and releases the global tracer provider in teardown. A
NodeSDK cannot be unregistered, so the old version would have made any later
real-SDK test silently export nothing. That also deletes the hand-rolled OTLP
protobuf decoder. Added guards for the aborted-turn path and for exec staying
parentless; both are mutation-verified.

* fix(diagnostics-otel): age retained parent contexts out after 10 minutes

Retention had no time bound at all, so a command backgrounded by a turn could
finish hours later and still attach to a parent that ended long before. Backends
derive trace latency from the span envelope, so a 30s turn rendered as hours with
a child extending past its parent.

The horizon is checked lazily on lookup rather than on a timer: a timer firing
mid-turn is exactly what silently split traces before. Past the horizon a
straggler starts its own trace, which only loses causality for a span that would
have skewed its parent's duration anyway.

The two bounds now pin each other - shrinking the horizon to the old 5s window
fails the original regression tests, and removing it fails the new horizon test.

* fix(diagnostics-otel): retain parent contexts until capacity
2026-07-30 20:41:41 +10:00
Vincent Koc
8a3f5c862b
fix(plugin-sdk): ship documented public typings (#116345)
* fix(plugin-sdk): ship documented public typings

* fix(plugin-sdk): refresh public contract artifacts
2026-07-30 18:20:07 +08:00
pash-openai
e52354ea13
fix(build): pin typebox to a published release (#116333) 2026-07-30 09:58:51 +00:00
WhatsSkiLL
e5cee36b46
fix(memory): retry failed queued session targets (#115923)
* fix(memory): retain failed queued sync targets

* fix(memory): drain retained targets on idle sync

* test(memory): prove idle queued sync recovery

* fix(memory): preserve queued sync ownership

* fix(memory): avoid queued sync self-deadlock

* fix(memory): stop queued recovery during close

* fix(memory): clear retained sync state on close

* test(memory): prove live queued rejection transition

* fix(memory): enforce sync repro invariants

* test(memory): bound archive recovery proof

* test(memory): seed archive proof transcript

* test(memory): exercise archived transcript recovery

* chore(knip): register memory sync repro

* fix(memory): reject blank queries before settings

* style(memory): satisfy queue recovery lint

* test(memory): align doctor migration expectations

* test(memory): insert explicit provenance fixture

---------

Co-authored-by: IWhatsskill <284122573+IWhatsskill@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-30 02:47:59 -07:00
Dallin Romney
714a46d7f2
fix(qa): align live scenario selection (#115767) 2026-07-30 16:59:01 +08:00
Ayaan Zaidi
21d3533460
fix(channels): ack mentions in groups that do not require them
The group-mentions ack gate returned false whenever a group did not require mentions, conflating group policy with whether this message mentioned the agent. Under the default scope, mentioning the agent in a group configured to answer everything produced no ack at all, so the user had no signal the turn was picked up until the reply arrived.

Behavior change on a default path: those groups now show the 👀 ack (and the lifecycle status reactions when messages.statusReactions.enabled is true) on mentions. Mention-required groups, group-all, unmentioned messages, and off/none are unchanged. The gate no longer reads requireMention, so the parameter is gone from the shared type and all channel call sites.

Proven live on Telegram across three configurations, including a no-mention control confirming group-mentions did not become group-all.
2026-07-30 17:58:46 +09:00
Dallin Romney
b765ada174
fix: stop WhatsApp restart loop after remote logout (#114445) 2026-07-30 16:48:32 +08:00
Dallin Romney
184069f3df
fix(qa): make Slack observer lazy and abortable (#116312) 2026-07-30 16:46:49 +08:00
Vincent Koc
6e4f67f084
Merge remote-tracking branch 'origin/main' into fix/control-ui-session-dedupe
* origin/main: (25 commits)
  fix(qqbot): use Gateway timezone for reminders (#116294)
  test(openai): cover out-of-order named mark acks
  docs(openai): clarify cumulative playback marks
  test(openai): cover sustained unnamed mark acknowledgements
  fix(openai): preserve playback acknowledgement order
  test(openai): verify bounded playback acknowledgements
  fix(openai): bound realtime playback marks
  test(agents): mock incremental registry persistence (#116307)
  fix(memory-core): MEMORY.md compaction deletes user notes written under a promotion-style heading (#116180)
  feat(reply): annotate recent history images (#100866)
  chore(ios): refresh native i18n inventory
  fix(ios): isolate capability router handlers
  docs(ios): cut 2026.7.22 release notes
  docs(hooks): document validation responses
  test(qa): assert hook account rejection reasons
  fix(gateway): validate hook delivery accounts
  perf(gateway): bound prepared runtime startup work (#116261)
  fix(agents): prevent registry stalls during large fan-outs (#116286)
  fix(sqlite): preserve replaced snapshot targets on Windows (#116284)
  fix(ci): load frozen shard runner without repository fetch (#116273)
  ...
2026-07-30 16:37:47 +08:00
Vincent Koc
91dca69dae
fix(qqbot): use Gateway timezone for reminders (#116294) 2026-07-30 16:32:18 +08:00
Vincent Koc
835dafb5a8 test(openai): cover out-of-order named mark acks 2026-07-30 16:29:29 +08:00
Vincent Koc
c240b2714d docs(openai): clarify cumulative playback marks 2026-07-30 16:29:29 +08:00
Vincent Koc
ba9e352e95 test(openai): cover sustained unnamed mark acknowledgements 2026-07-30 16:29:29 +08:00
Vincent Koc
750dc99903 fix(openai): preserve playback acknowledgement order 2026-07-30 16:29:29 +08:00
Vincent Koc
0ae46459d8 test(openai): verify bounded playback acknowledgements 2026-07-30 16:29:29 +08:00
Vincent Koc
567d04076a fix(openai): bound realtime playback marks 2026-07-30 16:29:29 +08:00
Yuval Dinodia
d952566385
fix(memory-core): MEMORY.md compaction deletes user notes written under a promotion-style heading (#116180)
* fix(memory-core): stop MEMORY.md compaction from deleting marker-free user sections

Budget compaction decided that a MEMORY.md section belonged to OpenClaw purely
from its heading text, so a user-authored section under a heading that looks
generated, such as "## Promoted From Short-Term Memory (2026-04-10)", was
eligible for deletion once the file plus the new promotion exceeded
memoryFileMaxChars. The rewrite succeeded silently and the atomic replace made
the loss durable.

A matching heading is now provisional: the block is classified as a promotion
only when it structurally contains at least one
"<!-- openclaw-memory-promotion:... -->" marker, which buildPromotionSection
emits for every promoted candidate. Marker-free sections are preserved.
Generated sections, including the multi-project shape whose markers sit under
"### Global" and "### Project:" subsections, are still dropped oldest-first, so
the size bound from #73691 is unchanged.

Extends #116057, which made only subsection handling marker-aware and left the
top-level heading classifier trusting presentation text.

* fix(memory-core): preserve mixed promotion blocks

Validate the complete marker-and-entry grammar before treating a promotion-shaped block as writer-owned. Preserve marker-only and mixed blocks so unheaded operator notes cannot be deleted during budget compaction.

Co-authored-by: yetval <yetvald@gmail.com>

* test(memory-core): allow cold citation startup

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-30 16:24:32 +08:00
joshavant
a56f7a2b98 test(qa): assert hook account rejection reasons 2026-07-30 02:59:06 -05:00
joshavant
54a6dba0d3 fix(gateway): validate hook delivery accounts 2026-07-30 02:59:06 -05:00
joshavant
32a61cabf0 fix(matrix): observe automatic reply settlement 2026-07-30 02:31:15 -05:00
Vincent Koc
bb68389a66
fix(ui): deduplicate gateway-native session hosts 2026-07-30 15:22:37 +08:00
Vincent Koc
ad1dc60259 fix(openai): scope realtime readiness state 2026-07-30 15:22:02 +08:00
Vincent Koc
21463e6e19 test(openai): verify terminal audio cleanup 2026-07-30 15:22:02 +08:00
Vincent Koc
3133abed32 fix(openai): bound pre-ready realtime audio 2026-07-30 15:22:02 +08:00
Dallin Romney
7df05d5fc0
refactor(qa): remove live-channel scenario registries and wrappers (#115752)
* refactor(qa): remove duplicate channel scenario registries

* refactor(qa): route live scenarios through shared runners

* fix(qa): isolate WhatsApp driver retries

* refactor(qa): add module export flow arguments

* test(qa): include scenario metadata in matrix fixture
2026-07-30 13:56:34 +08:00
Peter Steinberger
353c05d547
test(qa): recognize internal image completions (#116234) 2026-07-29 22:09:45 -07:00
Ayaan Zaidi
c63241d3ce fix(channels): resolve tool progress against the caller's stream mode
resolveChannelStreamingPreviewToolProgress guessed a mode when streaming.mode
was unset, so it could never be right for every channel: guessing "partial"
dropped Discord and Telegram's explicit progress.toolProgress opt-out, and my
earlier switch to "progress" broke it the other way for Slack, Mattermost, and
Teams, whose real default is partial. With both keys configured and disagreeing,
the wrong one won.

The helper has no channel identity, so it stops guessing and takes the mode the
caller already resolved. All nine call sites pass theirs. Omitting it keeps the
previous configured-mode reading, so untouched callers behave exactly as before.

Reported by ClawSweeper on #116143.
2026-07-30 13:36:21 +09:00
Ayaan Zaidi
06aa81a73f fix(channels): show tool lines under the progress status headline
A status headline replaced the rolling tool lines instead of sitting above
them, so a default Discord draft showed one preamble sentence for an entire
tool-heavy turn. Operators reached for `/verbose` to see any activity, which
delivers durable per-tool-call messages and floods the channel.

- Render the headline above the lines; both stay visible in one message.
- Shorten the start gate from 5s to 1.5s. The gate only creates the draft
  when the timer fires and finalize cancels it, so quick answers still post
  no draft while a 3s tool turn stops being silent.
- Drop Discord's label-gated tool-progress default so
  resolveChannelStreamingPreviewToolProgress is the single owner. An explicit
  `toolProgress: false` still silences the lines.
- Resolve that toggle against a "progress" mode guess when `streaming.mode`
  is unset, so the progress-draft channels stop ignoring an explicit
  `progress.toolProgress` opt-out.

Telegram now defaults to `streaming.mode: "progress"` like Discord; set
`"partial"` to keep streamed answer text. Its renderer draws work lines from
the compositor's structured lines, so `rendersRollingLinesNatively` keeps
them out of the composed text rather than printing every line twice.
2026-07-30 13:36:21 +09:00
joshavant
cfa98bdf00 fix(telegram): emit sent hook for finalized previews 2026-07-29 23:31:53 -05:00
Vincent Koc
039efadf59 fix(openai): bound realtime SDP answer reads 2026-07-30 12:31:02 +08:00
Vincent Koc
280c162753 fix(openai): bound GPT-Live sideband startup frames 2026-07-30 12:31:02 +08:00
Vincent Koc
69f10e72f2
fix(ollama): retry unreachable setup in place (#116210) 2026-07-30 12:23:56 +08:00
Yuval Dinodia
4e5bf66fb1
fix(whatsapp): silently drops inbound messages when more than 450 are waiting (#116179)
* fix(whatsapp): silently drops inbound messages when more than 450 are waiting

WhatsApp asked the shared ingress monitor to enforce a 450-entry cap and a
30-day TTL on pending durable ingress rows. Pending rows are undelivered work,
not history, and the monitor runs retention before its first claim, so an
account holding 451 accepted messages lost the oldest one to a hard DELETE
before it could ever be dispatched. Loss scaled linearly with backlog depth and
left no tombstone, no failure record, and no retained payload.

Drop pendingTtlMs and pendingMaxEntries from the WhatsApp retention config.
Completed and failed retention is unchanged so on-disk history stays bounded,
and the config now matches every other channel on the shared monitor, whose
defaults cover completed and failed only. The two values were carried over
mechanically from the pre-SQLite openKeyedStore replay-guard cache in
b0679d1f13 and inlined verbatim in #115824.

Add regression coverage driving the real monitor and the real SQLite ingress
queue over 451 accepted records.

* test(whatsapp): cover pending ingress retention

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-30 12:20:27 +08:00
Vincent Koc
58b4b94304 test(openai): verify superseded consult handoff 2026-07-30 12:11:06 +08:00