Commit Graph

73 Commits

Author SHA1 Message Date
can1357 e5e93ff762 feat(packages/coding-agent): enabled setup-version-gated startup flow
- Deferred setup wizard import until setup was forced or version stale.
- Dynamically loaded ACP, RPC, and print mode runners only when used.
- Added a marketplace auto-update scheduler with off-mode early exit and non-blocking errors.
- Added setup-version assertions to keep CURRENT_SETUP_VERSION aligned with scenes.
2026-06-06 22:58:33 +02:00
can1357 8a5b99a967 feat(coding-agent): enabled anonymous Perplexity fallback and updated web-search checks
- Added anonymous Perplexity authentication mode for unauthenticated web searches.
- Switched web-search setup checks to use `isExplicitlyAvailable` and removed key enforcement in doctor.
- Updated Perplexity OAuth flow to reuse auth handling for all non-key searches and anonymous responses.
- Updated CLI and provider option help text to mark the Perplexity key optional with fallback.
2026-06-06 19:42:16 +02:00
roboomp c4fa93e460 fix(plugins): preserved null package import exclusions
Kept explicit null package imports as exclusions so exact entries and active conditions do not fall through to wildcard or fallback targets.\n\nFixes #1889
2026-06-05 07:59:28 +00:00
roboomp 994ae513f5 fix(plugins): skipped non-source package import targets
Returned null from resolveSourceModuleFile when a package imports alias points at a JSON, WASM, or other non-code asset so the on-load rewrite hook no longer claims it and forces it through the JS loader. Bun resolves these targets natively. Added a regression where #schema maps to a .json file imported with a JSON type assertion.\n\nFixes #1889
2026-06-04 21:54:33 +00:00
roboomp 251d9152fc fix(plugins): honored package import condition order
Selected conditional package import targets by package.json object order for supported Bun runtime conditions, including node, instead of probing a fixed precedence list. Added a regression where node precedes import and must be selected for a plugin-local #src/* import.\n\nFixes #1889
2026-06-04 21:51:26 +00:00
roboomp 6e47ae8150 fix(plugins): matched side-effect imports in compat rewrite
Extended the legacy Pi, TypeBox, package-import alias, and extension graph regexes to recognise the bare \"import \\"specifier\\";\" shape so side-effect-only loads such as \"import \\"#src/register\\";\" walk into the source graph and get their legacy @(scope)/pi-* imports rewritten. Added a regression that loads a plugin with a side-effect alias import whose target contains a legacy scope import.\n\nFixes #1889
2026-06-04 21:49:06 +00:00
roboomp b2c6b7becf fix(plugins): loaded package import extensions
Resolved plugin-local package import aliases while loading extension source graphs so legacy Pi plugins with TypeScript source imports like #src/* register correctly under OMP. Added a regression covering legacy scope rewrites through a package import module.\n\nFixes #1889
2026-06-04 21:42:52 +00:00
can1357 ab935e992c fix(legacy-pi-compat): scope rewrite hook to the import graph, not a dir
Address review of the in-place loader: the directory-subtree filter had two
regressions vs the old mirror.

- It only rewrote files under the entry's package root, so a `dist/`
  entry importing `../../shared/helper.ts` (or a symlink-escaping sibling)
  left that module's legacy `@(scope)/pi-*` / `@sinclair/typebox` imports
  un-rewritten.
- `findExtensionRoot` walked up to the nearest package.json, which for an
  ad-hoc extension under a project (e.g. `/repo/.omp/extensions/foo.ts`)
  resolved to the project root — so the permanent onLoad hook would then
  rewrite unrelated project/host source imported later.

Replace the directory filter with a precise scope: pre-walk the entry's
relative-import graph (static + dynamic `./`/`../` specifiers), collect each
module's realpath, and build the onLoad filter as an exact-path alternation
of just those modules. This matches exactly the set the old mirror tracked
(minus the copy): it covers `../src`/symlinked siblings and never touches
the host, other extensions, node_modules deps, or unrelated project files.

