Skip to content

feat(plugin): integrate UserLevel plugin dispatch#36

Merged
Q00 merged 7 commits into
release/bootstrapfrom
codex/userlevel-plugin-integration
Jun 3, 2026
Merged

feat(plugin): integrate UserLevel plugin dispatch#36
Q00 merged 7 commits into
release/bootstrapfrom
codex/userlevel-plugin-integration

Conversation

@Q00
Copy link
Copy Markdown
Owner

@Q00 Q00 commented Jun 3, 2026

Summary

This consolidates the open UserLevel plugin PR stack plus the install quick-start fix into one branch and wires the plugin path into the live runtime.

Included upstream work:

Additional integration in this PR:

  • starts a lazy supervised UserLevel plugin registry in the runtime
  • refines ooo <plugin> ... prompt routing from discovered capabilities before journaling/dispatch
  • dispatches :user_level_plugin routes through UserLevelPluginInvocation with the guarded external command runner
  • keeps built-in ooo auto/interview/pm/run/... prompts on the existing Ouroboros MCP workflow path
  • adds tests proving prompt normalization, terminal workflow lane rendering, and live loop dispatch call the UserLevel runner

Testing

  • mix test -> 2395 tests, 0 failures

Notes

The previous stack had good resolver/adapter primitives but no live call site. This PR makes the path executable while preserving the original commits for review attribution.

