Reject symlinked generated-output roots before build and postbuild mutation paths can recurse into unrelated targets. Cover aggregate, UI, export-template, bundled-plugin, Docker-prune, and plugin-runtime entry points with regression tests and migration guidance.
Closes#116498
Co-authored-by: WangYan <wang.yan29@xydigit.com>
* fix(memory-wiki): preserve user edits when rolling back ChatGPT imports
Rollback deleted created pages and overwrote updated pages unconditionally,
destroying content the user added after the import with no recovery copy.
Import runs now record a content hash of each written page after vault
compile, and rollback preserves any page whose current content no longer
matches into the run's recovered directory before deleting or restoring.
Legacy run records without hashes preserve unconditionally.
Fixes#116457
* fix(memory-wiki): move pages aside atomically during rollback
Rollback now renames the live page into the recovery location before
inspecting it, so a concurrent external save cannot land between the
content read and the delete or snapshot restore. Matching pages drop
the moved copy; mismatching pages keep it as the recovery file.
* fix(memory-wiki): make rollback snapshot restore collision-safe
The snapshot restore wrote directly to the page path after the
move-aside, so a page recreated by an editor in that window was
overwritten with no recovery copy. Restore now creates the snapshot
exclusively and on collision moves the recreated page aside and
retries; recovery filenames are uniqued so a second move-aside cannot
clobber an earlier preserved copy.
* fix(memory-wiki): record rollback hashes at write time
* fix(memory-wiki): make ChatGPT rollback retry-safe
* fix(memory-wiki): fence ChatGPT rollback phases
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(agents): teach canonical automations tool in fallback guidance and reuse the identity source
Review follow-ups: the structured-list fallback still taught models the cron
tool; the cron-scope test echoed its own stub; MCP serve allowlist and voice
confirmation hardcoded the name instead of the canonical constant.
* fix(mcp): place automations identity import outside the header comment
* feat(agents): rename model- and user-facing scheduler strings to automations
Rewrites every string the model or user sees that names the feature:
tool label and terminal presentation, display-summary preset, subagents
catalog description, heartbeat guidance, subagent prompt, session labels
(Cron: -> Automation:), default job name, unattended-run preamble, delivery
awareness text, task-ledger progress summary, failure alerts, auto-disable
notification, model preflight diagnostics, validation errors, and the
shipped workspace AGENTS.md template. Adds a system-prompt backstop rule on
the automations tool line: the feature is called automations, never cron.
Cron stays only where it names the schedule syntax ({kind:"cron"}, cron
expressions), config keys (cron.triggers.enabled), RPC method names, and
session-key namespaces. Test fixtures with legacy labels are kept as
stored-data coverage. Part of RFC openclaw/rfcs#50 Phase 1 (PR B).
* docs(templates): keep workspace template on the live docs anchor until the docs PR renames it
* fix(cron): rename remaining failure-notification producers and stale test expectations
Codex review follow-ups: the per-run failure notification (server-cron-
notifications) and the doctor legacy-notify advisory still said Cron job;
failure-alert and incomplete-turn suites still asserted the old wording.
* chore(plugin-sdk): re-emit plugin-state test runtime dts
CI's extension-boundary runner holds stale tsgo incremental state whose
cached plugin-state-test-runtime.d.ts predates the keyed-store re-exports;
the freshness stamp validates it and reruns reuse the same sticky disk.
Touching the module forces incremental emit to regenerate the declaration.
Fresh builds of this head (local exact CI command and a forced Testbox
rebuild) both emit the exports correctly.
* fix(agents): rename residual model- and operator-facing cron prose
Found in combined dev-gateway E2E: fallback tool line still told models the
scheduler tool is cron; heartbeat scratch description, cron model preflight
rejection, doctor and Claw prose still said cron jobs.
* fix(agents): finish model-facing scheduler strings flagged in review
Loop work-order prompts, subagents tool description, cron-tool write errors,
and the headless exec denial label now say automations; loop builders covered
by prompt assertions; snapshots regenerated.
* chore(agents): refresh generated baselines and live docs anchor after rebase onto main
* fix(agents): rename new main-side cron tool error expectations to automations
* fix(gateway): rename main-side cron notification test expectations to automations
* chore(agents): apply review moves - automations commands in workspace template, drop unrelated sdk comment
---------
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
* docs(automation): rename scheduled-tasks feature wording to Automations
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhJ8EiMXue6ADLmHfb7FL6
* docs: regenerate docs map and add Automations glossary entries
* docs(templates): follow renamed automations-vs-heartbeat anchor
* docs(automation): fix markdown formatting drift
* docs(automation): teach the canonical automations tool and sync the copied heartbeat default
Review follow-ups: normal instructions use the automations tool with cron as
an explicit compatibility alias; every verbatim copy of the default heartbeat
prompt matches the new shipped text from the strings PR.
---------
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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
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.