Adds a regression test that a non-imported sibling stays outside the rewrite
scope, and renames the ../src test to reflect graph-following.
2026-06-02 09:15:44 +02:00
can1357 c5e3698f45 fix(legacy-pi-compat): load extensions in place instead of mirroring
Legacy Pi extensions were mirrored module-by-module into a flat temp dir
(`omp-legacy-pi-file/entry-<hash>/`) with imports rewritten to absolute
URLs. Running from that temp root made `import.meta.url`/`__dirname`
resolve to the mirror, so `readFileSync(join(__dirname, "ui.html"))`-style
asset loads ENOENT'd — e.g. @plannotator/pi-extension's HTML never loaded
and it auto-approved plans (#1674). The standing remedy (#1675) copied
~30MB of .html/.css per startup with a fragile extension whitelist.

Bun's runtime plugins don't fire onResolve for transitive imports, which
is why the mirror pre-resolved everything. But onLoad does fire
transitively for file-namespace modules matching its filter, and a real
file import keeps import.meta.url pointing at the source. So:

- Load the extension entry in place via `import(pathToFileURL(real))`;
  realpath first so the path matches what Bun hands onLoad (macOS
  /var->/private/var, bun link/pnpm symlinks).
- Register one Bun.plugin() onLoad per extension *package root* (nearest
  package.json), filtered to that root's .js/.ts but excluding any
  node_modules segment, that rewrites only `@(scope)/pi-*` and the bare
  `@sinclair/typebox` specifier to absolute bundled/shim URLs.
- Everything else — relative siblings (incl. ../src), the extension's own
  node_modules deps (CJS/ESM), and bundled assets — resolves natively.

Removes the flat mirror, the temp-dir writes, the asset-copy problem, and
the now-dead `omp-legacy-pi-file:` namespace machinery. import.meta.url is
the real source file, so assets resolve exactly as under the original Pi
runtime. Adds an in-place load regression test covering asset reads,
submodule .css siblings, node_modules-excluded native deps, and
package-root-scoped ../src rewrites.

Fixes #1674.
2026-06-02 09:15:44 +02:00
can1357 9f547d8e4f refactor(mnemosyne/core): typed embedding provider outputs and tightened normalization
- Defined `EmbeddingRow` and `EmbeddingOutput` in runtime options and exported them from core embeddings.
- Updated `EmbeddingProvider`, `MnemosyneEmbeddingProvider`, and `provider` runtime option types to return `EmbeddingOutput` instead of `unknown`.
- Refactored embedding result normalization to accept typed rows and sync/async batches and coerce them into validated `Float32Array` vectors.
2026-05-31 05:32:28 +02:00
can1357 495c570ed4 fix(coding-agent): normalize hosted plugin git shorthands
Addresses review feedback on #1527.
2026-05-31 04:47:39 +02:00
oldschoola 84a50e8921 fix(coding-agent): classify git+ and unprefixed scp specs as git
Codex review on #1527 flagged that the documented forms
`git+https://github.com/user/repo` and `git@github.com:user/repo` still
fell through to the npm install path. `git+https` was rejected by the
package-name validator; scp-style `git@…` passed the validator but then
resolved `actualName` via `extractPackageName` to `git` (everything before
the `@`), causing the post-install package.json lookup to fail at
`node_modules/git/package.json`.

- `parseGitUrl`: strip leading `git+` (forwarded to bun/git as-is) and
  extend the protocol gate to also accept scp-like `git@host:user/repo`.
  The scp form is unambiguous — no local path starts with `git@` — and
  matches what `git clone` itself takes.
- `isGitSpec` now returns true for both forms, routing them through the
  snapshot/diff path in `PluginManager.install` so the real package name
  is discovered correctly.
- Tests: flip the two cases that asserted rejection, add ref and
  `git+ssh` coverage. Verified end-to-end:
  `PluginManager.install('git+https://github.com/oldschoola/omp-insights')`
  installs `@oldschoola/omp-insights@1.2.3`.
2026-05-31 04:46:08 +02:00
oldschoola 22e564a85d feat(coding-agent): accept GitHub/git URLs in plugin install
Extends `omp plugin install` to accept git sources alongside npm specs and
marketplace refs. Bun's installer already understands git URLs; the blocker
was `PluginManager.install`'s strict npm-name validator and the assumption
that the actual package name could be derived from the spec.

- `git-url.ts`: `parseGitUrl` now recognizes npm-style namespaced shorthand
  (`github:user/repo`, `gitlab:`, `bitbucket:`, `codeberg:`, `sourcehut:` /
  `srht:`), with optional `#ref` and `.git` suffix. Exposes `isGitSpec` as
  `parseGitUrl(s) !== null`. Existing protocol-URL and `git:` shorthand paths
  are untouched.
- `manager.ts`: `install()` branches on `isGitSpec`. Git specs go through a
  separate `validateGitSpec` (shell-metachar rejection only — `/`, `:`, `@`,
  `#`, `+` are legal) and the real package name is discovered by snapshotting
  `plugins/package.json` deps before `bun install` and diffing afterwards.
  Falls back to value-match on force-reinstall where the key already exists.
- Help text in `plugin-cli` documents the new sources and adds a github:
  example.

Smoke tested end-to-end on Windows with both forms against the test repo:
  PluginManager.install('github:oldschoola/omp-insights')
  PluginManager.install('https://github.com/oldschoola/omp-insights')
both resolve `@oldschoola/omp-insights@1.2.3` and write a correct lock entry.
Shell-injection probe (`github:foo/bar; rm -rf /`) is rejected.
2026-05-31 04:46:08 +02:00
roboomp 2780f5dec6 fix(coding-agent): kept legacy-pi paths under compiled bunfs root
Bun 1.3 compiled modules report the bunfs mount root from import.meta.dir, not their source-layout module directory. The prior helper walked four directories above that value and escaped the embedded root.

Append packages to the compiled bunfs root, while keeping a guarded suffix path for future module-specific import.meta.dir semantics. Update regression tests to pin the compiled-root behavior observed by a bun build --compile probe.

Fixes #1514
2026-05-29 19:44:36 +00:00
roboomp 9e35b784d8 fix(coding-agent): derived legacy-pi compat bunfs root from import.meta.dir for windows
External extensions importing @oh-my-pi/pi-* values (e.g. AssistantMessageEventStream from @oh-my-pi/pi-ai) failed on Windows compiled binaries with "Cannot find package $bunfs\\root\\packages\\...". Shim paths in LEGACY_PI_PACKAGE_ROOT_OVERRIDES were built from a hardcoded POSIX literal "/$bunfs/root/packages"; Win32 normalised the leading slash to a backslash and the path never resolved against the real bunfs mount (<drive>:\\~BUN\\root\\...).

Derive the bunfs package root by walking four directories up from import.meta.dir (which oven-sh/bun#15766 confirms returns the platform-native bunfs path inside the binary). All override targets now go through path.join, so separators stay native on Windows, Linux, and macOS.

Fixes #1514
2026-05-29 19:39:50 +00:00
roboomp 4d108c1951 fix(plugins): enumerate linked plugins for sub-discovery
PluginManager.link symlinks the package into <plugins>/node_modules
and records it in omp-plugins.lock.json, but never writes to
<plugins>/package.json#dependencies. getEnabledPlugins iterated only
the dependency map, so the documented `omp install ./local-extension`
workflow (delegated to plugin link) succeeded but its sibling skills/,
hooks/, tools/, etc. stayed invisible after install.

Iterate the union of package.json#dependencies and
omp-plugins.lock.json#plugins so symlinked-only packages surface
alongside npm/marketplace installs. Lockfile entries whose
node_modules tree has since been deleted (stale link) are skipped
silently. Linked-only setups with no <plugins>/package.json at all
now work too.

Per-PR review feedback: https://github.com/can1357/oh-my-pi/pull/1498
2026-05-29 06:34:10 +00:00
roboomp bdce9cf068 feat(discovery): scan installed plugin packages for sub-discovery
Marketplace and `omp plugin link` installs write to
`<plugins>/node_modules/` rather than to `extensions:` in settings,
so the original PR still missed their sibling skills/, hooks/,
tools/, commands/, rules/, prompts/, .mcp.json sub-trees. Wire
listOmpExtensionRoots to enumerate getEnabledPlugins(cwd, { home })
in addition to CLI-injected and settings-driven roots.

Adds an optional { home } parameter to getEnabledPlugins so the
discovery loader can pass through LoadContext.home for tempdir-rooted
tests. The getPluginsNodeModules/getPluginsPackageJson/
getPluginsLockfile helpers gain the same optional home overload so
they mirror getPluginsDir.

Per-PR review feedback: https://github.com/can1357/oh-my-pi/pull/1498
2026-05-29 06:28:33 +00:00
can1357 625c8b1992 test(ai): updated tests for model metadata and auth token fallback
- Mocked the Vertex stream E2E test to override the home directory and clear GOOGLE_APPLICATION_CREDENTIALS so token resolution uses metadata credentials instead of local ADC files.
- Updated wafer and model-registry test expectations to match current model metadata values (Qwen3.7 Max and claude-opus-4-8).
2026-05-29 06:56:29 +02:00
roboomp 437b6524cd fix(cli): restored package resolver for non-compiled pi root remaps
Limited bunfs package-root overrides to compiled-binary mode so non-compiled installs (monorepo, source-link, node_modules) keep resolving legacy pi roots through Bun's package resolver instead of a hardcoded source-tree path.

Refs #1474
2026-05-29 06:42:47 +02:00
roboomp 349fb51ddb fix(cli): preserved legacy peer fallback for subpaths
Retried original legacy specifiers after canonical peer fallback fails so direct plugin imports with only legacy-scoped peer dependencies continue to load.
2026-05-29 06:42:46 +02:00
roboomp 564b6d0f22 fix(cli): routed legacy pi-coding-agent imports through a sibling shim
Listing the coding-agent's own ./src/index.ts as a bun --compile extra entrypoint silently breaks the CLI binary startup. Added a dedicated legacy-pi-coding-agent-shim.ts that re-exports the canonical barrel, registered the shim instead of the package index, and updated the compat resolver to point pi-coding-agent at the shim path.
2026-05-29 06:42:46 +02:00
roboomp 82008e4f38 fix(cli): restored legacy pi package root remaps
Added bundled root overrides for legacy pi package imports in compiled binaries and corrected fallback resolution to use canonical @oh-my-pi specifiers.

Fixes #1474
2026-05-29 06:42:46 +02:00
roboomp 802e3f3e63 fix(extensibility): resolved compat shims to bunfs entry paths in compiled binaries
Reviewer caught that the new `legacy-pi-ai-shim.ts` is only referenced
via a computed string path, so Bun's `--compile` static analyzer cannot
trace it into bunfs. The same gap existed for the pre-existing
`typebox.ts` shim — `path.resolve(import.meta.dir, "../typebox.ts")`
collapses to `/$bunfs/typebox.ts` in compiled mode (where
`import.meta.dir` is `/$bunfs/root`), which does not exist in bunfs.
Legacy plugins importing bare `@sinclair/typebox` or `@(scope)/pi-ai`
therefore hit "Cannot find module" in release builds.

- Branch `TYPEBOX_SHIM_PATH` and `LEGACY_PI_AI_SHIM_PATH` on
  `isCompiledBinary()`: in compiled mode they point at the
  `--root`-relative bunfs entry path with a `.js` extension
  (`/$bunfs/root/packages/coding-agent/src/extensibility/<file>.js`);
  in dev they keep the `path.resolve(import.meta.dir, "../<file>.ts")`
  source path so tests still resolve against the workspace tree.
- List both shims as additional `--compile` entrypoints in
  `scripts/build-binary.ts` so Bun actually emits them into bunfs at the
  paths the runtime expects.

Verified with a focused `bun build --compile` probe: `Bun.resolveSync`
returns the expected bunfs path for both shims when they are listed as
entries, and fails when they are not. Dev-mode test suite unchanged
(17 pass / 0 fail across the four legacy-pi compat test files).
2026-05-27 12:34:27 +00:00
roboomp 6f7da0769b fix(extensibility): redirected legacy pi-ai root imports through Type-compat shim
Plannotator-class legacy extensions still import `Type` from
`@(scope)/pi-ai` (e.g. `@earendil-works/pi-ai` rewritten to
`@oh-my-pi/pi-ai`). pi-ai 15.1.0 removed the root `Type` runtime
export, so extension load crashed with `Export named 'Type' not found`
even though the `@sinclair/typebox` Zod-backed shim still ships in the
coding agent.

Routed bare `@oh-my-pi/pi-ai` root specifiers — used by both the
mirrored-source rewriter and the Bun.plugin onResolve hook — through a
new sibling shim that re-exports the canonical pi-ai surface plus the
`Type` runtime from the existing TypeBox shim. Subpath imports such as
`@oh-my-pi/pi-ai/utils/oauth` continue to resolve directly against the
bundled pi-ai package.

Fixes #1437
2026-05-27 12:14:57 +00:00
Can Bölük a40864c5b2 Merge branch 'main' into farm/ccf5d9fd/csharp-lsp-plugin-doesn-t-work-with-omp- 2026-05-25 20:37:30 +03:00
roboomp 24b249219e fix(lsp): supported config-only marketplace servers
Loaded marketplace lspServers metadata from Claude plugin caches and embedded it for OMP marketplace installs so config-only plugins register without package code.

Fixes #1352
2026-05-25 17:34:32 +00:00
roboomp a2032850e5 fix(legacy-pi-compat): fall back to peer deps when resolveSync fails in binary mode
In a compiled binary, Bun.resolveSync(spec, import.meta.dir) throws
'Cannot find module' because import.meta.dir is inside /$bunfs/root
and the virtual FS exposes no node_modules tree at runtime.

Previously this throw propagated through rewriteLegacyPiImports ->
rewriteLegacyPiImportsForRuntime -> mirrorLegacyPiFile ->
loadLegacyPiModule -> loadExtension, which swallowed it as 'Failed to
load extension' and silently dropped any plugin whose files imported
@mariozechner/pi-ai (or any @mariozechner/pi-* whose bundled
counterpart isn't reachable via resolveSync in the binary).

Fix: wrap the resolution call in rewriteLegacyPiImports in a try/catch
and return the original match on failure. rewriteBareImportsForLegacyExtension
runs immediately afterwards in every call path and already resolves bare
specifiers against the importer's real filesystem directory, so it picks
up @mariozechner/pi-ai from the plugin's installed peer deps instead.

Apply the same fallback to resolveLegacyPiSpecifier (the Bun plugin
shim's onResolve handler) for tool/hook files loaded directly via Bun's
import system rather than through loadLegacyPiModule.

Fixes #1215
2026-05-25 12:22:57 +02:00
can1357 8dc9125b0b fix(coding-agent): stabilize extension and hyperlink tests 2026-05-21 17:35:04 +09:00
can1357 43ed173e2e fix(coding-agent/extensibility): added remap for bare @sinclair/typebox imports to shim
- Added a dedicated `@sinclair/typebox` specifier remap path and routed bare legacy imports to the in-repo shim during extension rewriting and module resolution.
- Added resolve hooks for both `file` and legacy-file namespaces so legacy extensions load the shim consistently at runtime.
2026-05-15 15:26:44 +02:00
oldschoola 69043d3071 fix(extensibility): treat Windows drive-letter paths as filesystem in legacy-pi mirror
isUrlLikeSpecifier matched Windows absolute paths (e.g. `C:\foo`) because the URL-scheme regex `^[A-Za-z][A-Za-z\d+.-]*:` happily eats a single drive letter. When a legacy plugin extension imported a bare-specifier dep from its own `node_modules`, rewriteBareImportsForLegacyExtension resolved it to an absolute path, then toRewrittenImportSpecifier short-circuited pathToFileURL and embedded the raw Windows path into the mirrored TS source.

The TS string-literal parser then ate \n, \U, \y and friends, producing nonsense package specifiers like `C:Usersjames.ompagentextensionssupipowers\node_modulesyamldistindex.js` that Bun rejected with `Cannot find package …`. Net effect: every legacy extension that pulls in any node_modules dep failed to load on Windows.

Fix: reject `^[A-Za-z]:[\\/]` in isUrlLikeSpecifier before the URL-scheme test so drive-letter paths flow through pathToFileURL and reach the mirror as proper `file:///C:/...` URLs.
2026-05-15 02:44:49 -07:00
can1357 a8477be7a7 fix: corrected legacy pi-* aliases and restored pi-tui key mapping
- Fixed legacy `pi-*` scope alias remapping to canonical packages, including `pi-ai/oauth` rewrites.
- Fixed restoration of `Key` on `@oh-my-pi/pi-tui` with canonical key strings and typed modifier helpers.
- Updated both package changelogs with unreleased notes for compatibility and `Key` restoration.
- Added `pi-scope-aliases.test.ts` coverage for alias remaps using fixture plugins and `loadExtensions`.
2026-05-15 00:03:57 +02:00
ephraimduncan 93420c5782 fix(coding-agent/plugins): resolve directory manifest extension entries to their index.{ts,js,mjs,cjs}
Plugin manifests that declare a directory entry (e.g. pi-goal's
`"pi": { "extensions": [".pi/extensions/pi-goal"] }`) were
pushed through to the legacy module loader unchanged. `Bun.file()`
on a directory throws "Directories cannot be read like files",
so every such plugin failed to load.

`resolvePluginPaths` now routes each joined manifest entry through
`resolveManifestEntryFile`, which:
- returns the path as-is when it is a file,
- returns the directory's `index.{ts,js,mjs,cjs}` when it is a
  directory containing one of those files,
- otherwise returns null (skip).

This mirrors the documented native auto-discovery behavior in
`resolveExtensionEntries` and the candidate list in
`extensibility/custom-commands/loader.ts`.

Closes #1001.
2026-05-11 00:05:00 +00:00
jiwangyihao a157b878f4 fix(plugins): 避免预解析动态相对导入 2026-05-10 03:35:46 +08:00
jiwangyihao 40e8b37202 fix(plugins): 保留 legacy 扩展的 Node 内置导入 2026-05-10 03:24:02 +08:00
jiwangyihao a7fcc0f63b fix(plugins): 修复 Windows legacy Pi 扩展加载 2026-05-10 03:13:51 +08:00
can1357 668ab38fd4 fix(coding-agent): resolve bare imports from extension node_modules in legacy-pi shim
Files loaded via the omp-legacy-pi-file: namespace bypass Bun's normal
node_modules lookup because the importer lives in a custom namespace,
so an extension that imports its own bundled dependencies (e.g.
`import parseDuration from "local-duration-parser"`) failed with
`Cannot find package`. Pre-resolve bare specifiers in the onLoad step
against the importer's directory so extensions can ship their own
node_modules. Relative, absolute, node:, and URL specifiers are left
untouched, preserving the existing legacy @mariozechner/pi-* remap
path.
2026-05-09 06:45:44 +02:00
can1357 05274585d8 feat(coding-agent): support legacy pi extension imports
Some extensions/plugins still import the legacy @mariozechner/pi-* package names (pi-agent-core, pi-ai, pi-coding-agent, pi-tui) instead of @oh-my-pi/pi-*. Register a single Bun.plugin on first plugin/extension load that rewrites those specifiers to the current @oh-my-pi/pi-* equivalents, including the @mariozechner/pi-coding-agent/extensibility/{extensions,hooks} sub-exports. No filesystem mutation, no symlinks, no proxy files.

Fixes #973
2026-05-09 04:02:20 +02:00
can1357 6d07944654 refactor: migrated binary detection to $which() utility across codebase
- Replaced all Bun.which() calls with $which() utility from @oh-my-pi/pi-utils across 22 files.
- Removed findBashOnPath() wrapper function from procmgr.ts, consolidating binary path resolution.
- Updated AGENTS.md documentation to reflect new $which() API usage pattern.
- Centralized binary detection logic through shared utility, reducing code duplication.
2026-04-08 05:28:22 +02:00
can1357 418b6801a0 refactor(coding-agent): migrated git operations to centralized utils module
- Extracted git operations from ControlledGit class into centralized utils/git module with 1276 lines of typed command wrappers.
- Replaced dependency injection of ControlledGit instances with direct cwd string parameters across commit agent tools and workflows.
- Migrated all git command execution from inline shell calls and custom helpers to structured git module API (diff, status, branch, worktree, patch, etc.).
- Removed ControlledGit class, operations.ts, and helper functions (findGitHeadPath, mergeStdoutStderr, joinPatch) now provided by git module.
- Exported git utilities from main package entry point for extension and plugin use.
2026-04-02 07:35:44 +02:00
can1357 5f0944ffe2 feat: added marketplace help command and improved catalog parsing resilience
- Added `/marketplace help` subcommand to display marketplace operations usage guide.
- Enhanced marketplace catalog parsing to skip invalid entries with warnings instead of failing.
- Improved `/marketplace discover` command to suggest official marketplace when no plugins available.
- Fixed marketplace error messages to display error details instead of object stringification.
- Added comprehensive marketplace plugin system documentation covering discovery, installation, and management.
2026-04-01 19:31:48 +02:00
can1357 94edc693cb fix(marketplace): corrected plugin name validation to allow dots
- Corrected plugin name validation regex to allow dots in addition to hyphens.
2026-04-01 19:09:55 +02:00
Miroslav Drbal 5acd56a5e3 fix(coding-agent): only shadow user entry when project install is enabled
listInstalledPlugins() was marking user-scope entries as shadowedBy:
"project" based on plugin ID presence in the project registry alone.
A disabled project copy (enabled: false) is not active in discovery, so
the user copy is the effective install and must not appear shadowed.

activeProjectIds now only includes project plugins where the first entry
does not have enabled: false.
2026-03-31 22:03:03 +02:00
Miroslav Drbal 05966c8910 feat(coding-agent): marketplace plugin project scope
Plugins can now be installed at user scope (global) or project scope
(per-project, higher capability priority). Scope is encoded in registry
file location, not a metadata field:

  user:    ~/.omp/plugins/installed_plugins.json
  project: <nearest-project>/.omp/plugins/installed_plugins.json
  cache:   ~/.omp/plugins/cache/plugins/  (shared, path-referenced)

Project root discovery: resolveActiveProjectRegistryPath(cwd) walks up
from cwd looking for the nearest .omp/ directory, falling back to the
nearest .git root. This is the single resolver used by install, uninstall,
list, upgrade, discovery, and doctor.

Discovery: listClaudePluginRoots(home, cwd?) reads both registries when
cwd is provided. Project entries shadow user entries for the same plugin
ID. Cache key is canonical ("${home}:${resolvedProjectPath}") so nested
cwds within the same project share a cache entry.

Manager changes:
- installPlugin({ scope? }): routes registry reads/writes by scope;
  checks collectReferencedPaths() across both registries before deleting
  any cached plugin dir to prevent cross-scope data loss
- uninstallPlugin(id, scope?), setPluginEnabled(id, enabled, scope?),
  upgradePlugin(id, scope?): throw a disambiguation error when the plugin
  exists in both scopes and no scope is specified
- upgradePluginAcrossScopes(id): new; upgrades all scopes the plugin is
  installed in; returns InstalledPluginEntry[]
- upgradeAllPlugins(): uses upgradePluginAcrossScopes; result includes scope
- listInstalledPlugins(): returns InstalledPluginSummary[] merged from both
  registries; user entries marked shadowedBy: "project" when overridden

CLI: omp plugin install|uninstall|upgrade|enable|disable --scope user|project
Slash: /marketplace install [--scope user|project] name@marketplace
MarketplaceManager constructed with projectInstalledRegistryPath in all
CLI handlers and builtin-registry.ts via resolveActiveProjectRegistryPath.

.gitignore: .omp/plugins/ added (local runtime state, not committed).
preloadPluginRoots/clearClaudePluginRootsCache carry cwd through for LSP.
main.ts passes getProjectDir() at startup.

Tests: 226 pass across 13 files. New: project-scope.test.ts (resolver
walk-up, .git fallback, null return, canonical path, shadow precedence);
manager scope tests (registry isolation, disambiguation errors, cross-scope
cache-ref protection, upgradePluginAcrossScopes, shadowedBy marking).

fixes #581
2026-03-31 17:43:09 +02:00
can1357 d18504d632 fix(coding-agent): corrected marketplace plugin selector error handling and cleanup
- Added error handling for marketplace plugin selector failures with user-facing error messages.
- Fixed marketplace clone promotion timing by extracting logic into separate function and applying consistently across addMarketplace and refreshMarketplace methods.
- Added cleanup of temporary clone directories on source verification failures to prevent orphaned files.
- Improved plugin selector UI rendering by expanding onCancel callback to trigger requestRender after completion.
- Fixed LSP config path handling by replacing hardcoded filename array with variable reference.
- Enhanced error handling for unknown plugin source variants in marketplace validation.
2026-03-30 13:54:31 +02:00
Miroslav Drbal 304158cadb fix: check duplicate marketplace before fetching, reject relative sources for URL marketplaces
- addMarketplace reads registry before fetching; if the fetched catalog
  name collides with an existing entry, cleans up the clone and throws
  before any cache corruption occurs
- installPlugin rejects relative string sources when the marketplace was
  added via URL (only marketplace.json is cached, not the plugin tree)
2026-03-30 13:44:55 +02:00
Miroslav Drbal f65df186b8 fix: refresh slash commands on /reload-plugins, validate string source paths at parse time
- /reload-plugins now calls refreshSlashCommandState() so newly
  installed/removed plugin commands take effect without restart
- parseMarketplaceCatalog rejects string sources that don't start
  with './' at parse time instead of deferring to install-time crash
2026-03-30 13:44:55 +02:00
Miroslav Drbal 9218b1450b fix: guard npm dist-tags in classifier, reject catalog name drift, validate source payloads
- classifyInstallTarget skips known npm dist-tags (latest, next, beta,
  etc.) and semver-like strings before checking marketplace names
- updateMarketplace throws if fetched catalog name differs from the
  registered name, preventing stale data on upstream renames
- parseMarketplaceCatalog validates variant-specific required fields
  (github.repo, url.url, git-subdir.url+path, npm.package) at parse
  time instead of deferring to install-time crashes
2026-03-30 13:44:55 +02:00
Miroslav Drbal 0b85d3a1ad fix: validate plugin names at parse time, atomic cache replacement
- parseMarketplaceCatalog rejects plugin names that fail isValidNameSegment
  instead of deferring the error to install time
- cachePlugin copies to staging dir first, then renames into place;
  a failed copy no longer destroys the active install
- Fix ReturnType<> usage in cloneAndReadCatalog
2026-03-30 13:44:55 +02:00
Miroslav Drbal b134c6628b fix: preserve disabled flag on force-reinstall, re-warm preloaded roots after cache clear
- Carry over enabled:false from existing entries during force reinstall
  and upgrade; disabled plugins stay disabled without user intent change
- After clearing plugin roots cache, fire-and-forget preloadPluginRoots()
  so sync LSP config reads stay valid for the rest of the session
2026-03-30 13:44:54 +02:00
Miroslav Drbal 7a8e4e021e fix: preserve --plugin-dir across cache clears, safe force-reinstall, URL classification
- Store injected --plugin-dir roots in module-level variable; survive
  cache clears by re-merging on next listClaudePluginRoots() call
- Defer old plugin deletion in force-reinstall until new cache succeeds;
  transient failures no longer leave the plugin uninstalled
- Classify HTTP URLs by pathname instead of full string; query params
  no longer cause .json URLs to be misclassified as git
2026-03-30 13:44:54 +02:00