mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: enable permission-aware subagents
This commit is contained in:
@@ -24,7 +24,7 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin
|
||||
- **Fails closed** — an internal gate error blocks the tool (with a `gate_error` review-log entry), and an unparseable bash command — or an indirection wrapper that hides the gated command (`bash -c`/`eval`, `sudo`, `env`, `xargs`, `find -exec`, …) — prompts (`ask`) rather than passing silently
|
||||
- **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
|
||||
- **Broadcasts UI prompt events** — `permissions:ui_prompt` fires only when the permission system is about to invoke the active user-facing permission UI
|
||||
- **Native [`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) integration** — in-process child sessions register with the permission system automatically, enabling per-agent policy enforcement and `ask`-state forwarding to the parent UI without configuration
|
||||
- **Native in-process subagent integration** — [`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) and the source-maintained `my-pi` fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) register child sessions automatically, enabling per-agent policy enforcement and `ask`-state forwarding to the parent UI without configuration
|
||||
|
||||
## Install
|
||||
|
||||
|
||||
@@ -428,14 +428,14 @@ This requires two detections:
|
||||
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| pi-agent-router (original) | `PI_IS_SUBAGENT`, `PI_SUBAGENT_SESSION_ID`, `PI_AGENT_ROUTER_SUBAGENT` | `PI_AGENT_ROUTER_PARENT_SESSION_ID` |
|
||||
| [nicobailon/pi-subagents](https://github.com/nicobailon/pi-subagents) | `PI_SUBAGENT_CHILD`, `PI_SUBAGENT_RUN_ID`, `PI_SUBAGENT_CHILD_AGENT`, `PI_SUBAGENT_DEPTH` | none set (see #98) |
|
||||
| [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) | none - runs fully in-process via `createAgentSession()` | n/a - deferred to #29 |
|
||||
| [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) | none - runs fully in-process via `createAgentSession()`; the maintained `my-pi` fork uses the registry lifecycle | n/a |
|
||||
| [HazAT/pi-interactive-subagents](https://github.com/HazAT/pi-interactive-subagents) | `PI_SUBAGENT_NAME`, `PI_SUBAGENT_ID`, `PI_SUBAGENT_SESSION`, `PI_SUBAGENT_ACTIVITY_FILE` | none set (see #98) |
|
||||
|
||||
### Detection (`isSubagentExecutionContext`)
|
||||
|
||||
`isSubagentExecutionContext()` checks three sources in priority order:
|
||||
|
||||
1. **Explicit registry** - `@gotgenes/pi-subagents` emits `subagents:child:session-created` before `bindExtensions()`; the permission system's subscriber writes the entry into `SubagentSessionRegistry` synchronously.
|
||||
1. **Explicit registry** - a compatible in-process spawner (`@gotgenes/pi-subagents` or the maintained `my-pi` Tintinweb fork) emits `subagents:child:session-created` before `bindExtensions()`; the permission system's subscriber writes the entry into `SubagentSessionRegistry` synchronously.
|
||||
The registry (keyed by **child session id**) is checked first.
|
||||
Each concurrent sibling child of the same parent receives a unique session id from `sessionManager.newSession()`, so siblings occupy distinct keys - one sibling's `disposed` event cannot evict another's entry (fixes #298).
|
||||
The registry is a process-global singleton (via `getSubagentSessionRegistry()`, backed by `globalThis` + `Symbol.for()`) because each session's `ResourceLoader` creates its own `pi.events` bus: the parent's instance registers the child over the parent bus, while the child's separate jiti instance reads the same global store to detect itself and resolve its forwarding target.
|
||||
@@ -457,8 +457,8 @@ Adding a new env var candidate when an extension adopts the convention is a one-
|
||||
|
||||
### In-process case (resolved)
|
||||
|
||||
In-process subagent extensions (e.g. `@gotgenes/pi-subagents`) call `createAgentSession()` directly - no child process is spawned and no env vars are ever set.
|
||||
`@gotgenes/pi-subagents` publishes `subagents:child:session-created` (before `bindExtensions()`) and `subagents:child:disposed` (in the run's `finally`); `src/authority/subagent-lifecycle-events.ts` subscribes and writes/removes the entry in `SubagentSessionRegistry` synchronously.
|
||||
In-process subagent extensions call `createAgentSession()` directly - no child process is spawned and no env vars are ever set.
|
||||
`@gotgenes/pi-subagents` and the maintained `my-pi` Tintinweb fork publish `subagents:child:session-created` before `bindExtensions()` and `subagents:child:disposed` after the child closes; `src/authority/subagent-lifecycle-events.ts` subscribes and writes/removes the entry in `SubagentSessionRegistry` synchronously.
|
||||
The registry is process-global (see `getSubagentSessionRegistry()` in `src/authority/subagent-registry.ts`) so the child's separate jiti instance reads the same store as the parent.
|
||||
See `src/authority/subagent-registry.ts` and [Subagent Integration](../subagent-integration.md) for details.
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ The extractor must not throw — guard your parsing and return `undefined` on an
|
||||
#### Subagent session registration
|
||||
|
||||
In-process subagent registration is event-driven.
|
||||
`@gotgenes/pi-subagents` emits `subagents:child:session-created` before `bindExtensions()` and `subagents:child:disposed` in the run's `finally`; the permission system subscribes automatically — no service call from the spawner is required.
|
||||
A compatible in-process spawner emits `subagents:child:session-created` before `bindExtensions()` and `subagents:child:disposed` after the child closes; the permission system subscribes automatically — no service call from the spawner is required. Current publishers are `@gotgenes/pi-subagents` and the source-maintained `my-pi` fork of `tintinweb/pi-subagents`.
|
||||
See [Subagent Integration](subagent-integration.md) for details.
|
||||
|
||||
### Reload Safety
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# Subagent Integration
|
||||
|
||||
## Native integration with `@gotgenes/pi-subagents`
|
||||
## Native in-process integration
|
||||
|
||||
[`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) is the only subagent extension with native permission-system integration.
|
||||
It publishes a child-execution lifecycle on `pi.events`; this package subscribes (see `src/authority/subagent-lifecycle-events.ts`) and registers every in-process child session with the `SubagentSessionRegistry` on the `subagents:child:session-created` event — emitted before `bindExtensions()` fires — and unregisters it on `subagents:child:disposed`.
|
||||
[`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) and the `my-pi` maintained fork of [`tintinweb/pi-subagents`](https://github.com/tintinweb/pi-subagents) publish the same child-execution lifecycle on `pi.events`. This package subscribes (see `src/authority/subagent-lifecycle-events.ts`) and registers every in-process child session with the `SubagentSessionRegistry` on the `subagents:child:session-created` event — emitted before `bindExtensions()` fires — and unregisters it on `subagents:child:disposed`.
|
||||
Because the event bus dispatches synchronously, the synchronous registration completes before binding proceeds.
|
||||
This inverts the former dependency direction: the core no longer looks up this package's service ([ADR-0002] / pi-subagents [#261]).
|
||||
|
||||
@@ -19,8 +18,10 @@ The integration enables:
|
||||
The parent approves or denies, and the child resumes.
|
||||
When the parent approves "for this session," it chooses a scope: **this subagent only** (the least-privilege default) records the grant on the requesting child, while **the whole session** records it on the serving parent so the parent and all its subagents resolve it without re-prompting.
|
||||
|
||||
No configuration is required - the integration is automatic when both extensions are installed.
|
||||
When `@gotgenes/pi-permission-system` is not installed, `@gotgenes/pi-subagents` emits its lifecycle events with no subscriber - a harmless no-op.
|
||||
No configuration is required - the integration is automatic when the permission system and a lifecycle-compatible subagent extension are installed.
|
||||
When the permission system is not installed, lifecycle events have no subscriber and are a harmless no-op.
|
||||
|
||||
Lifecycle registration alone is not a tool-call gate. A host that requires permission enforcement must also make the permission extension itself mandatory in every child loader. The maintained `my-pi` Tintinweb fork receives the exact bundle-owned `extensions/permission-system.ts` path from its host wrapper; Agent `extensions: false`, `isolated`, and `exclude_extensions` policy cannot remove it, and a missing or substituted entry aborts child startup. Mandatory binding does not automatically expose extension tools.
|
||||
|
||||
## Permission Forwarding
|
||||
|
||||
@@ -116,7 +117,8 @@ These compose correctly with the permission system because the two operate at di
|
||||
| Extension | Type | Permission integration | Frontmatter key |
|
||||
| ----------------------------------------------------------------------------------- | ---------- | -------------------------------- | ---------------------------------- |
|
||||
| [@gotgenes/pi-subagents](https://github.com/gotgenes/pi-subagents) | in-process | ✓ Native (registry + forwarding) | `disallowed_tools:` (CSV denylist) |
|
||||
| [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) | in-process | ✗ No registration | `disallowed_tools:` (CSV denylist) |
|
||||
| my-pi maintained [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) | in-process | ✓ Native (registry + forwarding) | `disallowed_tools:` (CSV denylist) |
|
||||
| upstream [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents) | in-process | ✗ No registration | `disallowed_tools:` (CSV denylist) |
|
||||
| [nicobailon/pi-subagents](https://github.com/nicobailon/pi-subagents) | subprocess | ✗ Missing env vars | `tools:` (CSV allowlist) |
|
||||
| [HazAT/pi-interactive-subagents](https://github.com/HazAT/pi-interactive-subagents) | subprocess | ✗ Missing env vars | `deny-tools:` (CSV denylist) |
|
||||
|
||||
@@ -124,7 +126,7 @@ Process-based subagent extensions (nicobailon, HazAT) spawn child processes but
|
||||
Without that env var, `ask` permissions in child processes are auto-denied.
|
||||
See [guides/permission-frontmatter-for-subagent-extensions.md](guides/permission-frontmatter-for-subagent-extensions.md) for the convention that subagent extension authors should follow.
|
||||
|
||||
The upstream `tintinweb/pi-subagents` (which `@gotgenes/pi-subagents` forks) does not publish the `subagents:child:session-created` lifecycle event, so it lacks deterministic child detection and `ask`-state forwarding.
|
||||
The upstream `tintinweb/pi-subagents` does not currently publish this lifecycle. The source-maintained `my-pi` snapshot adds the same event contract without importing permission-system internals; this row must not be read as compatibility for the unmodified npm/GitHub release.
|
||||
|
||||
### Interaction Rules
|
||||
|
||||
|
||||
Reference in New Issue
Block a user