-
Notifications
You must be signed in to change notification settings - Fork 406
Description
What do you want to change?
Following #984
Provide general hook point before/after a internal command for extension authors
s.t. extensions can initiate some actions before/after a command is sent by the users
Would love to hear suggestions and get some guidance from the team
Why?
Add extensibility around built‑in commands so extensions can add safety, compliance, and workflow automation without forking core code.
Some use cases:
- Pre‑processing before the data arrive the UI layer of
/shareor/export: redact PII, show diffs, or require confirmation in the UI. - Post‑processing after the event happened
/copy,/share, or/export: send audit logs to compliance systems. - Alternative share targets after
/sharein parallel: push to Google Drive, S3, or internal storage instead of Gist. - Workflow automation on
/newor/fork: run git commit, stash, or branch setup. - Clipboard transformations on
/copy: remove markdown, normalize whitespace, or extract code blocks.
Just as important, users will be able to compose and order multiple extensions for the same command.
Extension authors can ship independent handlers, while users decide which ones run and in what order.
How? (optional)
Today we already have a small set of ad‑hoc events (session_before_switch, session_before_fork, etc.).
There could be more and more internal commands added/removed during the development.
Extending this pattern requires a new event for every command and still would not allow chained transformations across multiple extensions.
Some ideas implemented in #984
Introduce a unified command pipeline API that:
- Registers handlers by command name (not by bespoke event types).
- Passes typed command data through a chain of “before” handlers.
- Allows cancellation of the built‑in command ({ cancel: true }).
- Runs “after” handlers in parallel to observe results without mutating them.
- Exposes handler metadata (id/label/transforms) for ordering, disablement, and UI introspection.
Example:
// Before phase: transform data, cancel, or redirect
pi.beforeCommand("export", { id: "my-handler", transforms: ["entries"] }, async (data) => {
return { data: { entries: transform(data.entries) } };
});
// After phase: observe results (notification only)
pi.afterCommand("export", { id: "my-observer" }, async (data) => {
await auditLog(data.result);
});Each handler declares metadata for introspection and conflict detection:
interface CommandHandlerOptions<K extends keyof CommandDataMap> {
id: string;
label?: string;
transforms?: Array<keyof CommandDataMap[K]>;
}Execution model:
- Before handlers run sequentially and receive the previous handler’s output.
- After handlers run in parallel and only observe the result.
- If any before handler cancels, the built‑in command and all after handlers are skipped.
Chained Execution for "Before" extensions
Initial data
│
▼
extension A (transforms entries)
│
▼
extension B (receives A's output, transforms further)
│
▼
extension C (can cancel or pass through)
│
▼
Built-in command (receives final transformed data)
If any before handler returns { cancel: true }, the chain stops, the built-in command is skipped, and no after handlers run.
Parallel Execution for "After" Handlers
After handlers can execute in parallel since they don't modify data:
Built-in command completes
│
├──► After extension A (audit log)
├──► After extension B (notification)
└──► After extension C (analytics)
Ordering configuration still lists after handlers for display and enable/disable, but it does not change their parallel execution. After handlers do not run when a before handler cancels the command.