* fix(memory-wiki): strip fenced code and inline code before wikilink extraction
The wikilink extractor in extractWikiLinks previously scanned
the full markdown content including fenced code blocks and
inline code spans. Literal [[...]] syntax inside code regions
(e.g. bash test syntax, Scala generics like
Future[Option[User]]) produced false-positive broken-wikilink
lint warnings.
Add FENCED_CODE_BLOCK_PATTERN and INLINE_CODE_PATTERN and
strip those regions from the searchable text before running
the Obsidian and Markdown link regexes. Five regression tests
cover backtick-fenced, tilde-fenced, 6-backtick-fenced, and
inline code scenarios plus a full vault lint round-trip.
Fixes#97945
* fix(memory-wiki): accept longer closing fence in code block stripping
The FENCED_CODE_BLOCK_PATTERN used a regex backreference (\1)
that required the closing fence to be exactly identical to the
opening fence. CommonMark allows the closing fence to be the
same character type and at least as long — e.g. ``` opening
with ```` closing is valid. Switch to a function-based
replacement that compares fence character type and length.
Add regression test for the longer-closing-fence case per
ClawSweeper review feedback (#98095).
* style(memory-wiki): add braces to if-body in fence replacement callback
Fixes ESLint curly rule violation at line 403.
* fix(memory-wiki): replace fence regex with line scanner for wikilink extraction
Replace FENCED_CODE_BLOCK_RE regex/callback with a line-by-line scanner
that keeps searching past invalid fence-looking lines (e.g. a shorter
``` line inside a longer `````` block) until a valid closing
fence of the same character type and at least as long is found.
Also handles tilde fences and longer closing fences per CommonMark spec.
🦞 diamond lobster: L2 evidence (5 real function-call scenarios, all passing)
Ref. https://github.com/openclaw/openclaw/pull/98095
* fix(memory-wiki): use CommonMark code masking
---------
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
* fix(feishu): bound Feishu API JSON response reads to prevent OOM
Replace the bare `response.json()` in `fetchFeishuJson`
(`extensions/feishu/src/app-registration.ts`) with a `readResponseWithLimit`
call capped at 16 MiB. A misconfigured or adversarial Feishu endpoint that
streams an unbounded body previously had no defence; the bounded reader now
cancels the stream at the cap and throws a labelled `feishu.api` error.
Tests: over-cap (32 MiB stream, no Content-Length — stream cancelled, error
matches feishu.api), under-cap (chunked valid JSON — parsed and returned),
and malformed-JSON (labelled feishu.api error). All five tests pass.
* fix(feishu): delegate JSON reads to provider helper
* test(feishu): add real node:http server proof for readProviderJsonResponse bound
* test(feishu): fix lint errors in real HTTP server proof (curly + no-promise-executor-return)
* test(feishu): prove bound reads through SSRF guard
* fix(feishu): satisfy overloaded LookupFn type in hermetic test lookup stub
* fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse
outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout
(daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI
killed early, or flag mismatch) threw a raw SyntaxError that propagated
uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a
context-free SyntaxError with no hint of the actual mcporter failure.
Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet
(matching the guard pattern already used in parseListedCollections in this
file, and the recent matrix #97973 / sms #97999 / signal #98073 /
telegram-ingress #98372 JSON.parse guard series).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): preserve cause in qmd mcporter JSON.parse guard
Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error
lint rule; the original SyntaxError is now chained, improving diagnosability.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary)
ClawSweeper flagged that the previous error message exposed raw mcporter
stdout (first 200 chars) before session visibility filtering, which could
leak sensitive content. Drop the stdout preview from the thrown message;
keep the original SyntaxError as `cause` for diagnostics so the parse-failure
reason is still reachable without surfacing unfiltered subprocess output.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak)
The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its
message (e.g. Unexpected token '<', then the raw bytes). Including that
SyntaxError message in the thrown Error would surface unfiltered mcporter
stdout before session visibility filtering. Drop the parse-error message from
the thrown Error; keep the original SyntaxError as cause for developer
diagnostics only.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage)
formatErrorMessage walks the .cause chain into the user-visible path.
Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw
stdout snippet through formatErrorMessage even with a generic message.
Give the cause a generic message too; the raw snippet no longer reaches
the user.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
The batch output file download path creates a readline interface over a
Readable.fromWeb() response body stream. If JSON.parse throws on a malformed
JSONL line, the for-await loop exits via exception but the readline interface
and underlying Readable stream were never explicitly closed or destroyed,
leaving the HTTP response body stream dangling.
Extract the stream reading into , a testable helper
that wraps the iteration in a try-finally so both reader.close() and
inputStream.destroy() are always called, matching the pattern established in
#98493 for the same class of leak.
* fix(clickclack): reply to a top-level message in-channel, not as a new thread
sendClickClackText routed any replyToId to createThreadReply, and the inbound
handler stamps replyToId = <triggering message id> on every reply. As a result
every reply to a top-level channel message opened its own thread, so the main
channel timeline showed nothing and the chat was effectively unusable.
Route a bare replyToId to the main channel as a quote-reply (quoted_message_id)
instead — matching the reply-to affordance of the Discord/Slack/Telegram
channels — and reserve threads for genuine thread context (an explicit threadId
or a thread-kind target). DM replies likewise quote-reply in the same
conversation. quoted_message_id is omitted when there is no reply context, so
plain sends are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(clickclack): cover quote payload typing
---------
Co-authored-by: Marvinthebored <marvinthebored@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Summary:
- Merged feat(tencent): add Tencent Hy3 provider (TokenHub and TokenPlan) after ClawSweeper review.
Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(tencent): preserve TokenHub auth compatibility
- Included post-review commit in the final squash: refactor(tencent): unify TokenPlan env/flag naming with TokenHub
- Included post-review commit in the final squash: docs: refresh Tencent provider docs metadata
- Included post-review commit in the final squash: fix: allow TokenPlan provider config overlays
- Included post-review commit in the final squash: docs: dedupe Tencent provider glossary labels
- Included post-review commit in the final squash: fix(tencent): repair TokenHub model defaults
Validation:
- ClawSweeper review passed for head 30c9fc130f.
- Required merge gates passed before the squash merge.
Prepared head SHA: 30c9fc130f
Review: https://github.com/openclaw/openclaw/pull/99076#issuecomment-4888527271
Co-authored-by: leisang <leisang@tencent.com>
Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Centralized managed worktrees under <state-dir>/worktrees/<repo-fingerprint>/<name>
with branch-per-task (openclaw/<name>), .worktreeinclude provisioning, an optional
.openclaw/worktree-setup.sh repo hook, and a SQLite registry in the shared state DB.
Removal always snapshots the tree (untracked included, gitignored excluded) to
refs/openclaw/snapshots/<id>; restore rebuilds the branch at the original commit with
the snapshot content as uncommitted state. Lossless run-end cleanup, 7-day idle GC for
run-owned worktrees (manual exempt), orphan reconciliation, 30-day snapshot retention.
Surfaces: worktrees.* gateway RPC (operator.admin mutations), openclaw worktrees CLI,
Control UI page, plugin-SDK facade + Workboard kind:"worktree" materialization.
E2E-verified on Testbox: full create->work->remove->restore->gc lifecycle.
Approval wait now fits inside the Codex dynamic-tool watchdog (70s +10s
gateway grace under the 90s kill), approval cards carry proposal id,
skill name, description, file count, and body size (spoof-safe
rendering), and timeouts return a structured pending-not-failed outcome
instead of a bare error. Expired requests cannot execute late; no
auto-apply; generic plugin approvals unchanged.
* fix(browser): drain download saves and use monotonic cursor for act response
- Add drainPendingDownloadSaves() to wait for in-flight saveAs before sampling
- Use monotonic downloadSeq cursor instead of bounded-list length
- Propagate downloads info in POST /act response for click/batch/evaluate
Fixes#93250
* fix(lint): add braces around single-line if returns
Fixes eslint(curly) failures in drainPendingDownloadSaves and
pickNewDownloads. PR #93307 required CI gate.
Ref: ClawSweeper P1 review finding
* fix(browser): scope act download metadata to action
* fix(browser): broadcast downloads to all active captures to prevent misattribution
When concurrent /act calls overlap on the same page, using
state.actionDownloadCaptures.at(-1) assigned downloads to the wrong
action's capture. Push managed save promises to all active captures
so the triggering action always receives its download metadata.
Also adds regression tests: broadcast-to-all-captures, sequential
capture isolation, and strengthen the dispose test to assert no
re-capture after disposal.
* fix(browser): prevent unhandled rejection when download capture action throws before drain
Move managedSave.catch() before the captures-branch check so the
rejection handler always runs, preventing an unhandled promise
rejection when the action throws before drain() is called. Simplify
the handler by removing the now-dead return + catch at the bottom.
* fix(browser): type downloads in BrowserActResponse, fix lint unused var
- Add optional downloads field to BrowserActResponse type contract so
typed callers of browserAct() can consume the new payload without casts.
- Remove unused afterDispose variable in pw-session tests (lint fix).
* fix(test): correct post-dispose download assertion
capture.promises is not cleared by dispose(), so re-draining a disposed
capture still returns pre-dispose results. This is benign — callers
should drain before disposing. Update the test to assert the actual
behavior (still shows old results, new download not captured).
* test(browser): cover /act download metadata response
* refactor(browser): report action-owned downloads safely
* fix(browser): close action download ownership races
* test(browser): type action download capture mock
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(auto-reply): render chat history since last reply as per-message prose
The inbound chat-history block dumped batched history as a raw JSON array, which models read poorly compared to the chat-window block's per-message prose. Reuse the existing formatChatWindowMessage renderer for history entries so both blocks share one shape, keep the untrusted framing label, and keep media rendered as a bare content-type tag so local paths and URLs stay redacted. Teach the metadata stripper to consume the new prose block form.
* fix(auto-reply): preserve every media content type in chat-history prose
The prose chat-history renderer only forwarded the first attachment's
content type per history message, dropping the rest for entries with
multiple media items. Join all bounded content types instead, and
regenerate the prompt snapshot fixture this changes.
* test(qa-lab): accept prose pending history
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ios): restore in-flight chat runs from gateway history
Restore active Apple chat ownership across reconnect, foreground, and sequence-gap recovery using the existing chat.history snapshot. Preserve agent/session scoping and Gateway user-turn identity across Codex and Copilot mirrors, including current offline-cache integration.
* fix(ios): restore in-flight chat runs from gateway history
* fix(skills): keep command spec descriptions UTF-16 safe at the truncation cut
* chore: fix oxlint no-unnecessary-type-assertion in test
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: fix Skill type completeness in test fixture
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(skills): prove UTF-16-safe command truncation
* fix(skills): apply description limits per channel
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): preserve managed Chrome cookies
* fix(browser): guard graceful close process ownership
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(diffs): share SSR preloads and repair language-pack hydration
Render viewer and file documents from a single @pierre/diffs SSR preload
per file (mode=both previously ran the full diff+highlight pipeline
twice; 651ms -> 303ms on an 8-file patch), apply the file-mode font bump
as a document-level override, and keep hydration payloads
variant-faithful.
Fix the language-pack runtime downgrading pack-only languages to plain
text at hydration by defining a per-target build flag and forwarding it
to payload normalization.
Also: case-insensitive language hints, identical before/after
short-circuit with details.changed, patch input failures classified as
tool input errors, canonical config values now win over deprecated
aliases, hash-pinned viewer runtime served immutable, truthful
browser-vs-render errors, timing-safe artifact token compare, unref
idle browser timer.
* docs(changelog): link diffs rendering entry to PR
* test(diffs): narrow manifest validation results before value access
* test(tooling): allowlist diffs viewer-client define suppression
* fix(slack): normalize react emoji glyphs and clarify member-info userId param
Slack's reactions.add/remove only accept shortcode names, never a raw
Unicode glyph, but the react action's emoji param had no description
steering models away from passing one, so calls like
emoji="✅" failed with invalid_name. Consolidates the glyph-to-shortcode
map that already existed privately in the ack-reaction dispatch path into
the shared normalizeSlackEmojiName export in actions.ts, the layer that
owns the actual Slack API calls, so the message-tool react action gets the
same normalization.
Also tightens the generic userId param description so models stop trying
target on member-info, which has no target mode and requires userId
directly.
* fix(slack): preserve emoji reaction semantics
* fix(slack): default reactions to inbound message
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>