## What Problem This Solves
Explicit blank or whitespace-only configure --section values were silently discarded by generic normalization, causing a scoped configuration request to unexpectedly launch the full setup wizard.
## Why This Change Was Made
The configuration-section parser now retains explicitly supplied blank values long enough to classify and report them as invalid, while preserving existing trimming and accepted-section behavior. The invalid-section message displays empty values clearly.
## User Impact
Operators who accidentally provide an empty section receive an actionable validation error instead of silently entering unrelated setup flows or changing the wrong configuration area.
## Context
Owner and independent reviewer exercised actual Commander-backed production paths with 13 and 12 passing assertions respectively. Regression coverage spans parser, command, and registered CLI boundaries. Existing oxfmt formatting and scoped whitespace checks passed; production code decreases by one line.
* fix(cli): route parent model status aliases
* refactor(cli): trim model status route plumbing
* refactor(cli): break parent path descriptor cycle
* fix(cli): align model status startup checks
* chore(cli): refresh plugin SDK API baseline
* fix: keep cron trigger once when editing only the script body
* test: cover cron trigger once preservation on script edit
* test: clean up temp script fixture in cron edit once test
The test leaked the mkdtemp directory under os.tmpdir. Remove it in a
finally block so repeated runs do not accumulate temp fixtures.
Fixes#120226
* test: prove trigger.once survives script edit via real gateway store
* fix(cron): preserve trigger metadata on script edits
Validate trigger script input before any Gateway lookup, retain existing trigger metadata during script-only edits, and replace mock-only handler coverage with isolated CLI-to-Gateway persistence proof.
Co-authored-by: Anuj Bolewar <bolewara@gmail.com>
* test(cron): normalize trigger-script rejection cases
Keep the table rows type-stable so the cron edit regression suite passes test type checking.
Co-authored-by: Anuj Bolewar <bolewara@gmail.com>
* style(cron): format trigger preservation tests
Co-authored-by: Anuj Bolewar <bolewara@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(cli): openclaw resume attaches the TUI to a recent session
Implements docs/plan/runners.md milestone 2.
* fix(cli): resume covers global sessions and preflights TTY
* fix(cli): require a terminal before resume
* fix(cli): adapt resume gateway disconnect state
* fix(cli): preserve global resume ownership
* test(cli): prove qualified global resume crosses the gateway boundary
* perf(agents): keep turn-path model catalog reads off the full live build
First agent turns (embedded and cron) resolved thinking capability through
loadPreparedModelCatalogSnapshot without readOnly, which materialized the
full live model-runtime catalog: ambient synthetic-auth discovery fanned out
to every registered provider and loaded plugin discovery modules through
jiti source transform (3,172 TS modules, 36s event-loop block, +600MB heap,
58.7s model-selection on a cold gateway).
- add loadProviderScopedThinkingCatalog: manifest metadata first, then a
provider-scoped read-only static catalog, then scoped live discovery only
for runtime-discovery providers (preserves #116584 Ollama semantics)
- route scopedLiveProviderDiscovery through the scoped read-only loader
- scope live-mode ambient synthetic-auth refs to the requested providers
- bound the last-resort synthetic-auth sweep to discovery entry modules
- memoize per-turn plugin skill dir resolution/republish (single-slot,
lifecycle-cleared; was a full walk + symlink republish every turn)
Cold first turn 72.7s -> ~22s wall (remaining cost is provider prefill of
the ~19.5k-token default prompt); model-selection 58,726ms -> 124ms.
* test(agents): align model-catalog.runtime mocks with scoped thinking catalog seam
Explicit vi.mock factories must export every binding prod touches; the new
loadProviderScopedThinkingCatalog export is now mocked everywhere the module
is stubbed, and the live-model-switch Ollama hydration test asserts the new
provider-scoped seam instead of the retired unscoped snapshot call shape.
* test(agents): export scoped thinking catalog from every prepared-catalog mock; split synthetic-auth helpers
- add loadProviderScopedThinkingCatalog to all explicit prepared-model-catalog
and model-catalog.runtime mock factories (vi.mock factories must export every
binding prod touches)
- move synthetic-auth ref scoping/resolution into
prepared-model-runtime.synthetic-auth.ts; keeps facts under the max-lines cap
* test(agents): prove scoped thinking hydration for runtime-only models
Boundary proof for the ClawSweeper review gap: the three-tier helper stops at
manifest or scoped-static when they resolve, and runs provider-scoped live
discovery (no broad fanout) only for runtime-only models; cron selection
hydrates through the same scoped helper and skips it entirely for thinking=off.
* test(agents): accept rest args in scoped thinking catalog mocks
* perf(plugins): declare doctor contract surfaces
* perf(doctor): slim migration import closures
* perf(plugins): narrow doctor declaration record surface and wire owner-test lane
Registry records carry only the doctorContract declaration instead of the whole
parsed manifest, and check:changed now selects the src/plugins-owned declaration
honesty and closure-guard tests for extension module/manifest changes so
cross-lane drift cannot pass PR classification.
* fix(doctor): keep control-plane dist imports require-safe
Keep doctor and channel control-plane chunks off exec-class dependencies, and enforce native require(esm) loading during postbuild.
* chore(plugin-sdk): regenerate API baseline
* chore(plugin-sdk): sync export ordering
* fix(plugins): satisfy doctor contract CI boundaries
* perf(doctor): make qqbot doctor closure dependency-light
qqbot was the last plugin above 5s in doctor state-migration enumeration
(~8s under tsx/jiti). The cost was not the state-key builder (already a
leaf): its doctor closure value-imported the runtime-doctor SDK barrel,
whose plugin-state-store/state-db re-exports pull kysely (~330 modules),
plus security-runtime for one fileExists (~200 modules), all resolved
per-module by jiti during enumeration.
Split the migration-define helpers and light re-exports into a new
private-local plugin-sdk/runtime-doctor-migrations subpath; runtime-doctor
re-exports it so its public surface is byte-identical (API baseline hash
unchanged). qqbot's doctor-contract and state-migrations now import only
the light subpath, swapping fileExists for the equivalent async
legacyStateFileExists already in the closure.
qqbot enumeration: ~8.0s/531 modules -> ~0.25s/18 modules.
* chore(plugin-sdk): drop private-local subpath from API baseline
runtime-doctor-migrations is private-local-only; the baseline tracks public
modules, and the earlier line was generated before the classification.
* fix(plugins): register runtime-doctor-migrations boundary paths
The private-local subpath list feeds the extension package boundary map;
the shared paths config and xai's derived overrides must carry the same
entry or the boundary contract test fails.
* feat(claws): export reviewed native bootstrap
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(claws): fail export when the package bootstrap drifted
Export re-emitted BOOTSTRAP.md only while the seeded copy was still pending, so
an agent whose bootstrap had been edited, flagged unsafe, or become unreadable
exported a package with no bootstrap at all. That is the same class of silent
loss the managed workspace files already guard against, so treat it the same
way: drifted bootstrap state now fails with `bootstrap_drifted` unless the
author supplies a reviewed `--bootstrap` replacement. A consumed bootstrap
stays a completed lifecycle state and still exports without BOOTSTRAP.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(claws): bind pending bootstrap export bytes
* fix(claws): preserve current export ownership limits
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* refactor(cli): inline state migration policy
Remove the single-use exported helper so migration policy now lives at ensureConfigReady, its lifecycle owner. Move duplicate argv and preaction coverage into the lifecycle-owner test table while preserving behavior. This follows #120451.
* refactor(cli): preserve gateway migration rationale
Keep the ownership invariant beside the inlined gateway-call exception.
Targeted config writes retain validation while Doctor and the gateway own state migration.
Fixes#120413
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ui): keep streamed media pinned to the chat
* test(ui): exercise real manual media scroll gestures
* fix(ui): settle chat follow at shared end boundary
* test(mcp): split OAuth CLI coverage
* style(mcp): format split CLI tests
* fix(ui): follow media growth during active streams
* test(ui): type active stream fixture
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>