* refactor(prompt): plain inbound context labels with a provenance marker
Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.
Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.
Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.
Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.
* fix(ci): resolve gate failures for plain inbound context labels
- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
so the Kysely connection-boundary guardrail holds; unexport the now-internal
transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
the 700-line oxlint cap; suppressions are disallowed, so follow the existing
sibling record-module pattern. Public exports and PluginCompatCode literals
unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
only). The bracket de-fang and System: rewrite it expected were removed with
sanitizeInboundSystemTags; forged system lines are neutralized at the
system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.
* fix(prompt): harden inbound context label migration and drop in-band sanitizer
Review follow-ups on the plain-label + provenance-marker change:
- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
look-alike `System:`/`[System]` markers corrupted legitimate user text and is
not a real injection boundary; role separation plus external-content wrapping
is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
now requires a complete known legacy sentinel line, a legacy label followed by
a fenced JSON body, or the complete legacy external-content header. The prior
predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
header's line break, migrated assistant rows skip newline normalization, and
without it the marked-header replace wins and the body strips to empty. Added
a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
prose-block strip path.
Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
* fix(memory-core): keep live reindex of reset/deleted session archives
The session transcript listener dropped every archive artifact before
scheduling, so .jsonl.reset and .jsonl.deleted archives written by /reset
and session delete were no longer indexed on the live path and only
surfaced in memory search after the next gateway restart. Remove the
archive guard so in-agent archives fall through to scheduleSessionDirty,
which reaches the usage-counted-archive branch in processSessionDeltaBatch.
Regression from #89912; restores the incremental archive indexing added in
* fix(memory-core): constrain live archive indexing
* test(memory-core): align migrated search fixtures
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(memory): keep canonical rows when same-file legacy memory tables diverge
* fix(memory): keep canonical-owned sources' chunk sets coherent during legacy import
* fix(memory): import legacy chunks when canonical source has no chunks
The same-file legacy migration excluded a legacy chunk from import whenever the
canonical index already had a source row for its (path, source). That stranded a
file whose canonical source was registered but had no chunks yet (indexing
interrupted before chunks were written, or embedding pending/failed): the legacy
chunks were its only searchable content, and the matching source hash stops sync
from re-indexing, so the file went silently unsearchable.
Re-key the chunk-coherence exclusion on canonical chunk ownership instead:
snapshot the (path, source) pairs that already have canonical chunks before the
import and skip legacy chunks only for those. A source with a canonical row but
no chunks now imports its legacy chunks. The snapshot is taken pre-insert because
the exclusion predicate reads the chunks table the import writes to.
Add regressions: legacy chunks import for a chunk-less canonical source while a
chunk-owning source still drops its stale legacy chunk; and restore abort
coverage for the meta and chunks copy assertions (previously only files was
exercised).
* fix(memory): harden same-file legacy conflict recovery
* fix(memory): rebuild ambiguous partial legacy sources
* fix(memory): reconcile migrated derived indexes
* fix(memory): close migrated index ownership gaps
* test(memory): align migration expectations
* test(tooling): match routed test order
* test(memory): exercise vector reload cleanup
* test(memory): prove real vector reload cleanup
* fix(memory): make migrated indexes converge
---------
Co-authored-by: Serhii Leniv <leniv.tech@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(memory-core): write MEMORY.md atomically during short-term promotion
applyShortTermPromotions rewrote MEMORY.md with a single non-atomic
fs.writeFile, which truncates the file before streaming the new content.
An OS write failure part way through (for example EFBIG on a size-limited
or full volume) left MEMORY.md truncated to the bytes written before the
failure, permanently dropping user long-term memory. The dreaming cron
path invokes this writer automatically, and the recall store is only
updated after the write, so the promotion stays eligible and the next
run reads the already-truncated file.
Route the write through replaceFileAtomic (temp file, fsync, atomic
rename), the same durable-write helper the sibling DREAMS.md writer in
this extension already uses. On failure the temp file is discarded and
the existing MEMORY.md is left untouched; on success the content and the
existing file mode are preserved.
* fix(memory-core): harden atomic promotion durability
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(plugin-sdk): narrow wildcard barrels to explicit used exports
* refactor(tools): delete dead tool-planning module exposed by barrel narrowing
* fix(plugin-sdk): restore deprecation tag on OpenClawSchemaType alias
* test(agents): drop test for deleted runtime proxy module
* refactor(tools): trim descriptor types to cache consumers
* refactor(deadcode): harvest exports orphaned by barrel narrowing
* refactor(deadcode): harvest exports orphaned by barrel narrowing (rest)
* fix(agents): restore sdk imports and test markers via public predicate
* fix(plugin-sdk): named type re-exports in plugin-entry; trim types barrel precisely
* chore(plugin-sdk): account unmasked deprecated provider types in budgets
* fix(plugins): name star-only type rows for dts bundling
* fix(plugins): restore host-hook surface; unexport internal api compositions
* fix(plugins): named type imports for api composition; restore needed source exports
* fix(plugins): knip-visible type imports for registry surfaces
* test: adapt tests to privatized media and command internals
* fix(qa-lab): re-export snapshot conversation type
* style: format sessions sdk imports
* fix(plugins): restore smoke entry export; pin budgets to exact actuals
* fix(plugins): canonical smoke-entry import; drop orphaned root shims
* fix(plugins): allowlist manifest probe, repoint qa web import, drop dead browser barrels
* fix(plugin-sdk): pin codex auth marker and scaffold provider type
* fix(qa-lab): keep web-facing model-selection shim within boundary rules
* fix(plugin-sdk): preserve merged contracts through narrowed barrels
* chore(plugin-sdk): pin post-rebase surface budgets
* fix(memory-core): clear pending-update wait timer after update settles
* test(memory-core): wrap setImmediate Promise executor in block body
`oxlint(no-promise-executor-return)` (enabled in the bundled-extension
config used by `pnpm lint:extensions:bundled` /`check-lint` /
`check-additional-extension-bundled`) flags the implicit return from
`new Promise((resolve) => setImmediate(resolve))`. The arrow function
is expression-bodied and returns the Immediate handle, which the rule
treats as a return inside a Promise executor.
Switch both occurrences in the regression guard to block bodies so the
executor callback does not return a value. Behavior is unchanged:
setImmediate still fires resolve on the next macrotask; the regression
assertion (process.getActiveResourcesInfo() delta) still passes.
* test(memory-core): track pending wait timer cleanup
* refactor(memory-core): keep timer cleanup LOC-neutral
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>