Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
108aafa
fix: stale HookRef in TaskProcessor, and cmf vs. bare compatibility
tedhabeck Jul 7, 2026
22ae6b1
enh: These staged changes add support for "bare-FQN" plugins — plugin…
tedhabeck Jul 15, 2026
f819c13
doc: add implementation plan
tedhabeck Jul 16, 2026
2711c25
chore: missed commit - unit test fixtures, unit test fix
tedhabeck Jul 16, 2026
c1f2479
fix: add --no-convert eschape hatch and a change log entry.
tedhabeck Jul 22, 2026
75644e4
fix: address PR #113 "Should fix" review items
tedhabeck Jul 23, 2026
0433e3a
fix: Fixed bugs in worker.py's stdin reading: bad readline keyword, w…
tedhabeck Jul 23, 2026
2d44459
chore: remove commented code
tedhabeck Jul 23, 2026
e543727
chore: revert normalized namespace hook names
tedhabeck Jul 23, 2026
64699c8
chore: revert normalized namespace hook names
tedhabeck Jul 23, 2026
781ec16
chore: revert normalized namespace hook names
tedhabeck Jul 23, 2026
39a1f59
feature: python compatibility layer
tedhabeck Jul 29, 2026
56a33a1
Merge branch '0.1.x' into feat/python_plugin_compat_0.1.x
tedhabeck Jul 30, 2026
4825cee
chore: lint fix
tedhabeck Jul 30, 2026
5c94365
fix: pin mcp at 1.29.0 until issue 129 is addressed
tedhabeck Jul 30, 2026
58c24b1
chore: ping mcp ">=1.29.0,<2".
tedhabeck Jul 30, 2026
b6f9f7c
feat: isolated_venv extensions support
tedhabeck Jul 31, 2026
1e315e4
Merge branch '0.1.x' into feat/python_plugin_compat_0.1.x
tedhabeck Aug 5, 2026
919800f
enh: add spawn-time handshake
tedhabeck Aug 5, 2026
153916e
Merge branch '0.1.x' into feat/python_plugin_compat_0.1.x
tedhabeck Aug 6, 2026
02d21ec
Merge branch '0.1.x' into feat/python_plugin_compat_0.1.x
tedhabeck Aug 6, 2026
7c99bfc
Merge branch '0.1.x' into feat/python_plugin_compat_0.1.x
tedhabeck Aug 7, 2026
383c51c
fix: address PR review feedback (#113)
tedhabeck Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 98 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,102 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [Unreleased]

## [0.1.4] - 2026-08-07

### Added

- Auto-conversion of "bare-FQN" plugins: a plugin whose manifest `kind` is a Python class
path (e.g. `package.module.ClassName`) instead of a known kind is now converted to an
`isolated_venv` plugin at install time (the FQN is moved into `default_config.class_name`),
so it runs out-of-process in a per-plugin virtual environment ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- `--no-convert` flag on `cpex plugin install` to opt out of the conversion above and keep
the plugin's declared FQN `kind` (loaded in-process). `--no-convert` also softens an
unknown/unsupported `kind` from a hard error to a warning. Applies to pypi/test-pypi/git/local
installs ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **Credential delivery to isolated workers.** Credential-bearing hooks
(`identity_resolve`, `token_delegate`) cross the process boundary with the credential
redacted by `SecretStr` serialization. The worker now reconstructs the live credential from
the task's `credential` field before invoking the hook, so an out-of-process plugin sees the
same payload an in-process one would ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **Credential leak scrubbing on the isolated-worker boundary.** For credential-bearing hooks,
the worker scrubs the token from log records, exception text, and captured stdout/stderr
before any of it leaves the subprocess, and fails the task closed if the plugin's own result
echoes the token back. Log scrubbing installs a `logging.setLogRecordFactory` wrapper and
stream capture uses `contextlib.redirect_stdout`/`redirect_stderr` — both process-global,
both scoped to the hook call and restored afterward. Tokens shorter than
`MIN_SCRUBBABLE_TOKEN_LENGTH` (12) are still delivered to the plugin but are not used as a
substring needle, since a short needle matches unrelated text and would fail tasks closed
spuriously ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **`Extensions` support for `isolated_venv` plugins.** The worker reconstructs a frozen
`Extensions` from the task's `extensions` field and passes it to the plugin, and returns a
plugin's modified extensions on `modified_extensions`. The inbound dict is the host's
capability-filtered view — slot visibility is not re-derived in the worker — and unknown
slots are dropped rather than raising, so a host that grows a slot ahead of the worker does
not take every plugin on the channel down ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **Spawn-time `capabilities` handshake.** A worker answers a `capabilities` task with its
wire-protocol version and the feature names it actually implements (`credential`,
`extensions`, `modified_extensions`), letting the host refuse to run a plugin whose declared
needs the worker would otherwise silently drop. The host gates on the feature list, not on
the reported `cpex` version, because two builds have shipped as the same version with
different worker protocols ([#113](https://github.com/contextforge-org/cpex/pull/113)).

### Changed

- **Runtime model of existing FQN-declared Python plugins.** On 0.1.x, declaring a plugin
`kind` as a Python class path was how in-process Python plugins were declared. Because
conversion is now **on by default**, upgrading changes such plugins from in-process to the
out-of-process `isolated_venv` model unless installed with `--no-convert`. Conversion also
runs during `cpex plugin catalog update` and persists the converted form to
`plugin-manifest.yaml` / `plugins/config.yaml` ([#113](https://github.com/contextforge-org/cpex/pull/113)).

### Fixed

- **Isolated worker: error responses could carry a stale `request_id`.** The worker reused a
`main()`-local `request_id` across loop iterations, so an error raised before the next task
parsed could be tagged with the previous request's id — misdelivering the error to the wrong
caller's queue or hanging the real caller until timeout. The id is now reset per iteration
([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **`cpex plugin install pkg@<constraint>` could wrongly skip.** The repeat-install check
dropped the version constraint and, for pypi/test-pypi, compared against a possibly stale
catalog entry. An explicit version constraint now always proceeds with the install
([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **Upgrade no longer force-rebuilds every existing `isolated_venv` venv.** The venv cache now
treats a *missing* manifest version/hash signal (metadata written by an earlier CLI) as "no
signal" rather than a mismatch, so pre-existing venvs are not wiped and rebuilt on the first
run after upgrade ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **Multi-plugin packages no longer thrash the venv cache.** The persisted plugin manifest is
now keyed on the plugin's full class name instead of the shared package root, so installing
one plugin in a package no longer invalidates a sibling plugin's cache hash and triggers a
rebuild loop ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **test-pypi isolated installs resolve transitive dependencies.** Installing a plugin from
test.pypi into a fresh isolated venv now also passes `--extra-index-url https://pypi.org/simple/`,
so transitive dependencies (including `cpex` itself) resolve from real PyPI instead of failing
when they are absent from test.pypi ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **A failed package install no longer leaves a valid-looking venv cache.** Venv cache metadata
was persisted during `initialize()`, before the catalog installed the plugin's package into
the venv. Converted bare-FQN plugins have no `requirements.txt`, so that package install is
the only thing making the venv usable — if it failed, the install reported an error but the
cache read as valid, and at runtime `initialize()` skipped provisioning and the worker could
not import the plugin class. Metadata is now committed only after the package install
succeeds, so a failed install rebuilds on the next run instead of needing an explicit
reinstall ([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **`--no-convert` no longer silently no-ops on monorepo installs.** The catalog manifest is
already normalized during `catalog update`, so by dispatch there is no unconverted `kind`
left for the flag to honor. The monorepo path now warns that the flag was ignored and names
the install types that do honor it (git/pypi/test-pypi/local)
([#113](https://github.com/contextforge-org/cpex/pull/113)).
- **A dropped credential on a payload-less hook is no longer silent.** When a credential-bearing
hook arrived with a credential but no payload, the credential was discarded without a trace,
unlike every other failure on that path. It now logs a warning naming the hook (never the
credential) ([#113](https://github.com/contextforge-org/cpex/pull/113)).

### Dependencies

- `uv.lock` resolves `mcp` to 1.29.0 (was 1.27.0). No `pyproject.toml` change: the declared
constraint stays `mcp>=1.26.0,<2`, and 1.29.0 satisfies it. The 2.0 migration
(`McpError` → `MCPError`) remains tracked separately and out of the 0.1.x line
([#113](https://github.com/contextforge-org/cpex/pull/113)).

## [0.1.3] - 2026-08-06

### Changed
Expand Down Expand Up @@ -68,7 +164,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

- Initial release

[Unreleased]: https://github.com/contextforge-org/cpex/compare/0.1.3...HEAD
[Unreleased]: https://github.com/contextforge-org/cpex/compare/0.1.4...HEAD
[0.1.4]: https://github.com/contextforge-org/cpex/compare/0.1.3...0.1.4
[0.1.3]: https://github.com/contextforge-org/cpex/compare/0.1.2...0.1.3
[0.1.2]: https://github.com/contextforge-org/cpex/compare/0.1.1...0.1.2
[0.1.1]: https://github.com/contextforge-org/cpex/compare/0.1.0...0.1.1
Expand Down
18 changes: 18 additions & 0 deletions cpex/framework/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,26 @@

# Model constants.
# Specialized plugin types.
NATIVE_PLUGIN_TYPE = "native"
BUILTIN_PLUGIN_TYPE = "builtin"
WASM_PLUGIN_TYPE = "wasm"
EXTERNAL_PLUGIN_TYPE = "external"
ISOLATED_VENV_PLUGIN_TYPE = "isolated_venv"
PDP_PLUGIN_TYPE = "PDP"

# The set of manifest `kind` values the installer recognizes as-is. Any `kind`
# outside this set is a candidate for FQN auto-conversion to isolated_venv
# (see cpex.tools.catalog.classify_plugin_kind).
KNOWN_PLUGIN_KINDS = frozenset(
{
BUILTIN_PLUGIN_TYPE,
NATIVE_PLUGIN_TYPE,
WASM_PLUGIN_TYPE,
EXTERNAL_PLUGIN_TYPE,
ISOLATED_VENV_PLUGIN_TYPE,
PDP_PLUGIN_TYPE,
}
)


# MCP related constants.
Expand Down
Loading
Loading