config get answered identically for a real-but-unset schema path and a
path that does not exist, and pointed both at config validate, which only
reports whether the current file parses.
Use the schema runConfigGet already builds to split the two cases: a known
path reports that it is unset and offers a runnable config set command for
that exact path; an unknown path points at config schema. Exit code stays 1
for both, in text and --json.
`claws remove` deleted the agent and reported status complete while its
exec-approvals policy survived in the shared state DB, so a later agent
reusing the id silently inherited the old allowlist.
claws was the third agent-removal path and the only one that never opened
an agent-deletion journal, which withAgentExecApprovalsRemoved requires as
its fence. Open the journal around the config commit and run both commit
branches inside the approvals fence, matching the gateway and CLI paths.
Cron stays declaration-owned and unchanged.
Reuse the active Gateway registry only for the exact current metadata generation and matching runtime authority. Seed selected-runtime loading from imported inbound plugin IDs so deferred records remain eligible for their scoped load, while the generation owner continues to supply built-artifact preference.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* perf(agents): reuse gateway plugin generation for prepared model runtime
The model-runtime sidecar rebuilt two broad plugin registries on gateway
startup: 167 imports across 84 plugins (83 loaded twice) with 82 forced
TypeScript transformations, ~39.6s of a ~50s source-checkout startup.
- Reuse the active gateway registry for inbound dispatch when the
gateway-bindable subagent mode, workspace, and manifest identity of the
requesting snapshot generation all match; fall back to isolated loading
otherwise.
- Seed the runtime registry from the inbound registry's plugin ids instead
of a second unscoped broad load.
- Prefer built plugin artifacts on the agents lane, matching the gateway
loader; externally-packaged plugins no longer force source transforms.
Measured (M3 Ultra, dist build, median of 3): sidecars.model-runtime
33.93s -> 0.379s; gateway readiness 41.69s -> 7.58s; plugin imports
167 -> 18; TypeScript transformations 82 -> 0.
* fix(agents): keep source artifacts on isolated agent registry loads
The full agents lane caught ~20 Code Mode regressions from preferring
built artifacts on the agents loader lane: in a source checkout a stale
dist silently replaces edited plugin source. The gateway-generation
reuse already removes the broad re-imports that made source transforms
expensive, so the artifact preference is unnecessary for the startup
win and unsafe for dev checkouts.
* fix(agents): bind gateway registry reuse to the current metadata generation
Reuse authority is identity-based: the requesting snapshot must be the
process-current published generation. Bundled manifest records compare by
id+origin only, so an equivalence check alone could let an older or leaked
active registry serve a different generation's plugins.
* perf(agents): resolve prepared-runtime metadata through the current generation
The sidecar acquired its snapshot with loadPluginMetadataSnapshot, which
always constructs a fresh object, so the generation-identity reuse guard
could never hold in the gateway and inbound loads stayed isolated. Resolve
through the slot-probing entry instead; the published current generation
satisfies the read and registry reuse engages.
Test updates: model the current-generation identity in the reuse fixture,
drop stale preferBuiltPluginArtifacts expectations from the isolated-load
fixtures, and add a negative case proving a non-current generation refuses
reuse.
* perf(agents): inherit gateway artifact preference when extending its generation
The runtime-registry delta load re-imported teams-meetings and zoom-meetings
channel graphs through forced source transforms (~9.2s) even though the
reused gateway generation had already realized them from built artifacts.
When the inbound registry is the active gateway registry, the delta load now
inherits the generation owner's built-artifact preference; isolated loads
keep source truth, so source-executed tests and dev flows are unaffected.
Measured (M3 Ultra, dist build, 3 runs): sidecars.model-runtime
289-331ms (baseline 33,930ms); gateway ready 3.7-4.0s (baseline 41.7s);
forced source transforms 0 (baseline 82).
* test(agents): count both slot-probing metadata resolutions in static startup
The prepared cold plugin context now resolves through the canonical
slot-probing entry, joining the model-id normalization lane's existing
resolution. Both share the published generation; the eager-work
protections remain the catalog laziness assertions.
* refactor(agents): compose generation reuse with lifecycle artifact selection
Rebase over #125957, which preserves built-artifact mode for
lifecycle-selected prepared registries. Reuse of the active gateway
generation and the generation-inherited delta preference compose with
that owner's parameter instead of duplicating the forwarding.
Writing an auth profile on an install whose credentials still live in the
retired JSON store printed two contradictory lines: the real migration error,
followed by a generic "the auth store lock may be busy. Wait a moment and
retry." An operator reading the last line retries forever, because nothing is
locked and the write can never succeed until Doctor migrates the store.
updateAuthProfileStoreWithLock wrapped its transaction in a blanket catch that
logged a warn and returned null, erasing the failure's identity. Callers only
saw null, so the sole guess available to them was lock contention. Read paths
already let AuthProfileMigrationRequiredError and AuthProfileStoreUnreadableError
through; only the write path swallowed them.
Rethrow those two credential-boundary errors at the producer and keep null for
genuine write failures, which is what the lock hint was always describing. All
three sibling call sites (upsertAuthProfileWithLockOrThrow, models auth logout,
models auth order set) funnel through that null, so they are fixed by the one
change. Onboard now prints only the migration line, and `openclaw doctor --fix`
followed by the same onboard command succeeds.
Keep plugin-originated text and media bound to the active host-selected route and revoke authority at turn closure.
Fence Gateway-owned channels until a server-verifiable authority and media-policy contract exists.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Non-positive plugin retention now disables age pruning instead of deleting eligible sessions. The disabled path also skips the SQLite stale-row scan and full-store load.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: 曾令彪 0668001395 <zeng.lingbiao@xydigit.com>
The node host resolves its plugin registry via loadPluginRegistryHandle
(activate:false). Since #117587 static definition.nodeHostCommands only
registered under runFullActivationOnlyRegistrations, so headless nodes
silently lost browser.proxy (and the browser/file caps), breaking the
meeting-bot chain with 'No connected Google Meet-capable node with
browser proxy'. Register node-host commands in every load mode; each
command keeps its own isAvailable gate. reload and security audit
collectors stay activation-only.
* fix(scripts): keep Windows lint runs from failing before any file is checked
`pnpm lint:extensions` aborts on Windows whenever the plugin SDK boundary
cache is cold: the boundary prep spawns `node_modules/.bin/tsgo` directly,
and Windows cannot execute the extensionless pnpm shim, so the run dies with
ENOENT before oxlint checks a single file. The stylelint runner reached by
`check-changed` fails the same way for the same reason.
Both now build their child process with `createManagedCommandInvocation`,
the launcher every other repo tool runner already uses, which routes the shim
through cmd.exe on Windows and returns the command unchanged everywhere else.
Off Windows the spawned command, arguments, and options are byte-identical to
before, so only the broken platform changes behavior.
* fix(scripts): stop the lint pipeline from spawning a tool shim directly
`pnpm lint` reaches stylelint through the same raw shim spawn that broke the
boundary prep, so the pipeline dies on Windows after oxlint succeeds. Route it
through the managed launcher like every other tool runner.
The remaining hazard is structural: the resolver hands out a path that only
some callers know to normalize, and the three sites that forgot were spread
across two spawn shapes. Add a static guard so a shim can only flow into a
launcher that understands the platform, and so the next occurrence fails on
Linux CI instead of on a contributor's Windows machine.
* test(scripts): trim Windows shim regression coverage
Punchcard-Session: cobalt-orchard-willow-2q
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>