Commit Graph

17685 Commits

Author SHA1 Message Date
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
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
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
Vincent Koc
6b37d46b17 fix(openai): bound GPT-Live delegation work 2026-07-30 12:11:06 +08:00
xingzhou
cc48aef143 fix(beam): prevent mirror retries from stalling on slow responses (#116174)
* fix(beam): release mirror upload responses

* test(beam): cover response cancellation failures

* fix(buzz): repair typing indicator contracts

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-30 11:33:22 +08:00
Shakker
c49bfa7382 feat: add Buzz typing indicators 2026-07-30 03:59:34 +01:00
Vincent Koc
3672ab6fa1 fix(test): stop collecting Vydra test helpers (#116139) 2026-07-30 10:19:42 +08:00
Vincent Koc
b660662e01 fix(discord): preserve webhook deadline errors (#116155) 2026-07-30 10:17:47 +08:00
Vincent Koc
0370fce7ff fix(test): isolate Google embedding batch timeout (#116161) 2026-07-30 10:11:29 +08:00
Vincent Koc
18e107f494 fix(plugins): report request timeouts for stalled response bodies (#116166)
* fix(openai): preserve device-code request timeouts

* fix(msteams): preserve SharePoint upload timeouts

* fix(qqbot): preserve channel API request timeouts
2026-07-30 10:03:39 +08:00
joshavant
21db50efc7 refactor(whatsapp): keep inbound boundary transport-private 2026-07-29 20:34:55 -05:00
Vincent Koc
5c296b5a54 fix(whatsapp): preserve injected listener normalization 2026-07-29 20:34:55 -05:00
Vincent Koc
f970e5093b refactor(channels): add portable inbound boundary 2026-07-29 20:34:55 -05:00
Vincent Koc
636d7705c4 fix(feishu): preserve source time in message envelopes (#116156) 2026-07-30 09:27:01 +08:00
Peter Steinberger
ec46d30fbf fix(codex): preserve requester across approval bridge (#116152) 2026-07-29 18:16:51 -07:00
Vincent Koc
d9565bf677 fix(mattermost): honor configured envelope timezone (#116145) 2026-07-30 09:14:40 +08:00
Yuval Dinodia
772235cc55 fix(memory-core): stop MEMORY.md compaction from deleting user headings (#116057)
* fix(memory-core): stop MEMORY.md compaction from deleting user headings

parseMemoryBlocks segmented MEMORY.md only on "## ", so a user heading of
any other level did not close the promotion section above it. The section
ran on to the next "## " heading or EOF, and dropping it to fit the budget
deleted the user text it had swallowed, with no backup and no diagnostic.

Close the current block on any ATX heading, keeping the generator's own
"### Global" and "### Project: <key>" subheadings inside the promotion
body so multi-project sections stay droppable whole.

* fix(memory-core): identify generated promotion subsections by their entry marker

The first exception treated every "### Global" and "### Project: <key>"
heading under a promotion block as dreaming-owned, so a user who wrote one
of those headings themselves still lost it to compaction, which is the same
data-loss class this change set out to fix.

A generated subsection always leads with an openclaw-memory-promotion entry
marker comment, so require that marker as the next non-blank line before
treating the heading as part of the promotion body. User headings that
collide with the generated names now terminate the block and survive.

* fix(memory-core): recognize tab-delimited and empty ATX headings

The heading detector required a literal ASCII space after the opening #
sequence. CommonMark also allows a tab or end of line, so a user note
starting with a tab-delimited heading stayed inside the promotion block
above it and was deleted when compaction dropped that block.

Accept a space, a tab, or end of line after the # sequence. The generated
subsection pattern stays space-only because that is what buildPromotionSection
emits, so a tab-delimited heading is user-authored and is preserved.

* fix(memory-core): preserve Setext headings during compaction

Setext headings were still absorbed into the generated promotion block above them, so dropping that block could silently delete durable user notes. Move the heading paragraph into a preserved block when its underline is encountered, while keeping marker-backed generated content compactable.

* fix(memory-core): preserve indented ATX headings

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-30 09:13:57 +08:00
QiuYuang
87c71cf0ab fix(voice-call): reject malformed telnyx timestamps (#107565) 2026-07-30 09:12:10 +08:00