Skip to content

perf: thread explicit plugin discovery through contracts registry#75451

Merged
RomneyDa merged 1 commit into
openclaw:mainfrom
SebTardif:cache-plugin-discovery-scan
May 19, 2026
Merged

perf: thread explicit plugin discovery through contracts registry#75451
RomneyDa merged 1 commit into
openclaw:mainfrom
SebTardif:cache-plugin-discovery-scan

Conversation

@SebTardif

@SebTardif SebTardif commented May 1, 2026

Copy link
Copy Markdown
Contributor

Problem

discoverOpenClawPlugins() is called independently by multiple plugin subsystems during startup. When callers in the same flow independently call discoverOpenClawPlugins with identical params, the filesystem walk repeats unnecessarily.

Approach

Add an optional discovery?: PluginDiscoveryResult parameter to the three functions that call discoverOpenClawPlugins internally:

  • loadBundledCapabilityRuntimeRegistry
  • resolveBundledPluginSources
  • listChannelCatalogEntries

When provided, the pre-computed result is used directly; when absent, the function calls discoverOpenClawPlugins as before (fully backward compatible).

In contracts/registry.ts, the retry loop in loadScopedCapabilityRuntimeRegistryEntries computes discovery once and passes it to both retry attempts, eliminating one redundant scan per retried load. The discovery is function-scoped, not module-scoped. discoverOpenClawPlugins() itself remains stateless.

Files changed

File Change
src/plugins/bundled-capability-runtime.ts Added optional discovery? param
src/plugins/bundled-sources.ts Added optional discovery? param
src/plugins/channel-catalog-registry.ts Added optional discovery? param
src/plugins/contracts/registry.ts Function-scoped discovery in retry loop
CHANGELOG.md Entry added

Closes #82308

Real behavior proof

Behavior addressed: plugin subsystem functions that call discoverOpenClawPlugins() internally now accept an optional pre-computed discovery result, allowing callers in the same startup flow to share one filesystem walk instead of repeating it per subsystem.
Real environment tested: local source checkout of openclaw/openclaw at commit 5d81c29 (current upstream/main) on Linux x86_64, Node v26.0.0, vitest 4.1.6. Used git worktree from the local fork at ~/openclaw/. Cherry-picked the feature commit onto upstream/main (zero source file conflicts).
Exact steps or command run after this patch: pnpm tsgo:core (type check); node scripts/run-vitest.mjs src/plugins/bundled-capability-runtime.test.ts src/plugins/bundled-sources.test.ts src/plugins/channel-catalog-registry.test.ts src/plugins/contracts/registry.ts (focused test suite covering all changed modules).
Evidence after fix: terminal output copied below.

Type check:

$ pnpm tsgo:core
$ node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo

No errors.

Test suite (14 tests across 4 changed modules):

$ node scripts/run-vitest.mjs src/plugins/bundled-capability-runtime.test.ts src/plugins/bundled-sources.test.ts src/plugins/channel-catalog-registry.test.ts src/plugins/contracts/registry.ts

 RUN  v4.1.6 /tmp/discovery-rework

 ✓ |unit-fast| ../../src/plugins/bundled-capability-runtime.test.ts (1 test) 2ms
 ✓ |contracts-plugin| ../../src/plugins/contracts/registry.ts (0 test) 0ms
 ✓ |plugins| ../../src/plugins/bundled-sources.test.ts (8 tests) 67ms
 ✓ |plugins| ../../src/plugins/channel-catalog-registry.test.ts (5 tests) 146ms

 Test Files  4 passed (4)
      Tests  14 passed (14)
   Duration  1.49s (transform 1.99s, setup 469ms, import 1.79s, tests 215ms, environment 0ms)

Observed result after fix: all 14 tests pass across the 4 changed modules. Type check is clean. The change is fully backward compatible: all existing callers continue working without the optional param. The only behavioral change is when the optional discovery param is provided, the internal discoverOpenClawPlugins call is skipped. The retry loop in contracts/registry.ts is the first caller to use this, computing discovery once and sharing it across retry attempts.