shaun0927 and others added 7 commits June 4, 2026 00:03
Re-anchor the userlevel plugin dispatch document on the orchestration
SSOT (#25) and docs/product-vision.md, replacing the implementation
phasing section with the five-PR breakdown that this document drives.

Notes the existing read-only slash-command CapabilityPreflight as the
sibling layer that already handles /<plugin> input, and scopes this
document to the ooo-prefixed dispatch path that is still missing.

Records the deferred vision issues (#19, #22, #24) with their trigger
conditions so they stay open without blocking implementation work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the Ourocode.Plugin.UserLevel namespace that lets the runtime
discover installed Ouroboros UserLevel plugins and treat their commands
as first-class registry entries without reimplementing trust or storage
semantics.

What is added:

- Ourocode.Plugin.UserLevel.Capability + .Capability.Command
  Normalized identity and command surface (plugin_id, source, version,
  install scope, trust scopes, manifest digest, declared commands,
  expected artifacts, continuation hints). Identity stability via
  (plugin_id, version, manifest_digest) tuple so re-discovery without
  manifest changes returns the same struct.

- Ourocode.Plugin.UserLevel.Discovery
  Behaviour with a Discovery.run/2 helper that normalizes raw
  descriptors into Capability structs and surfaces per-descriptor
  validation errors separately so one bad command never loses a whole
  plugin.

- Ourocode.Plugin.UserLevel.Discovery.OuroborosCLI
  First discovery adapter; invokes `ouroboros plugin list --json` via a
  pluggable command runner. Tests inject a stub runner to avoid spawning
  real processes. Failure modes (exit != 0, runner unavailable,
  malformed JSON, unexpected shape) all surface as structured errors.

- Ourocode.Plugin.UserLevel.Registry
  Small Agent that caches the latest discovery snapshot with a 60 s
  TTL, explicit refresh, and identity-preserving merge. Discovery
  failure degrades the snapshot but preserves last good capabilities,
  so missing/broken ouroboros CLI never blocks boot.

- Ourocode.Plugin.UserLevel.RegistryEntry
  Projects Capability into the existing Command.Registry plugin-source
  entry shape (mirrors PluginSurfaceEntry's metadata so the existing
  CapabilityPreflight.Trust and Projection modules apply unchanged).

What is NOT changed in this PR:

- No supervision wiring — the registry is standalone and ships dead
  code until PR 4 wires it into application_services.ex alongside the
  dispatch adapter that needs it. This keeps PR 2 boot-safe.
- No new slash command — `/plugins refresh` ships with PR 4.
- No router/dispatcher changes — those land in PR 3.

Tests: 5 ExUnit files (1255 LOC total with lib code) cover capability
shape, identity, command lookup, discovery normalization,
OuroborosCLI parsing of the superpowers fixture, registry TTL +
degraded handling, identity-stable merge, and registry projection
into the existing plugin-source entry shape.

Closes #5
Closes #8
Closes #9
Closes #18
Closes #27
Closes #29

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a pure resolver from `ooo <plugin> <command> [args ...]`-shaped
input to a structured PreflightResult. The result is read-only: it
describes what dispatch would do without executing, mutating trust, or
touching the registry.

What is added:

- Ourocode.Plugin.UserLevel.PreflightResult
  Struct with kind (:unique_match | :ambiguous | :unknown |
  :not_applicable), task_input, matched plugin/command, parsed argv
  tail, trust state, remediation string, risk class, expected
  artifacts, continuation policy, candidates (ambiguous case), and a
  match_explanation (matched_by + confidence).

- Ourocode.Plugin.UserLevel.Resolver
  resolve/2 turns task_input + capabilities into a PreflightResult.
  applies_to?/2 is the cheap predicate routing layers call to decide
  whether to swap their routing decision before invoking dispatch.

Resolution rules (intentionally narrow):

  * Direct ooo/ouroboros prefix only. Free-form natural language is
    deferred until the exact path is stable.
  * Plugin id and command name/alias matching is exact (case
    insensitive at lookup time but capability fields are preserved).
  * Argument tokens are passed through verbatim — no shell parsing,
    no case folding. Shell injection input becomes argv tokens that
    Dispatcher.guarded_external_command_runner will still guard.
  * Duplicate plugin ids surface as :ambiguous with candidates, never
    silent guess.
  * Trust state: :allowed when the capability declares trust_scope,
    :missing otherwise (with a remediation suggestion that points the
    user at `ouroboros plugin trust ...`).

What is intentionally NOT in this PR:

- Router or Dispatcher route additions. Those land in PR 4 together
  with the UserLevelPluginInvocation adapter, so dispatch can be tested
  end-to-end in one place.
- TUI panel rendering. PR 4 ships PreflightPanel.
- Decision journal. PR 5.

Tests: 2 ExUnit modules (async: true) covering unique_match (canonical
+ alias + mixed-case + arg case preservation + shell injection input),
trust missing, unknown plugin/command, missing tokens, ambiguous
duplicate ids, not_applicable inputs, and applies_to? predicate.

Closes #16
Closes #23

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the UserLevel plugin layer into the runtime dispatch contract so
`ooo <plugin> <command>` input can be routed through the existing
guarded external command runner without bypassing trust, risk class,
or shell-injection protection.

What is added:

- Ourocode.Runtime.UserLevelPluginInvocation
  Implements the runtime Adapter behaviour. Reads :capabilities from
  the dispatch context, runs the Resolver, evaluates trust state and
  risk class, and either invokes via :external_command_runner or
  returns a structured :blocked result. Argv is always a list — the
  Dispatcher's guarded_external_command_runner stays the
  shell-injection authority.

  Blocked reasons surfaced:
    :trust_missing, :ambiguous_match, :unknown_plugin_or_command,
    :destructive_action_requires_approval, :not_user_level_plugin_input,
    {:external_command_failed, reason}

  Destructive risk_class commands require explicit
  context.destructive_action_approved? = true. Default fails closed.

- Ourocode.Plugin.UserLevel.PreflightView
  JSON-safe projection of a PreflightResult shaped like the existing
  Ourocode.Command.CapabilityPreflight.Projection. Lets any UI render
  UserLevel plugin preflight using the same shape as the slash command
  preflight (trust, side_effects, candidates, match_explanation).

- Ourocode.Plugin.UserLevel.Entry
  Router refinement helper. Takes a parsed TaskRequest and a capability
  snapshot; when the input targets a known UserLevel plugin, swaps the
  routing_decision to :user_level_plugin and attaches plugin_id. Keeps
  Ourocode.Runtime.Router itself transport- and registry-agnostic.

- Ourocode.Runtime.Dispatcher.RouteResolution
  Adds :user_level_plugin to @supported_routes, adds adapter_keys/1
  clauses (plugin-id-scoped + generic fallback), reuses existing
  validate_adapter_route guard to reject decisions that incorrectly
  carry adapter_route.

- Ourocode.TaskRequest
  routing_decision type extended with :user_level_plugin kind /
  execution_route and optional :plugin_id field.

What is intentionally NOT in this PR:

- Registry supervision wiring in ApplicationServices. Until the live
  TUI integration ships, callers pass capability snapshots directly via
  context. PR 5 wires the registry into the runtime supervision tree
  alongside the artifact watcher.
- /plugins refresh slash command. Ships with PR 5 once the registry is
  supervised.
- Continuation/auto-run policy and artifact detection. PR 5.

Tests: 4 ExUnit modules (async: true) — adapter happy path, trust
blocked, unknown command blocked, destructive blocked + approved,
shell-injection argv passthrough, runner contract errors, view
projection across kinds, entry refinement (rewrites only when
applicable), and dispatcher route validation + adapter_keys.

Closes #15
Closes #17 (minimal — trust-blocked structured error path)
Closes #20
Closes #21

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rnal

Closes the loop from UserLevel plugin dispatch to verified follow-up:
artifacts are discovered via the plugin's own declared globs, a
conservative continuation policy decides whether to suggest or
auto-run an `ooo run seed_path=...` step, and every decision phase
is appended to the existing journal writer for audit and replay.

What is added:

- Ourocode.Plugin.UserLevel.ArtifactWatcher
  Pure scan over a CommandCapability's expected_artifacts globs
  rooted at the dispatch cwd. Classifies seed / handoff / report /
  log / other by basename. When lstat? is enabled (default),
  attaches size, sha256 digest (capped at 1 MiB), and generated_at.
  Never hardcodes plugin-internal storage paths.

- Ourocode.Plugin.UserLevel.Continuation
  Pure policy: read_only → :none, handoff_producing without a seed
  → :none, handoff_producing with a seed → :suggest, and :auto_run
  only when the user's prompt contains an explicit opt-in phrase
  ("then run the generated handoff", "이어서 실행", etc.).
  Destructive commands never auto-run.

- Ourocode.Plugin.UserLevel.DecisionJournal
  Appends one structured event per phase
  (:user_level_preflight, :user_level_dispatch,
  :user_level_artifact, :user_level_continuation) into the existing
  Ourocode.Journal.Writer. Accepts a Path.t or a 1-arity function so
  tests can capture events without filesystem writes.

What is modified:

- Ourocode.Runtime.UserLevelPluginInvocation
  After a successful runner call, scans declared artifacts, decides
  continuation, and attaches both to the result envelope as optional
  :artifacts and :continuation keys. When :decision_journal is
  provided in the dispatch context, every phase emits a structured
  event. Behaviour without these context keys is unchanged from
  PR 4 (backward-compatible).

What is intentionally NOT in this PR:

- Registry supervision wiring in ApplicationServices. The standalone
  registry is still usable today via Ourocode.Plugin.UserLevel.Registry
  callers (e.g. CLI integration); supervision wiring is a runtime
  integration concern that should be planned alongside TUI rendering
  and tracked in a follow-up.
- TUI panel rendering. PR 4 ships the JSON-safe PreflightView; the
  actual TUI integration is downstream UI work.
- /plugins refresh slash command. Follow-up.
- Full failure recovery (#17 beyond structured trust-blocked errors)
  and durable session lifecycle (#24). Deferred per
  docs/userlevel-plugin-dispatch.md "Deferred" section.

Tests: 4 ExUnit modules (async: true). ArtifactWatcher (5 tests with
real tmp filesystem), Continuation (10 tests across risk classes +
opt-in phrases), DecisionJournal (6 tests with capture function),
UserLevelPluginInvocation post-execution integration (4 tests with
end-to-end seed detection + auto_run intent + journal emission +
blocked-still-emits).

Closes #7
Closes #10
Closes #11
Closes #26
Closes #28

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The release path documented in the README ("curl ... install.sh | bash"
then "ourocode") failed on any machine other than the maintainer's:

1. StartupArgs hardcoded the default project directory to a build-time
   developer path (/Users/jaegyu.lee/Project/ourocode). Running `ourocode`
   without --project-dir on any other machine errored with
   "project directory does not exist". Default to the current working
   directory instead, with an OUROCODE_PROJECT_DIR override.

2. The bundled `ourocode` is an Erlang escript and needs the Erlang/OTP
   runtime (escript/erl) on PATH, but install.sh never checked for or
   installed it. On a fresh machine the installer "succeeded" yet every
   invocation died with "env: escript: No such file or directory".
   Add a best-effort runtime ensure step (brew on macOS, apt/dnf on
   Linux) that fails fast with manual instructions when it cannot help,
   with an OUROCODE_SKIP_ERLANG=1 escape hatch.

Also document the Erlang/OTP requirement and the CWD default in the README.

Verified with `mix test test/ourocode/cli_test.exs` (46 tests, 0 failures)
and `bash -n install.sh`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Connect the UserLevel plugin capability stack to the live terminal/runtime path. Prompt normalization now refines ooo plugin inputs from discovered capabilities, loop bindings dispatch user_level_plugin routes through the guarded command runner, and the runtime supervises a lazy UserLevel registry so startup stays offline-safe.

This turns the previously isolated resolver/adapter modules into an executable path while keeping built-in Ouroboros workflow prompts on their existing MCP route.

Affected files:

- lib/ourocode/plugin/user_level/entry.ex

- lib/ourocode/plugin/user_level/registry.ex

- lib/ourocode/runtime/application_services.ex

- lib/ourocode/runtime/application_state.ex

- lib/ourocode/runtime/loop_binding_workflow_dispatch.ex

- lib/ourocode/runtime/loop_bindings.ex

- lib/ourocode/runtime/user_level_plugin_invocation.ex

- lib/ourocode/terminal/event_loop_prompt_input.ex

- lib/ourocode/terminal/event_loop_task_submission.ex

- test/ourocode/cli_test.exs

- test/ourocode/runtime/application_test.exs

- test/ourocode/runtime/loop_binding_workflow_dispatch_test.exs

- test/ourocode/terminal/event_loop_prompt_input_test.exs

- test/ourocode/terminal/event_loop_task_submission_test.exs
@Q00 Q00 merged commit 08cafab into release/bootstrap Jun 3, 2026
@Q00 Q00 deleted the codex/userlevel-plugin-integration branch June 3, 2026 15:12
Q00 added a commit that referenced this pull request Jun 3, 2026
Bump the package, installer, CLI version output, and README release artifact examples for v0.1.13.

This release includes the UserLevel plugin dispatch integration and installer quick-start fixes merged through PR #36.

Affected files:

- README.md

- install.sh

- lib/ourocode/cli.ex

- mix.exs

- test/ourocode/cli_test.exs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants