* 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>
* 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>
Sandboxed sub-agents could read another agent's bridge-compiled memory through the shared global vault via wiki_get, wiki_search, and the memory_get/memory_search corpus fallback. The wiki-corpus read path now filters pages through one visibility gate that fails closed for bridge-compiled pages a sandboxed caller does not own, including bridge pages that carry no ownership metadata.
The Diffs plugin named the `message` tool from three model-visible surfaces:
the guidance it injects via before_prompt_build, the mode=file tool result,
and its SKILL.md. `disableMessageTool` removes that tool in eight production
paths (subagent spawn, system agent, worker runtime, both skills-workshop
reviews, companion-ask, restart sentinel, active-memory recall), so agents in
those sessions were told to call a tool absent from their tool list.
The same surfaces plus the `diffs` tool description named the separately
gated Canvas plugin — SKILL.md through `canvas present` / `canvas navigate`,
the others through "for canvas use".
All of them now describe the capability instead of naming the tool, per the
root AGENTS.md rule that descriptions never statically name tools from other
toolsets. The two pinned assertions keep their snapshots and now also assert
the invariant directly.
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(harness): report copilot code-mode engagement on the attempt result
* test(copilot): prove code-mode engagement through the production tool bridge
* docs: describe the normalized codeModeEngaged value for native harnesses
* fix(discord): bound webhook send body reads
Rebase onto latest main to clear unrelated CI flake. No code changes.
Co-Authored-By: nebulacoder-v8.0 <noreply@zte.com.cn>
* fix(discord): bound webhook send body reads
Keep the Discord REST deadline active through webhook response-body reads while preserving malformed-body fallbacks. Avoid logging token-bearing webhook URLs.
Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
---------
Co-authored-by: nebulacoder-v8.0 <noreply@zte.com.cn>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>