mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
280 lines
15 KiB
Markdown
280 lines
15 KiB
Markdown
---
|
|
issue: 96
|
|
issue_title: "Subagent permission forwarding broken for all major pi-subagent extensions"
|
|
---
|
|
|
|
# Broaden subagent env hint keys for major pi-subagent extensions
|
|
|
|
## Problem Statement
|
|
|
|
Permission forwarding relies on `SUBAGENT_ENV_HINT_KEYS` to detect whether the current process is running as a subagent.
|
|
Those three keys (`PI_IS_SUBAGENT`, `PI_SUBAGENT_SESSION_ID`, `PI_AGENT_ROUTER_SUBAGENT`) are not set by any of the three major pi-subagent extensions.
|
|
As a result, `isSubagentExecutionContext()` returns `false` in child sessions spawned by nicobailon/pi-subagents or HazAT/pi-interactive-subagents, and `resolvePermissionForwardingTargetSessionId()` returns `null` because `PI_AGENT_ROUTER_PARENT_SESSION_ID` is also never set by those extensions.
|
|
Any `ask`-state permission in such a headless child session silently denies instead of forwarding the dialog to the parent.
|
|
|
|
The tintinweb extension runs subagents fully in-process — no env vars are ever set, so detection there cannot rely on env vars at all.
|
|
That case is deferred to #29 (event bus RPC).
|
|
|
|
## Goals
|
|
|
|
- Broaden `SUBAGENT_ENV_HINT_KEYS` to include the env vars that nicobailon/pi-subagents and HazAT/pi-interactive-subagents actually set in child processes.
|
|
- Add a `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES` list covering known parent-session env vars so parent-session resolution succeeds for each extension where that information is available.
|
|
- Emit a structured debug/review log entry when parent-session resolution fails so users get an actionable message instead of a silent denial.
|
|
- Add tests covering detection and parent-session resolution for each extension's env var pattern.
|
|
- Document which extensions are now covered and which remain deferred (tintinweb in-process case → #29).
|
|
|
|
## Non-Goals
|
|
|
|
- Fixing the tintinweb in-process subagent case (no child process → no env vars; tracked in #29).
|
|
- Proposing or enforcing a shared upstream convention across extensions (tracked in #98).
|
|
- Changing the file-based forwarding protocol or polling logic.
|
|
- Modifying how `yolo-mode` short-circuits the `ask` path.
|
|
|
|
## Background
|
|
|
|
### Permission surfaces involved
|
|
|
|
This issue touches the **forwarding** path that sits above all permission surfaces: when a permission resolves to `ask` in a headless subagent context the extension must forward the dialog to the parent session rather than blocking or silently denying.
|
|
No surface-level rule evaluation changes — only the subagent detection and parent-session resolution steps.
|
|
|
|
### Relevant modules
|
|
|
|
`src/permission-forwarding.ts`
|
|
: Declares `SUBAGENT_ENV_HINT_KEYS`, `SUBAGENT_PARENT_SESSION_ENV_KEY`, and `resolvePermissionForwardingTargetSessionId()`.
|
|
These are the primary targets of this fix.
|
|
|
|
`src/subagent-context.ts`
|
|
: `isSubagentExecutionContext()` iterates `SUBAGENT_ENV_HINT_KEYS` and falls back to session-dir path comparison.
|
|
The path-based fallback works only when the session directory happens to be nested under `subagentSessionsDir`, which is path-layout-dependent and fragile for external extensions.
|
|
|
|
`src/forwarded-permissions/polling.ts`
|
|
: `waitForForwardedPermissionApproval()` calls both `isSubagentExecutionContext()` and `resolvePermissionForwardingTargetSessionId()`.
|
|
When either returns a falsy result the function logs a forwarding error and returns `{ approved: false, state: "denied" }` — the silent denial.
|
|
|
|
`tests/subagent-context.test.ts`
|
|
: Existing tests cover the three original `SUBAGENT_ENV_HINT_KEYS` and the session-dir path fallback.
|
|
New tests for the additional keys go in the same file.
|
|
|
|
### Extension env var inventory
|
|
|
|
| Extension | Child-process env vars | Parent-session env var |
|
|
| ------------------------------ | ----------------------------------------------------------------------------------------- | ----------------------------- |
|
|
| nicobailon/pi-subagents | `PI_SUBAGENT_CHILD`, `PI_SUBAGENT_RUN_ID`, `PI_SUBAGENT_CHILD_AGENT`, `PI_SUBAGENT_DEPTH` | none set (no mechanism today) |
|
|
| tintinweb/pi-subagents | none (in-process, deferred) | n/a |
|
|
| HazAT/pi-interactive-subagents | `PI_SUBAGENT_NAME`, `PI_SUBAGENT_ID`, `PI_SUBAGENT_SESSION`, `PI_SUBAGENT_ACTIVITY_FILE` | none set (no mechanism today) |
|
|
|
|
Neither extension currently sets a parent-session env var.
|
|
Parent-session resolution for these extensions will fail at the `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES` lookup.
|
|
The fix improves the error log to surface this explicitly so users can track progress against #98.
|
|
|
|
## Design Overview
|
|
|
|
### 1. Broaden env hint keys
|
|
|
|
Add the known child-indicator vars from each extension to `SUBAGENT_ENV_HINT_KEYS` in `src/permission-forwarding.ts`:
|
|
|
|
```typescript
|
|
export const SUBAGENT_ENV_HINT_KEYS = [
|
|
// pi-agent-router (original)
|
|
"PI_IS_SUBAGENT",
|
|
"PI_SUBAGENT_SESSION_ID",
|
|
"PI_AGENT_ROUTER_SUBAGENT",
|
|
// nicobailon/pi-subagents
|
|
"PI_SUBAGENT_CHILD",
|
|
"PI_SUBAGENT_RUN_ID",
|
|
"PI_SUBAGENT_CHILD_AGENT",
|
|
"PI_SUBAGENT_DEPTH",
|
|
// HazAT/pi-interactive-subagents
|
|
"PI_SUBAGENT_NAME",
|
|
"PI_SUBAGENT_ID",
|
|
"PI_SUBAGENT_SESSION",
|
|
"PI_SUBAGENT_ACTIVITY_FILE",
|
|
] as const;
|
|
```
|
|
|
|
This makes `isSubagentExecutionContext()` return `true` for child processes from both extensions without changing the function's signature or logic.
|
|
|
|
### 2. Add parent-session env var candidates
|
|
|
|
The existing `SUBAGENT_PARENT_SESSION_ENV_KEY` is a single string (`"PI_AGENT_ROUTER_PARENT_SESSION_ID"`).
|
|
Replace it with an ordered array of candidates so `resolvePermissionForwardingTargetSessionId()` can try each in turn:
|
|
|
|
```typescript
|
|
/** Ordered list of env var names to check for the parent session ID. */
|
|
export const SUBAGENT_PARENT_SESSION_ENV_CANDIDATES: readonly string[] = [
|
|
// pi-agent-router (original)
|
|
"PI_AGENT_ROUTER_PARENT_SESSION_ID",
|
|
] as const;
|
|
```
|
|
|
|
Neither nicobailon nor HazAT currently sets a parent-session env var, so only the original key appears now.
|
|
The array design lets a future step (or a #98 adoption) add more candidates without changing call sites.
|
|
|
|
`resolvePermissionForwardingTargetSessionId()` is updated to iterate the candidates:
|
|
|
|
```typescript
|
|
export function resolvePermissionForwardingTargetSessionId(options: {
|
|
hasUI: boolean;
|
|
isSubagent: boolean;
|
|
currentSessionId?: string | null;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): string | null {
|
|
if (options.hasUI) {
|
|
return normalizePermissionForwardingSessionId(options.currentSessionId);
|
|
}
|
|
if (!options.isSubagent) {
|
|
return null;
|
|
}
|
|
for (const key of SUBAGENT_PARENT_SESSION_ENV_CANDIDATES) {
|
|
const resolved = normalizePermissionForwardingSessionId(options.env?.[key]);
|
|
if (resolved) return resolved;
|
|
}
|
|
return null;
|
|
}
|
|
```
|
|
|
|
`SUBAGENT_PARENT_SESSION_ENV_KEY` is kept as a deprecated re-export alias for one release so external callers are not broken:
|
|
|
|
```typescript
|
|
/** @deprecated Use SUBAGENT_PARENT_SESSION_ENV_CANDIDATES */
|
|
export const SUBAGENT_PARENT_SESSION_ENV_KEY =
|
|
SUBAGENT_PARENT_SESSION_ENV_CANDIDATES[0];
|
|
```
|
|
|
|
### 3. Improve the failure log message
|
|
|
|
In `waitForForwardedPermissionApproval()` in `src/forwarded-permissions/polling.ts`, the existing error message names only `PI_AGENT_ROUTER_PARENT_SESSION_ID`.
|
|
Update it to list all candidates and mention the open tracking issue:
|
|
|
|
```typescript
|
|
logPermissionForwardingError(
|
|
deps.logger,
|
|
`Permission forwarding target session could not be resolved. ` +
|
|
`Checked env vars: ${SUBAGENT_PARENT_SESSION_ENV_CANDIDATES.join(", ")}. ` +
|
|
`If you are using nicobailon/pi-subagents or HazAT/pi-interactive-subagents, ` +
|
|
`parent-session forwarding is not yet supported for those extensions (see issue #98).`,
|
|
);
|
|
```
|
|
|
|
### Edge cases
|
|
|
|
- A `PI_SUBAGENT_DEPTH=0` value is a non-empty string and will correctly trigger detection; depth-0 is still a subagent context.
|
|
- `PI_SUBAGENT_ACTIVITY_FILE` is a file path string; any non-empty value marks the child as a subagent.
|
|
- The session-dir path-based fallback in `isSubagentExecutionContext()` is unchanged and remains as a secondary guard.
|
|
- Adding keys increases the surface area of "what counts as a subagent"; this is intentional and aligned with least-privilege (forward/ask rather than silently allow in a falsely-non-subagent context).
|
|
|
|
### Merge precedence impact
|
|
|
|
No config-level policy change.
|
|
The forwarding path sits above rule evaluation; this fix only changes when the extension *decides* to attempt forwarding rather than what decision it makes.
|
|
|
|
## Module-Level Changes
|
|
|
|
`src/permission-forwarding.ts`
|
|
: - Replace `SUBAGENT_ENV_HINT_KEYS` tuple with the expanded list.
|
|
: - Add `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES` array.
|
|
: - Keep `SUBAGENT_PARENT_SESSION_ENV_KEY` as a deprecated alias.
|
|
: - Update `resolvePermissionForwardingTargetSessionId()` to iterate candidates.
|
|
|
|
`src/forwarded-permissions/polling.ts`
|
|
: - Update the "could not resolve" error log message to name all candidates and reference #98.
|
|
|
|
`tests/subagent-context.test.ts`
|
|
: - Add detection tests for each new env hint key (nicobailon group and HazAT group).
|
|
: - Add a test asserting `SUBAGENT_ENV_HINT_KEYS` contains every key from both groups.
|
|
|
|
`tests/permission-forwarding.test.ts` *(new file)*
|
|
: - Test `resolvePermissionForwardingTargetSessionId()`:
|
|
|
|
- hasUI=true returns current session ID.
|
|
- isSubagent=false returns null.
|
|
- isSubagent=true, none of the candidates set → returns null.
|
|
- isSubagent=true, first candidate (`PI_AGENT_ROUTER_PARENT_SESSION_ID`) set → returns its value.
|
|
- isSubagent=true, first candidate absent but a hypothetical second set → returns second's value (future-proofing).
|
|
- Test `SUBAGENT_PARENT_SESSION_ENV_KEY` is still exported and equals the first candidate.
|
|
|
|
`docs/architecture/target-architecture.md`
|
|
: - Update the subagent-detection section to name the three extensions and their env var sets.
|
|
: - Note the tintinweb in-process case as deferred to #29.
|
|
|
|
## TDD Order
|
|
|
|
### Step 1 — tests: new env hint key detection
|
|
|
|
File: `tests/subagent-context.test.ts`
|
|
|
|
Add test cases that `isSubagentExecutionContext()` returns `true` for each newly added key: `PI_SUBAGENT_CHILD`, `PI_SUBAGENT_RUN_ID`, `PI_SUBAGENT_CHILD_AGENT`, `PI_SUBAGENT_DEPTH`, `PI_SUBAGENT_NAME`, `PI_SUBAGENT_ID`, `PI_SUBAGENT_SESSION`, `PI_SUBAGENT_ACTIVITY_FILE`.
|
|
|
|
Add a "covers all declared SUBAGENT_ENV_HINT_KEYS" guard test that reads the exported array and asserts each key has an individual test.
|
|
|
|
These tests are **red** until Step 2.
|
|
|
|
Commit: `test: cover nicobailon + HazAT subagent env hint keys (#96)`
|
|
|
|
### Step 2 — feat: broaden SUBAGENT_ENV_HINT_KEYS
|
|
|
|
File: `src/permission-forwarding.ts`
|
|
|
|
Expand `SUBAGENT_ENV_HINT_KEYS` with the eight new keys.
|
|
No other logic changes.
|
|
|
|
Step 1 tests turn **green**.
|
|
|
|
Commit: `feat: broaden SUBAGENT_ENV_HINT_KEYS for nicobailon + HazAT extensions (#96)`
|
|
|
|
### Step 3 — tests: SUBAGENT_PARENT_SESSION_ENV_CANDIDATES and updated resolver
|
|
|
|
File: `tests/permission-forwarding.test.ts` *(new)*
|
|
|
|
Cover:
|
|
|
|
- `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES` is an array containing `"PI_AGENT_ROUTER_PARENT_SESSION_ID"`.
|
|
- `SUBAGENT_PARENT_SESSION_ENV_KEY` equals `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES[0]` (deprecated alias still present).
|
|
- `resolvePermissionForwardingTargetSessionId` with hasUI=true, isSubagent=false, isSubagent=true+none set, isSubagent=true+first candidate set.
|
|
|
|
These tests are **red** until Step 4.
|
|
|
|
Commit: `test: cover SUBAGENT_PARENT_SESSION_ENV_CANDIDATES and resolver (#96)`
|
|
|
|
### Step 4 — feat: add SUBAGENT_PARENT_SESSION_ENV_CANDIDATES, iterate in resolver
|
|
|
|
File: `src/permission-forwarding.ts`
|
|
|
|
- Add `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES`.
|
|
- Keep `SUBAGENT_PARENT_SESSION_ENV_KEY` as a deprecated alias.
|
|
- Update `resolvePermissionForwardingTargetSessionId()` to iterate the candidates array.
|
|
|
|
File: `src/forwarded-permissions/polling.ts`
|
|
|
|
- Update the forwarding-failure log message to list all candidates and reference #98.
|
|
|
|
Step 3 tests turn **green**.
|
|
|
|
Commit: `feat: add SUBAGENT_PARENT_SESSION_ENV_CANDIDATES, iterate in resolver (#96)`
|
|
|
|
### Step 5 — docs: update target architecture
|
|
|
|
File: `docs/architecture/target-architecture.md`
|
|
|
|
Document the three extensions, their env vars, and the tintinweb in-process deferral.
|
|
|
|
Commit: `docs: update target-architecture subagent detection for #96`
|
|
|
|
## Risks and Mitigations
|
|
|
|
| Risk | Mitigation |
|
|
| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| New env hint keys over-match — a non-subagent process happens to have one of these vars set and is wrongly treated as headless | `PI_SUBAGENT_DEPTH=0` and similar are specific to these extensions; over-match risk is low. The consequence of a false positive is that the extension tries to forward rather than silently allow, which still prompts the user — not a silent bypass. |
|
|
| Could this silently weaken a permission? | No. Broadening detection makes more sessions *attempt* forwarding rather than silently denying. The only failure mode is a forwarding attempt that cannot resolve a parent session, which already emits a denial — not an allow. |
|
|
| `SUBAGENT_PARENT_SESSION_ENV_KEY` removal breaks external callers | Kept as a deprecated alias for at least one release. |
|
|
| Parent-session resolution still fails for nicobailon and HazAT (no parent-session env var) | The improved error message makes this explicit and points to #98. The silent-denial behavior is unchanged for this specific sub-case until #98 lands. |
|
|
| `PI_SUBAGENT_SESSION` from HazAT is the *child's* session ID, not the parent's | It is added to `SUBAGENT_ENV_HINT_KEYS` (detection only), not to `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES` (resolution). No confusion possible. |
|
|
|
|
## Open Questions
|
|
|
|
- **#98 adoption**: Once nicobailon and HazAT adopt a shared parent-session env var, add it to `SUBAGENT_PARENT_SESSION_ENV_CANDIDATES`.
|
|
Plan is intentionally array-shaped to make that a one-line change.
|
|
- **`PI_SUBAGENT_DEPTH=0`**: Depth-0 could mean "top-level orchestrator in a subagent run".
|
|
If that case should be excluded from subagent detection, a depth check could be added — deferred until there is a concrete user report.
|
|
- **tintinweb in-process**: No env var approach can fix this; deferred to #29 (event bus RPC).
|