Runtime profiling (independent confirmation by @RomneyDa)

@RomneyDa independently profiled a TUI startup freeze (25-30s, process at 101% CPU) and confirmed the redundant discovery walks this PR targets are part of the measured problem. Full profiling methodology and data in this comment.

CPU profile (Node --cpu-prof, Bottom-Up):

% self time Activity
24% lstat (Node module resolution)
13% open
13% (total) realpathSync
9.5% (total) tryReadJsonSync from dist/json-files-*.js

Per-call instrumentation (temporary perfMark around tryReadJsonSync):

Metric Value
Total sync JSON reads 212,119
Unique JSON paths read 379
Average reads per file ~560x

An event-loop heartbeat probe (500ms setInterval) missed every tick during the freeze (gaps of 5s and 25s), confirming the freeze is synchronous JS work, not I/O wait.

Methodology: source-checkout branch, macOS, Node 22.22. Per-call instrumentation via temporary perfMark calls. CPU profile via NODE_OPTIONS="--cpu-prof --cpu-prof-dir=/tmp/openclaw-prof --cpu-prof-interval=200".

This PR directly addresses one source of the redundancy: subsystems independently invoking discoverOpenClawPlugins, and the retry loop re-discovering on each attempt. The function-scoped sharing matches the explicit design constraint from src/plugins/AGENTS.md (no persistent metadata caches). Remaining callers in loader.ts, manifest-registry.ts, installed-plugin-index-registry.ts, and config-contracts.ts would benefit from the same threading pattern in follow-up PRs.

@clawsweeper

clawsweeper Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The branch adds optional PluginDiscoveryResult parameters to three plugin helper functions, reuses one discovery result in the scoped contracts retry loop, and adds a changelog entry.

Reproducibility: yes. by source inspection: current main's scoped contracts retry loop can call the bundled capability runtime helper twice, and that helper discovers plugins internally. I did not establish a runtime benchmark in this read-only review.

PR rating
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Summary: The patch is small and structurally sound, but merge confidence is capped by mock-only proof and unresolved branch coordination.

Rank-up moves:

What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

PR egg
🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature, rarity, or ASCII portrait is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

Real behavior proof
Needs real behavior proof before merge: The PR body provides copied typecheck and focused Vitest output only; it still needs redacted real startup/discovery trace, benchmark, runtime log, or terminal output showing the after-fix path before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge
Why this matters: - The PR body supplies typecheck and focused Vitest output, but not a real startup trace, discovery trace, benchmark, runtime log, or other after-fix proof of the intended path.

  • The supplied GitHub context reports mergeable: false, and there is an overlapping fresher maintainer PR, so maintainers need a rebase or branch-selection decision before merge.
  • The patch only addresses the scoped retry/helper path; related profiling context says other discovery and module-resolution costs remain follow-up work.

Maintainer options:

  1. Decide the mitigation before merge
    Land one rebased version of the explicit function-scoped discovery threading with redacted runtime or perf proof, then track broader startup-read reductions separately.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge
Needs contributor real-behavior proof plus a maintainer choice between rebasing this PR or landing the overlapping fresher branch.

Security
Cleared: The diff only changes internal TypeScript plugin registry plumbing plus changelog text, with no concrete security or supply-chain concern found.

Review details

Best possible solution:

Land one rebased version of the explicit function-scoped discovery threading with redacted runtime or perf proof, then track broader startup-read reductions separately.

Do we have a high-confidence way to reproduce the issue?

Yes by source inspection: current main's scoped contracts retry loop can call the bundled capability runtime helper twice, and that helper discovers plugins internally. I did not establish a runtime benchmark in this read-only review.

Is this the best way to solve the issue?

Mostly yes: explicit function-scoped discovery threading matches the plugin metadata freshness rule better than a hidden cache. The remaining blockers are proof and branch coordination, not a different core approach.

