# Package lab: one fictional notebook, four creation formats These are source-backed teaching examples for the workbook's custom OMP 18.0.7 build, not a claim that every public build with that version has the same APIs. No example bundles credentials, contacts a service, installs itself, or modifies real project files. Notebook selections are factory-local memory, not persistent session entries. ## Start with an explicit entry From this downloaded directory, with the compatible `omp` on PATH: ```sh omp --no-extensions -e ./single/field-notes.ts omp --no-extensions -e ./multi omp --no-extensions -e ./manifest ``` Run one command at a time. The single-file entry registers `field_notes` and `/field-notes`; try `/field-notes reed`. `field_notes` accepts `{"op":"discover"}`, `{"op":"inspect","value":"reed"}`, and `{"op":"query","value":"marsh"}`. An unknown inspection id rejects. The multi-file entry registers `notebook` and `/notebook`. Its `index.ts` imports `notebook.ts` as an ordinary helper, not as another extension. Machine flow: `{"op":"discover"}` → `{"op":"inspect","id":"reed"}` → `{"op":"act","id":"reed"}` → `{"op":"query"}`. `/notebook fern` uses the same state. Invalid selections leave the previous selection intact. An exact completed argument returns no completion, so Enter can submit. Only the inline SDK example needs a runtime package dependency: install the workbook-compatible `@oh-my-pi/pi-coding-agent` into the environment that runs it, then `bun ./inline/run.ts`. The root manifest marks that host package as an optional peer, not a lifecycle script or registry-published example. CLI entries use type-only SDK imports and obtain TypeBox through `pi.typebox`; they need no extra runtime library. The examples intentionally do not resolve local/private package paths. The SDK host uses in-memory auth and session storage, an explicit scratch model-config path, explicit empty discovery inputs, and never calls `session.prompt`. Its script prints registered tool/command metadata and disposes the session. `inline/factory.ts` returns an `ExtensionFactory`; `inline/session.ts` passes it in `createAgentSession({ extensions: [...] })`. `extensions` here is an array of functions, not paths. Explicit module paths belong in `additionalExtensionPaths`. Factories execute per extension binding; top-level module code may be cached by Bun. Mutable state inside a factory belongs to that binding, **not automatically to one transcript**. These simple notebook selections survive `/new` and session switches because the existing runner and closures are reused; a fresh factory binding resets them. The helpers deliberately do not reset on `session_switch` or persist selection in session entries. Do not forward already-bound extension instances to a separately constructed SDK session; prepared factories can be rebound to a fresh runtime. ## File, directory, manifest - `-e file.ts` targets one module with a default factory. It does not expand sibling entries. - `-e directory` resolves `package.json` → `omp.extensions` (legacy `pi` fallback), then `index.ts` / `index.js`. If no package entry exists, discovery scans direct `.ts`/`.js` files and immediate child entry packages; it does not walk arbitrary nested helpers. - `manifest/package.json` declares two base entries: a catalog and a `resources_discover` handler. Resource paths are module-relative absolute paths, so they remain correct from another cwd. The prompt is separate static Markdown, not a string inside the extension. - The `summary` feature is real plugin metadata and defaults off. Explicit `-e ./manifest` reads the base `extensions` list; it does **not** select plugin features. To try the optional entry without installation: `omp --no-extensions -e ./manifest -e ./manifest/entries/summary.ts`. - Native ambient extension modules come from the cwd's `.omp/extensions` and the active profile's agent `extensions` directory (default `~/.omp/agent/extensions`). Configured paths, hooks, and enabled installed plugins add other discovery inputs. Do not infer extension-module scope from skills' ancestor traversal rules. - `--no-extensions` disables ambient extension discovery; explicit `-e` entries still load. It is not an OS sandbox and is not a blanket switch for every resource family. - Disabled ids use `extension-module:`: a direct file uses its stem; `index.ts` uses its parent directory name. In this build, disabled ids filter directory-discovered entries, while an explicitly configured file is still loaded. For example, the multi package's id is `extension-module:multi`. ## Trusted paths and plugins are different concerns From this downloaded package-lab directory, `omp --trusted-extension "$PWD/single/field-notes.ts"` supplies the required absolute module-file path. The CLI rejects relative paths and directories, resolves the selected file's real path, and restricts extension module selection to those trusted file paths. It does not sandbox the factory, inspect imported helpers for safety, constrain filesystem/network privileges, authenticate dependencies, or prevent arbitrary code in the selected module. A trusted extension still executes code in the host process. Review the whole import graph. Do not run installation commands just to do this lab. In this source revision: - `omp plugin link ` symlinks into the user's plugins `node_modules` tree and records runtime state; it is not a project-only `-e` substitute. - `omp plugin install ./local-directory` routes to that same link operation. Local/npm installs warn that `--scope` is supported only for marketplace installs; adding `--scope project` does not make these commands project-scoped. - npm/git install can execute package-manager work and fetch dependencies. Nothing in this lab runs it. Package feature selectors exist in install specs (`name[summary]`, `name[*]`, `name[]`); linked plugins begin with feature defaults. - Marketplace installation has a distinct `--scope user|project` route. Runtime discovery can read both user and project plugin roots, with enabled project packages shadowing user packages of the same name. Discovery's ability to read a project root does not imply every installer writes there. ## Collision rules are not one universal rule Within one factory, registering the same tool or command name twice replaces the earlier Map entry. Across extension instances, the runner retains both tool registrations in `getAllRegisteredTools`; its effective tool lookup and command lookup use the later extension. Aggregated commands filter reserved built-in names with diagnostics. These are separate boundaries, even when the final winner is the same. Other families have their own semantics: handlers append and run in order; message-renderer lookup returns the first extension with that renderer; flags and composer shapes aggregate last-wins; reserved shortcuts are rejected. A tool-name winner does not establish a general collision policy for the entire SDK. A failed module factory becomes a loader error and later modules still bind. Inline `loadExtensionFromFactory` instead rejects its promise. Provider registrations are restored to the pre-factory queue on failure, including prior entries the factory removed. This is not an arbitrary-side-effect transaction: flag defaults, external writes, listeners, or module evaluation are not all rolled back. ## Small advanced seams - `advanced/event-bus.ts`: runnable entry, `omp --no-extensions -e ./advanced/event-bus.ts`. `field_events` exposes discover/inspect/query/act; act publishes a fictional note id. The bus is process-local coordination, not durable delivery. `emit` does not await asynchronous listeners. The handler validates unknown payloads and unsubscribes on `session_shutdown`, not on `/new` or `session_switch`; its binding-local selection therefore survives those transcript transitions. - `manifest/entries/resources.ts`: runnable resource registration; the private runner proves event dispatch and provenance, not UI prompt-menu rendering. - `advanced/provider-registration.ts`: host adapter, **not a standalone provider**. Import `createProviderExtension(name, realConfig)` into an SDK host and include its result in `extensions`. The host owns real configuration and any genuine transport. `ProviderConfig.models` replaces that provider's models; a base-URL-only registration overrides existing models; `streamSimple` registers a custom stream API. No fake transport or fake credentials are supplied. The proof only exercises queued registration/rollback, not a model request. - `advanced/file-fallback.ts`: host adapter, **not an elevated writer**. Import `createFieldNotesFallback(canonicalDestination, realBroker)` into an SDK host. It registers at factory time, refuses other sessions and destinations, delegates the exact bytes, and returns true only after the broker resolves. The host must provide the real privileged channel and canonical policy path. Fallbacks install at runner initialization, are process-wide, and are disposed on shutdown. Only permission-denied native byte writes reach the write seam; archives, SQLite, arbitrary subprocess writes and deletes do not. Deletion is a separate API and requires plain unlink, never recursive removal. No broker or elevation is fabricated by this lab. ## Source map and proof honesty Source: `packages/coding-agent/src/extensibility/extensions/{loader,runner,types,wrapper}.ts`, `src/discovery/{builtin,helpers}.ts`, `src/extensibility/plugins/{loader,manager,types}.ts`, `src/cli/plugin-cli.ts`, `src/main.ts`, `src/sdk.ts`, `src/utils/event-bus.ts`, and `src/tools/file-write-fallback.ts` (paths relative to the coding-agent package where abbreviated). The workbook's private `proof/loading/run.ts` requires macOS `sandbox-exec`: network denied, writes restricted to a fresh temporary root, real credential-directory reads denied, sanitized temporary home, dotenv loading disabled, and a 120-second wall-clock limit. It uses production discovery, loader, runner, tool adapter, event bus and SDK paths; it imports these public examples. It emits named invariants plus observed structured values and preserves `proof/loading/latest.json` outside the temporary tree before cleanup. It does not prove TUI rendering, command-composer delivery, provider requests, credential isolation under adversarial code, real privilege brokering, or installation. Authorship did not run validation; use the workbook's published execution receipt, not this text, as evidence of what passed.