Address review feedback on #66412.
`writeDeepDreamingReport` is called with a defensive
`params.config.storage ?? { mode: "inline", ... }` fallback for cases
where the config object is loosely constructed (tests, migrations).
After flipping the unset default to `"separate"` in
`src/memory-host-sdk/dreaming.ts`, this hardcoded `"inline"` fallback
would silently re-introduce the old behavior for any caller that
exercises the fallback branch.
Update the fallback to `"separate"` so all default code paths agree.
The `MemoryDreamingStorageMode` type continues to accept all three
documented modes; this is purely a default-value alignment.
Closes#66328.
Before this change, every dreaming installation that did not explicitly
set `plugins.entries.memory-core.config.dreaming.storage.mode` accepted
the silent default of `"inline"`, which routes Light Sleep and REM Sleep
phase blocks (the structured staged-candidate lists with their
`<!-- openclaw:dreaming:{phase}:start -->` markers) into
`memory/YYYY-MM-DD.md`. On heavy days the inline blocks dominate the
daily memory file - the reporter measured 340 lines for the Light phase
alone, and a second cross-host check on a v2026.4.12 install observed
475 inline lines from Light plus 14 from REM in a single sweep, leaving
the daily file unusable as a "what happened today" record.
The `"separate"` storage mode that was already wired up in
`writeDailyDreamingPhaseBlock` writes the same content to
`memory/dreaming/{phase}/YYYY-MM-DD.md` instead, leaving the daily file
untouched. `stripManagedDailyDreamingLines` was already in place so the
daily-ingestion scanner does not re-record dream blocks as recall
candidates either way, but flipping the default also avoids the
incidental round-trip where the dreaming pipeline writes inline output
and then reads its own output back as fresh recall material in the next
cycle.
Operators who want the previous behavior can still opt in explicitly:
plugins.entries.memory-core.config.dreaming.storage.mode: "inline"
The host helper `normalizeStorageMode` continues to accept all three
documented modes (`inline | separate | both`); only the unset-default
fallback changes.
Test changes:
- src/memory-host-sdk/dreaming.test.ts adds two new assertions: one
pinning the new default shape and one confirming explicit `"inline"`
still round-trips for opt-in callers.
- extensions/memory-core/src/dreaming.test.ts updates four
default-output assertions in `resolveShortTermPromotionDreamingConfig`
to expect `mode: "separate"`.
- extensions/memory-core/src/dreaming-phases.test.ts pins
`LIGHT_DREAMING_TEST_CONFIG` and the inline-mode harness in
"checkpoints daily ingestion and skips unchanged daily files" to
`storage.mode: "inline"` with a comment, since those tests rely on
inline-mode side effects on `memory/<day>.md` and now need to opt in
explicitly.
The schema accept-test in extensions/memory-core/src/config.test.ts and
the inline-mode write-path tests in
extensions/memory-core/src/dreaming-markdown.test.ts intentionally keep
asserting `mode: "inline"` because they cover input handling and the
inline write path itself, both of which still need to work.
* docs: add async exec duplicate completion investigation
Add an internal refactor note tracing the node exec completion to system event to heartbeat to transcript path for duplicate async exec injections. Document the most likely gateway-side gap as missing idempotency for replayed exec.finished events, and note why plain outbound delivery retry is a weaker fit for duplicate user turns.
Regeneration-Prompt: |
Investigate a live duplicate async exec completion that appeared as two identical user turns in an OpenClaw session. Trace the completion path from exec producers into enqueueSystemEvent, heartbeat wake scheduling, prompt assembly, and embedded transcript persistence. Decide whether duplicate wake handling, outbound delivery retry, or duplicate completion event ingestion is the more likely cause, cite the exact code locations, and capture the smallest plausible fix seam without making runtime changes.
* fix: dedupe replayed exec finished node events
Add a narrow idempotency guard in the gateway node-event handler for repeated exec.finished events with the same canonical session key and runId. This blocks replayed async exec completions from being enqueued and heartbeated twice into the parent session. Also only request a heartbeat when the system event was actually queued, and add a regression test for duplicate runId injection.
Regeneration-Prompt: |
Prevent duplicate async exec completion events from being injected twice into the parent session. Keep the scope tight around the highest-confidence path: node exec.finished events entering gateway server-node-events and becoming system-event-driven heartbeat prompts. Add a small idempotency guard keyed by canonical session plus exec runId, avoid broader delivery or retry changes unless needed, and add regression coverage that fails if the same exec.finished replay is enqueued and woken twice.
* fix: note exec finished replay dedupe
* fix: tighten trusted tool media passthrough
* changelog: tighten trusted tool media passthrough (#67303)
* address review: thread rawToolName into emitToolResultOutput and keep plugin-tool media passthrough
- Pass rawToolName through emitToolResultOutput params so the emit and
collect calls no longer reference an out-of-scope identifier
(ReferenceError on any verbose tool-output path).
- Widen builtinToolNames to all effective tool raw names for this run
(core + bundled/trusted plugin tools), so plugin tools on the trusted
media list still receive local MEDIA: passthrough. Admission-time
client-tool conflict check keeps using the core-only set so unrelated
plugin names do not spuriously reject client definitions; MEDIA
passthrough is still gated by the raw-name set, so a client tool that
normalize-collides with a plugin name cannot inherit its media trust.
- Add unit coverage for bundled-plugin raw-name passthrough and for
case-variant plugin-name collisions.
* drop redundant String() casts flagged by oxlint no-useless-cast
The names from effectiveTools, client tool function names, and the
existingToolNames iterable are already typed as string, so wrapping them
in String(...) adds nothing and trips oxlint's no-useless-cast rule.
formatDocsLink called path.trim() unconditionally. The typed contract
says 'docsPath: string' (required on ChannelMeta), but a handful of
channel plugins and catalog rows leave it unset at runtime, so
onboarding flows that call formatChannelSelectionLine(entry.meta, ...)
hit a TypeError on the first meta without a docsPath:
TypeError: Cannot read properties of undefined (reading 'trim')
Symptom: 'openclaw onboard --install-daemon' and the 'Select channel
(QuickStart)' -> 'Skip for now' path both crash on 2026.4.12 and
2026.4.14.
Fix: widen formatDocsLink's path parameter to 'string | undefined |
null' and fall back to the docs root when path is missing. The single
call site that guards with 'if (params.docsPath)' stays fine; the
unguarded channel-selection path now degrades gracefully.
Fixes#67076Fixes#67074
The hardcoded `OPENCLAW_VITEST_MAX_WORKERS=4` default in gates.sh
short-circuits the host-aware scheduling introduced in c247e366.
`resolveLocalVitestScheduling` sees the explicit override and returns
maxWorkers=4, which falls below the >= 5 threshold required by
`shouldUseLargeLocalFullSuiteProfile`, so every machine—regardless of
resources—gets the DEFAULT profile (4 shard parallelism) instead of
the LARGE profile (10 shard parallelism).
Drop the hardcoded default so `test-projects.mjs` can detect actual
host resources and pick the appropriate profile automatically. When
the user explicitly sets OPENCLAW_VITEST_MAX_WORKERS, forward it as
before.