Label justifications:

  • P3: This is a narrow internal plugin registry performance cleanup with limited blast radius and no user-data or security impact shown.

What I checked:

  • Current retry path repeats helper calls: Current main can call loadBundledCapabilityRuntimeRegistry twice inside the scoped capability retry loop, so a retry repeats the helper's work. (src/plugins/contracts/registry.ts:260, 9b97e1ef2fd2)
  • Current helper performs discovery internally: loadBundledCapabilityRuntimeRegistry currently computes discoverOpenClawPlugins({ env }) internally before loading the manifest registry. (src/plugins/bundled-capability-runtime.ts:235, 9b97e1ef2fd2)
  • PR threads a shared discovery result: The PR head computes one function-scoped discovery result and passes it into loadBundledCapabilityRuntimeRegistry across retry attempts. (src/plugins/contracts/registry.ts:259, 44991371bda5)
  • Optional fallback preserves existing callers: The PR head keeps fallback discovery when params.discovery is not supplied, so existing callers retain the old behavior. (src/plugins/bundled-capability-runtime.ts:236, 44991371bda5)
  • Plugin boundary favors explicit flow-owned metadata: The scoped plugin guide allows fresh metadata to be reused when a caller owns an explicit current-flow object, and rejects hidden persistent discovery caches. (src/plugins/AGENTS.md:30, 9b97e1ef2fd2)
  • Related maintainer context is not terminal supersession: The supplied GitHub context shows the overlapping maintainer PR is open, but the latest maintainer comment says this is a partial fix and they are considering merging this PR and building on it.

Likely related people:

  • steipete: Peter Steinberger has the largest recent commit count across the touched plugin registry files and authored central bundled capability/runtime stabilization and channel catalog discovery work. (role: heavy plugin registry contributor; confidence: high; commits: 46c292823425, 181a50e14659; files: src/plugins/bundled-capability-runtime.ts, src/plugins/channel-catalog-registry.ts, src/plugins/contracts/registry.ts)
  • gumadeiras: Gustavo Madeira Santana's d0e0150 added scoped contract registry retry coverage and substantially changed the retry helper that this PR optimizes. (role: introduced retry behavior; confidence: high; commits: d0e0150129fe; files: src/plugins/contracts/registry.ts, src/plugins/contracts/registry.retry.test.ts)
  • vincentkoc: Vincent Koc added the web fetch provider boundary in the same contracts registry surface, which is one of the capability-loading paths affected by discovery threading. (role: adjacent feature owner; confidence: medium; commits: 38d2faee20af; files: src/plugins/contracts/registry.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 9b97e1ef2fd2.

@SebTardif SebTardif changed the title perf: cache plugin discovery scan to eliminate redundant filesystem walks perf: thread explicit plugin discovery through contracts registry May 1, 2026
@SebTardif SebTardif force-pushed the cache-plugin-discovery-scan branch 3 times, most recently from ef7f5a9 to c578f18 Compare May 2, 2026 16:33
@SebTardif SebTardif force-pushed the cache-plugin-discovery-scan branch from e4c53d9 to dc6be2b Compare May 9, 2026 01:34
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 9, 2026
@SebTardif SebTardif force-pushed the cache-plugin-discovery-scan branch from dc6be2b to 72c24d9 Compare May 13, 2026 01:42
@SebTardif SebTardif force-pushed the cache-plugin-discovery-scan branch from 72c24d9 to 4499137 Compare May 17, 2026 04:56
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 17, 2026
@clawsweeper clawsweeper Bot added the P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. label May 17, 2026
@SebTardif SebTardif closed this May 17, 2026
@SebTardif SebTardif reopened this May 17, 2026
@RomneyDa

Copy link
Copy Markdown
Member

Hi @SebTardif — thank you for this fix. I've been profiling a TUI startup freeze (25–30s with the process pegged at 101% CPU, ~212K sync JSON reads against ~380 unique paths) and found that the redundant filesystem walks you addressed here are part of the picture.

I've reproduced your patch verbatim with attribution in #84258 so it can land on a fresher base alongside the profiling evidence and follow-up discussion of which call sites still need the same treatment. Your authorship is preserved via Authored-by: and Co-authored-by: trailers on the commit.

Closing this in favor of #84258. Happy to credit further if there are details about the original investigation you'd like included.

@RomneyDa RomneyDa closed this May 19, 2026
@RomneyDa RomneyDa reopened this May 19, 2026
@RomneyDa

RomneyDa commented May 19, 2026

Copy link
Copy Markdown
Member

EDIT, that was AI driven, it's a partial fix considering just merging this and building on it

@RomneyDa

RomneyDa commented May 19, 2026

Copy link
Copy Markdown
Member

Real behavior proof — profiling evidence

Posting this to address the ClawSweeper review's "needs real behavior proof before merge" block. I profiled a TUI startup freeze on main and confirmed the redundant discovery walks this PR targets are part of a measurable problem.

CPU profile (Node --cpu-prof, Bottom-Up)

TUI startup CPU was dominated by synchronous filesystem I/O, with the process at R+ 101% CPU throughout the freeze:

% self time Activity
24% lstat (Node module resolution)
13% open
13% (total) realpathSync
5% jiti runtime TS transpile (node_modules/jiti/dist/jiti.cjs)
3% (garbage collector)
9.5% (total) tryReadJsonSync from dist/json-files-*.js

Per-call instrumentation (temporary perfMark around tryReadJsonSync)

One TUI startup against a running gateway:

Metric Value
Total sync JSON reads 212,119
Unique JSON paths read 379
Average reads per file ~560×
Plugin module loads 248 (62 unique modules)
Loaded via jiti (slow path) 119 (~48%)

Top read offenders in one run:

3838  ~/.openclaw/npm/node_modules/@openclaw/codex/package.json
2166  ~/.openclaw/plugins/installs.json
1565  extensions/whatsapp/package.json
1565  extensions/anthropic-vertex/package.json
1565  extensions/amazon-bedrock/package.json
... (50+ extensions, all ~1565× each)

Event-loop heartbeat

A 500 ms setInterval probe missed every tick during the freeze (heartbeat gaps of 5 s and 25 s logged on a single run), confirming the freeze is synchronous JS work, not I/O wait. Wall-clock duration varied 3.7 s → 27 s → 30 s across runs, tracking OS file-cache temperature.

How this PR fits

This PR directly addresses one source of the redundancy: subsystems independently invoking discoverOpenClawPlugins, and the retry loop in loadScopedCapabilityRuntimeRegistryEntries re-discovering on each attempt. The function-scoped sharing matches the explicit design constraint from src/plugins/CLAUDE.md (no persistent metadata caches).

It is unlikely to fully resolve the freeze by itself — the manifest-read amplifier (~1,565× per extension) suggests the dominant cost is inside the loader / Node require() walks during plugin module loading, not in outer discovery scans. Estimated impact of this PR is a partial improvement; the remaining callers in loader.ts (2 sites), manifest-registry.ts, installed-plugin-index-registry.ts, and config-contracts.ts would benefit from the same threading pattern in follow-up PRs.

Methodology

  • Source-checkout branch, macOS, Node 22.22.
  • Per-call instrumentation: temporary perfMark calls appended to /tmp/openclaw-perf.log via appendFileSync. Available on the audit branch if useful.
  • CPU profile: NODE_OPTIONS="--cpu-prof --cpu-prof-dir=/tmp/openclaw-prof --cpu-prof-interval=200" node ./openclaw.mjs tui, opened in Chrome DevTools / VS Code.

…m walks

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 19, 2026
@RomneyDa RomneyDa force-pushed the cache-plugin-discovery-scan branch from 4499137 to e9f2cb0 Compare May 19, 2026 19:15
@RomneyDa RomneyDa merged commit 28beea9 into openclaw:main May 19, 2026
25 checks passed
@SebTardif

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed proof: supplied External PR includes structured after-fix real behavior proof. labels May 19, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
galiniliev pushed a commit to galiniliev/openclaw that referenced this pull request May 25, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
galiniliev pushed a commit to galiniliev/openclaw that referenced this pull request May 25, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
galiniliev pushed a commit to galiniliev/openclaw that referenced this pull request May 25, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
galiniliev pushed a commit to galiniliev/openclaw that referenced this pull request May 25, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
SebTardif added a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
SebTardif added a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
SebTardif added a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…y, installed-index, and config contracts (openclaw#84283)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…84302)

* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).

* perf(plugins): drop verbose JSDoc from discovery? params

* perf(plugins): scan-scoped package.json cache in discovery

Adds a per-scan Map<string, PackageManifest | null> threaded through
discoverFromPath/discoverInDirectory/readCandidatePackageManifest, keyed
by the directory's resolved real path. Within one discovery scan, a
plugin's package.json is now read from disk once and reused across the
overlapping discovery code paths (bundled overlay scan, stock-root scan,
source-checkout extensions scan, installed-path scan, global-root scan)
that previously each fired their own read.

The cache lifetime is one scan (created in runPluginDiscovery alongside
the existing realpathCache and seen Set, dies when the scan returns).
discoverOpenClawPlugins remains stateless externally; no persistent
metadata cache.

* perf(plugins): expose raw parsed package.json on PluginCandidate

Discovery already reads each plugin's package.json once and produces a
parsed PackageManifest object before distilling it into metadata via
getPackageManifestMetadata. Currently only the distilled metadata is
kept on the candidate; the full parsed manifest is discarded.

Store the full parsed manifest on rawPackageManifest so downstream
consumers iterating candidates can use it instead of re-reading from
disk. This is the candidate-side groundwork for the scenario-C followup
that routes consumers (bundled-plugin-metadata, bundle-* helpers, etc.)
through the cached field; those consumers currently do their own
directory scans and would need to be refactored to iterate
PluginCandidate arrays before they can benefit.

The field is a frozen-at-discovery-time snapshot, same lifetime semantics
as the existing packageManifest / packageName / packageVersion fields on
PluginCandidate. No new staleness window introduced.

* perf(plugins): make package-manifest cache key trust-aware
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…m walks (openclaw#75451)

Add optional discovery parameter to loadBundledCapabilityRuntimeRegistry,
resolveBundledPluginSources, and listChannelCatalogEntries so callers
that already hold a PluginDiscoveryResult can skip redundant filesystem
walks.

In contracts/registry.ts, the retry loop in
loadScopedCapabilityRuntimeRegistryEntries computes discovery once
and shares it across retry attempts (function-scoped, not module-scoped).

discoverOpenClawPlugins() itself remains stateless with no hidden cache.

Closes openclaw#82308

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…y, installed-index, and config contracts (openclaw#84258)

Follow-up to openclaw#75451. Threads optional discovery?: PluginDiscoveryResult
through the remaining helpers that still call discoverOpenClawPlugins
internally during startup:

- loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts):
  add discovery? to PluginLoadOptions and consult it before falling back to
  an internal scan at both call sites.

- loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept
  discovery? as a more ergonomic alternative to the existing candidates? /
  diagnostics? pair; candidates? still wins when both are supplied.

- resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts):
  add discovery? to LoadInstalledPluginIndexParams and use it when
  candidates aren't supplied.

- resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add
  discovery? and thread it into the bundled-fallback discovery call.

Add discovery-threading.test.ts asserting each entry point skips its
internal discoverOpenClawPlugins call when discovery is supplied, calls it
when nothing is supplied, and prefers explicit candidates over discovery
when both are present (6 tests, all pass).

discoverOpenClawPlugins remains stateless; sharing is function-scoped per
src/plugins/CLAUDE.md guidance. Backward compatible: every change is
additive (new optional param).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Perf]: Redundant filesystem walks in plugin discovery during startup

2 participants