@gotgenes/pi-permission-system 25.4.0 → 26.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +14 -12
- package/config/config.example.json +1 -2
- package/dist/public.d.ts +31 -10
- package/docs/configuration.md +23 -19
- package/docs/cross-extension-api.md +30 -4
- package/docs/migration/0745-prompt-payload-contracts.md +68 -0
- package/docs/migration/0746-review-log-fields.md +69 -0
- package/docs/troubleshooting.md +2 -1
- package/package.json +1 -1
- package/schemas/permissions.schema.json +14 -4
- package/src/access-intent/tool-kind.ts +1 -1
- package/src/authority/approval-escalator.ts +6 -4
- package/src/authority/authorizer.ts +3 -3
- package/src/authority/forwarded-request-server.ts +0 -2
- package/src/authority/forwarding-io.ts +7 -5
- package/src/authority/permission-forwarding.ts +12 -3
- package/src/authority/permission-prompter.ts +5 -4
- package/src/config-loader.ts +31 -0
- package/src/config-schema.ts +13 -4
- package/src/extension-config.ts +8 -9
- package/src/handlers/gates/bash-external-directory.ts +1 -12
- package/src/handlers/gates/bash-path.ts +1 -10
- package/src/handlers/gates/descriptor.ts +16 -5
- package/src/handlers/gates/external-directory.ts +1 -13
- package/src/handlers/gates/path.ts +1 -9
- package/src/handlers/gates/runner.ts +24 -14
- package/src/handlers/gates/skill-input-gate-pipeline.ts +2 -2
- package/src/handlers/gates/skill-input.ts +1 -10
- package/src/handlers/gates/skill-read.ts +1 -11
- package/src/handlers/gates/tool.ts +1 -11
- package/src/log-field-cap.ts +82 -0
- package/src/logging.ts +24 -3
- package/src/permission-events.ts +15 -2
- package/src/permission-prompts.ts +4 -3
- package/src/permission-session.ts +1 -1
- package/src/permission-ui-prompt.ts +4 -2
- package/src/presentation/agent-renderer.ts +215 -0
- package/src/presentation/dialog-renderer.ts +8 -64
- package/src/presentation/fact-vocabulary.ts +103 -0
- package/src/presentation/forwarded-ask-payload.ts +42 -17
- package/src/presentation/path-ask-payload.ts +8 -1
- package/src/presentation/prompt-payload.ts +165 -4
- package/src/presentation/review-log-renderer.ts +51 -0
- package/src/service.ts +11 -0
- package/src/tool-input-preview.ts +0 -1
- package/src/tool-preview-formatter.ts +18 -33
- package/src/denial-messages.ts +0 -269
- package/src/presentation/legacy-message.ts +0 -117
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [26.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v25.4.0...pi-permission-system-v26.0.0) (2026-08-16)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* **pi-permission-system:** the `message` field is removed from every `permission_request.*` review-log entry. A consumer parsing it should read the structured fields instead: `surface`, `matchedPattern`, `executedUnit`, `commandContext`, `invokedToolName`, `forwarded`, and `requesterSessionId`, alongside the existing `toolName`, `command`, `path`, `target`, and `toolInputPreview`.
|
|
14
|
+
* **pi-permission-system:** values in the permission review log are now truncated at `reviewLogFieldMaxWidth` (default 1000) with a trailing ellipsis, where a bash `command` was previously written whole. Raise `reviewLogFieldMaxWidth` to keep longer values. `ToolPreviewFormatterOptions.toolInputLogPreviewMaxLength` and `TOOL_INPUT_LOG_PREVIEW_MAX_LENGTH` are removed, superseded by that setting.
|
|
15
|
+
* **pi-permission-system:** `toolInputPreviewMaxLength` and `toolTextSummaryMaxLength` are still accepted but ignored, and a config setting either now logs a deprecation warning at session start. Remove them; use `promptMaxRows` and `promptFieldMaxWidth` to bound what a permission prompt renders.
|
|
16
|
+
* **pi-permission-system:** `PermissionUiPromptEvent.message` is removed. Read `request.value` for the decision-relevant value and `request.matchedPattern` for the rule that fired; `surface`, `value`, `agentName`, and `forwarding` are unchanged. `DirectPromptInput.message` likewise becomes `payload: PromptPayload`.
|
|
17
|
+
* **pi-permission-system:** `ForwardedPermissionRequest.message` is removed and replaced by `payload: PromptPayload`, which carries the ask's complete structured facts. A serving node on this version accepts an older child's `message`-only request and renders it from `surface`, `value`, and the requester provenance; an older *parent* rejects a newer child's request, so upgrade the parent session first.
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **pi-permission-system:** bound review-log field width with reviewLogFieldMaxWidth ([#746](https://github.com/gotgenes/pi-packages/issues/746)) ([dceb931](https://github.com/gotgenes/pi-packages/commit/dceb93197e5f0e16287d63a68fcb2cad0bec2e97))
|
|
22
|
+
* **pi-permission-system:** carry the prompt payload on the forwarded-request wire ([bf67cd6](https://github.com/gotgenes/pi-packages/commit/bf67cd6673d1e4bf57c339873d8642295bd643c0)), closes [#745](https://github.com/gotgenes/pi-packages/issues/745)
|
|
23
|
+
* **pi-permission-system:** ignore the deprecated tool-preview caps and notice their use ([47d7610](https://github.com/gotgenes/pi-packages/commit/47d7610d1217ebd6c0a968999dbfff4ad426af88)), closes [#745](https://github.com/gotgenes/pi-packages/issues/745)
|
|
24
|
+
* **pi-permission-system:** narrow the ui_prompt broadcast to the request facts ([fcdb174](https://github.com/gotgenes/pi-packages/commit/fcdb17494f73d7a506fe1ab967cf2db8f815b50a)), closes [#745](https://github.com/gotgenes/pi-packages/issues/745)
|
|
25
|
+
* **pi-permission-system:** render a forwarded ask from the child's own payload ([8587269](https://github.com/gotgenes/pi-packages/commit/8587269a066e87007b8e8dea4b85649d0240b697)), closes [#745](https://github.com/gotgenes/pi-packages/issues/745)
|
|
26
|
+
* **pi-permission-system:** render the review log from the prompt payload ([#746](https://github.com/gotgenes/pi-packages/issues/746)) ([b373876](https://github.com/gotgenes/pi-packages/commit/b3738761e572b91a3388b0681d2a34862b11859d))
|
|
27
|
+
* **pi-permission-system:** replace the forwarded-request message with the structured payload ([1af41a9](https://github.com/gotgenes/pi-packages/commit/1af41a9a18cf5850da3bc787c9e0ed0db493921a)), closes [#745](https://github.com/gotgenes/pi-packages/issues/745)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* **pi-permission-system:** stop echoing tool input in agent-facing denial text ([#746](https://github.com/gotgenes/pi-packages/issues/746)) ([525b7e4](https://github.com/gotgenes/pi-packages/commit/525b7e4b19cfa997029a178b413a2838b3834021))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
|
|
37
|
+
* **pi-permission-system:** document the payload contracts and mark Phase 13 Step 3 complete ([a2381ac](https://github.com/gotgenes/pi-packages/commit/a2381ac700e08c80c415b31f732a481367226264)), closes [#745](https://github.com/gotgenes/pi-packages/issues/745)
|
|
38
|
+
* **pi-permission-system:** record the agent and review-log renderers ([#746](https://github.com/gotgenes/pi-packages/issues/746)) ([af88cc1](https://github.com/gotgenes/pi-packages/commit/af88cc11e31e98c0fa43d3629c64f31d6b73de05))
|
|
39
|
+
* **pi-permission-system:** retire references to the dissolved denial module ([#746](https://github.com/gotgenes/pi-packages/issues/746)) ([53647b2](https://github.com/gotgenes/pi-packages/commit/53647b2b0930737940714378d8421cd2ce76e66f))
|
|
40
|
+
|
|
8
41
|
## [25.4.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v25.3.0...pi-permission-system-v25.4.0) (2026-08-15)
|
|
9
42
|
|
|
10
43
|
|
package/README.md
CHANGED
|
@@ -139,18 +139,20 @@ If you relied on the old permissive behavior for bash, set an explicit permissiv
|
|
|
139
139
|
|
|
140
140
|
## Documentation
|
|
141
141
|
|
|
142
|
-
| Document | Contents
|
|
143
|
-
| ------------------------------------------------------------------------------------------------------------------------------ |
|
|
144
|
-
| [docs/configuration.md](docs/configuration.md) | Full policy reference, runtime knobs, per-agent overrides, recipes
|
|
145
|
-
| [docs/session-approvals.md](docs/session-approvals.md) | Session-scoped rules, pattern suggestions, bash arity table
|
|
146
|
-
| [docs/cross-extension-api.md](docs/cross-extension-api.md) | Cross-extension service accessor, event bus integration, prompt and decision broadcasts
|
|
147
|
-
| [docs/subagent-integration.md](docs/subagent-integration.md) | Permission forwarding, coexistence with subagent extensions
|
|
148
|
-
| [docs/guides/permission-frontmatter-for-subagent-extensions.md](docs/guides/permission-frontmatter-for-subagent-extensions.md) | Convention guide for subagent extension authors
|
|
149
|
-
| [docs/opencode-compatibility.md](docs/opencode-compatibility.md) | OpenCode compatibility — shared concepts, divergences, porting guide
|
|
150
|
-
| [docs/troubleshooting.md](docs/troubleshooting.md) | Common issues, diagnostic logging, threat model
|
|
151
|
-
| [docs/migration/legacy-to-flat.md](docs/migration/legacy-to-flat.md) | Migration from pre-v2 config layout
|
|
152
|
-
| [docs/migration/strict-config-validation.md](docs/migration/strict-config-validation.md) | Strict config validation (breaking) — rejected configs, and the cross-scope fail-closed clamp
|
|
153
|
-
| [docs/migration/0644-project-trust-gating.md](docs/migration/0644-project-trust-gating.md) | Project-trust gating (breaking) — project config loads only after project trust
|
|
142
|
+
| Document | Contents |
|
|
143
|
+
| ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| [docs/configuration.md](docs/configuration.md) | Full policy reference, runtime knobs, per-agent overrides, recipes |
|
|
145
|
+
| [docs/session-approvals.md](docs/session-approvals.md) | Session-scoped rules, pattern suggestions, bash arity table |
|
|
146
|
+
| [docs/cross-extension-api.md](docs/cross-extension-api.md) | Cross-extension service accessor, event bus integration, prompt and decision broadcasts |
|
|
147
|
+
| [docs/subagent-integration.md](docs/subagent-integration.md) | Permission forwarding, coexistence with subagent extensions |
|
|
148
|
+
| [docs/guides/permission-frontmatter-for-subagent-extensions.md](docs/guides/permission-frontmatter-for-subagent-extensions.md) | Convention guide for subagent extension authors |
|
|
149
|
+
| [docs/opencode-compatibility.md](docs/opencode-compatibility.md) | OpenCode compatibility — shared concepts, divergences, porting guide |
|
|
150
|
+
| [docs/troubleshooting.md](docs/troubleshooting.md) | Common issues, diagnostic logging, threat model |
|
|
151
|
+
| [docs/migration/legacy-to-flat.md](docs/migration/legacy-to-flat.md) | Migration from pre-v2 config layout |
|
|
152
|
+
| [docs/migration/strict-config-validation.md](docs/migration/strict-config-validation.md) | Strict config validation (breaking) — rejected configs, and the cross-scope fail-closed clamp |
|
|
153
|
+
| [docs/migration/0644-project-trust-gating.md](docs/migration/0644-project-trust-gating.md) | Project-trust gating (breaking) — project config loads only after project trust |
|
|
154
|
+
| [docs/migration/0745-prompt-payload-contracts.md](docs/migration/0745-prompt-payload-contracts.md) | Prompt payload contracts (breaking) — the forwarded wire, the `ui_prompt` broadcast, and the deprecated preview caps |
|
|
155
|
+
| [docs/migration/0746-review-log-fields.md](docs/migration/0746-review-log-fields.md) | Review-log fields (breaking) — `message` replaced by request facts, and the `reviewLogFieldMaxWidth` bound |
|
|
154
156
|
|
|
155
157
|
## Development
|
|
156
158
|
|
package/dist/public.d.ts
CHANGED
|
@@ -68,10 +68,10 @@ interface PromptPayload {
|
|
|
68
68
|
*
|
|
69
69
|
* Present because the ask shapes are not separable by surface alone: a tool
|
|
70
70
|
* external-directory ask and a bash one share the `external_directory` surface,
|
|
71
|
-
* and the `path` gate and the per-tool gate differ only in wording. It
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
71
|
+
* and the `path` gate and the per-tool gate differ only in wording. It gives
|
|
72
|
+
* every renderer an exhaustive switch rather than a set of string comparisons a
|
|
73
|
+
* new variant sails past — which is what let the parallel denial-context union
|
|
74
|
+
* ADR 0011 §7 described dissolve into this one (#746).
|
|
75
75
|
*/
|
|
76
76
|
type PromptPayloadKind = "bash" | "mcp" | "tool" | "path" | "external_directory" | "bash_external_directory" | "skill" | "skill_read" | "forwarded";
|
|
77
77
|
/**
|
|
@@ -148,6 +148,16 @@ interface PromptAnnotation {
|
|
|
148
148
|
readonly text: string;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Permission event channel — public contract.
|
|
153
|
+
*
|
|
154
|
+
* Exports channel name constants, TypeScript types for all emitted events,
|
|
155
|
+
* and thin emit helpers.
|
|
156
|
+
*
|
|
157
|
+
* Stability guarantee: fields may be added, but existing fields will not be
|
|
158
|
+
* removed or renamed without a semver-major version bump.
|
|
159
|
+
*/
|
|
160
|
+
|
|
151
161
|
/** Emitted at `session_start`, after the service is published. */
|
|
152
162
|
declare const PERMISSIONS_READY_CHANNEL = "permissions:ready";
|
|
153
163
|
/** Emitted when a permission request is committed to the active UI prompt path. */
|
|
@@ -198,8 +208,19 @@ interface PermissionUiPromptEvent {
|
|
|
198
208
|
value: string | null;
|
|
199
209
|
/** Agent name (when known). */
|
|
200
210
|
agentName: string | null;
|
|
201
|
-
/**
|
|
202
|
-
|
|
211
|
+
/**
|
|
212
|
+
* The ask's invariant core (ADR 0011 §3), verbatim from the prompt payload.
|
|
213
|
+
*
|
|
214
|
+
* Nested rather than flattened so the event and the payload share one shape:
|
|
215
|
+
* a fact added to `PromptRequestFacts` reaches the bus without a second
|
|
216
|
+
* hand-maintained declaration. Carries no evidence and no annotations — the
|
|
217
|
+
* bus is the narrowest renderer (ADR 0011 §6), observable by any loaded
|
|
218
|
+
* extension without the operator having named it.
|
|
219
|
+
*
|
|
220
|
+
* `request.surface` is the *gate* surface the rule fired on; the top-level
|
|
221
|
+
* `surface` is the display projection. Both are here on purpose.
|
|
222
|
+
*/
|
|
223
|
+
request: PromptRequestFacts;
|
|
203
224
|
/** Forwarding context, or null for a direct prompt. */
|
|
204
225
|
forwarding: ForwardedPromptContext | null;
|
|
205
226
|
}
|
|
@@ -281,13 +302,13 @@ interface PromptPermissionDetails {
|
|
|
281
302
|
requestId: string;
|
|
282
303
|
source: PermissionReviewSource;
|
|
283
304
|
agentName: string | null;
|
|
284
|
-
message: string;
|
|
285
305
|
/**
|
|
286
306
|
* The complete structured description of this ask (ADR 0011 §2).
|
|
287
307
|
*
|
|
288
308
|
* Required: every ask carries one, and the type is what guarantees it rather
|
|
289
|
-
* than a convention each gate has to remember.
|
|
290
|
-
*
|
|
309
|
+
* than a convention each gate has to remember. Every consumer — the dialog,
|
|
310
|
+
* the wire, the broadcast, the review log, the agent-facing denial text — is
|
|
311
|
+
* a render over it, so no two of them can disagree.
|
|
291
312
|
*/
|
|
292
313
|
payload: PromptPayload;
|
|
293
314
|
toolCallId?: string;
|
|
@@ -529,4 +550,4 @@ declare function getPermissionsService(): PermissionsService | undefined;
|
|
|
529
550
|
declare function unpublishPermissionsService(service: PermissionsService): void;
|
|
530
551
|
|
|
531
552
|
export { PERMISSIONS_DECISION_CHANNEL, PERMISSIONS_READY_CHANNEL, PERMISSIONS_UI_PROMPT_CHANNEL, getPermissionsService, publishPermissionsService, unpublishPermissionsService };
|
|
532
|
-
export type { Authorizer, AuthorizerLog, AuthorizerVerdict, ForwardedPromptContext, PermissionCheckResult, PermissionDecisionEvent, PermissionQuery, PermissionState, PermissionUiPromptEvent, PermissionUiPromptSource, PermissionsReadyEvent, PermissionsService, PromptPermissionDetails, ToolInputFormatter };
|
|
553
|
+
export type { Authorizer, AuthorizerLog, AuthorizerVerdict, ForwardedPromptContext, PermissionCheckResult, PermissionDecisionEvent, PermissionQuery, PermissionState, PermissionUiPromptEvent, PermissionUiPromptSource, PermissionsReadyEvent, PermissionsService, PromptAnnotation, PromptEvidence, PromptPayload, PromptPayloadKind, PromptPermissionDetails, PromptRequestFacts, PromptRequester, ToolInputFormatter };
|
package/docs/configuration.md
CHANGED
|
@@ -58,8 +58,6 @@ This clamp is deny-preserving and, like `yoloMode`, applied at composition; when
|
|
|
58
58
|
"yoloMode": false,
|
|
59
59
|
"doublePressToConfirm": true,
|
|
60
60
|
"forwardingTimeoutMs": 600000,
|
|
61
|
-
"toolInputPreviewMaxLength": 400,
|
|
62
|
-
"toolTextSummaryMaxLength": 120,
|
|
63
61
|
"piInfrastructureReadPaths": [],
|
|
64
62
|
|
|
65
63
|
// Non-bash tools that carry shell semantics
|
|
@@ -99,19 +97,20 @@ This clamp is deny-preserving and, like `yoloMode`, applied at composition; when
|
|
|
99
97
|
|
|
100
98
|
## Runtime Knobs
|
|
101
99
|
|
|
102
|
-
| Key | Default | Description
|
|
103
|
-
| --------------------------- | -------- |
|
|
104
|
-
| `debugLog` | `false` | Enables verbose diagnostic logging to `logs/pi-permission-system-debug.jsonl`
|
|
105
|
-
| `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl`. Records bash command strings
|
|
106
|
-
| `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled
|
|
107
|
-
| `doublePressToConfirm` | `true` | Requires a confirming second press of a decision hotkey in the inline TUI dialog (see below). TUI sessions only; set to `false` for single-press.
|
|
108
|
-
| `forwardingTimeoutMs` | `600000` | How long a subagent waits for the parent session to answer a forwarded permission request, in milliseconds. A child whose in-process parent is not draining its inbox gives up in ~2 s regardless.
|
|
109
|
-
| `promptMaxRows` | `24` | Max rows a permission prompt renders before eliding its evidence. The request's own facts are never elided by this budget; `Ctrl+O` expands the prompt to the complete request.
|
|
110
|
-
| `promptFieldMaxWidth` | `400` | Max characters of any one field shown in a permission prompt. This is what bounds a single long field (a here-string command, say) that would otherwise fill the prompt through wrapping.
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
100
|
+
| Key | Default | Description |
|
|
101
|
+
| --------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `debugLog` | `false` | Enables verbose diagnostic logging to `logs/pi-permission-system-debug.jsonl` |
|
|
103
|
+
| `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl`. Records bash command strings unredacted — see [Log file sensitivity](#log-file-sensitivity) |
|
|
104
|
+
| `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
|
|
105
|
+
| `doublePressToConfirm` | `true` | Requires a confirming second press of a decision hotkey in the inline TUI dialog (see below). TUI sessions only; set to `false` for single-press. |
|
|
106
|
+
| `forwardingTimeoutMs` | `600000` | How long a subagent waits for the parent session to answer a forwarded permission request, in milliseconds. A child whose in-process parent is not draining its inbox gives up in ~2 s regardless. |
|
|
107
|
+
| `promptMaxRows` | `24` | Max rows a permission prompt renders before eliding its evidence. The request's own facts are never elided by this budget; `Ctrl+O` expands the prompt to the complete request. |
|
|
108
|
+
| `promptFieldMaxWidth` | `400` | Max characters of any one field shown in a permission prompt. This is what bounds a single long field (a here-string command, say) that would otherwise fill the prompt through wrapping. |
|
|
109
|
+
| `reviewLogFieldMaxWidth` | `1000` | Max characters of any one value written to the review log. A longer value is stored shortened, marked with an ellipsis. Raise it to keep longer values; this is a length bound, not redaction. |
|
|
110
|
+
| `toolInputPreviewMaxLength` | — | **Deprecated and ignored.** Superseded by `promptMaxRows` / `promptFieldMaxWidth`. Still accepted so an existing config is not rejected, but the value no longer applies; setting it logs a warning. |
|
|
111
|
+
| `toolTextSummaryMaxLength` | — | **Deprecated and ignored.** Superseded by `promptMaxRows` / `promptFieldMaxWidth`. Still accepted so an existing config is not rejected, but the value no longer applies; setting it logs a warning. |
|
|
112
|
+
| `piInfrastructureReadPaths` | `[]` | Extra directories to auto-allow for reads, bypassing the `external_directory` gate. Supports `~`/`$HOME`/`${HOME}` expansion and wildcard patterns (`*`, `?`). |
|
|
113
|
+
| `authorizerChain` | `[]` | Ordered names of registered live-authority chain links to consult before the terminal authorizer (see [Authorizer chain](#authorizer-chain--case-by-case-decision-links)). |
|
|
115
114
|
|
|
116
115
|
Both logs write to `~/.pi/agent/extensions/pi-permission-system/logs/`.
|
|
117
116
|
No debug output is printed to the terminal.
|
|
@@ -962,16 +961,16 @@ Additional behaviors:
|
|
|
962
961
|
- The narrowed prompt is recomputed and returned on every turn but is byte-stable for a stable policy/agent, so the provider's prompt cache (tools + system prefix) is preserved rather than rewritten each turn
|
|
963
962
|
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled by exact registered name
|
|
964
963
|
- Generic extension-tool approval prompts include a bounded input preview; built-in file tools use concise human-readable summaries
|
|
965
|
-
- Permission review logs include
|
|
964
|
+
- Permission review logs include `toolInputPreview` values for non-bash/non-MCP tool calls, with sensitive-keyed values masked and every value bounded by `reviewLogFieldMaxWidth` (see [Log file sensitivity](#log-file-sensitivity))
|
|
966
965
|
|
|
967
966
|
---
|
|
968
967
|
|
|
969
968
|
## Log file sensitivity
|
|
970
969
|
|
|
971
|
-
The review log is enabled by default and records what the agent actually did, which means it records payload as well as decisions: the
|
|
970
|
+
The review log is enabled by default and records what the agent actually did, which means it records payload as well as decisions: the bash command string for every bash decision, and a JSON preview of the tool input for other tools.
|
|
972
971
|
The debug log carries the same payload when `debugLog` is on.
|
|
973
972
|
|
|
974
|
-
|
|
973
|
+
Three protections apply.
|
|
975
974
|
|
|
976
975
|
Both logs are created **owner-only** (`0600`, in a `0700` directory), and a log created by an earlier version is tightened on the next write.
|
|
977
976
|
The permission-forwarding request and response files are written the same way.
|
|
@@ -984,9 +983,14 @@ The boundary is worth stating exactly, because it is easy to over-read:
|
|
|
984
983
|
|
|
985
984
|
> A value bound to a sensitive key name is masked; a secret embedded in a bash command string is not.
|
|
986
985
|
|
|
987
|
-
A command string has no keys, so `deploy --token abc123` is logged
|
|
986
|
+
A command string has no keys, so `deploy --token abc123` is logged unredacted.
|
|
988
987
|
The extension deliberately does not try to guess which parts of a command look secret-shaped — see [ADR 0010] for the measured reasoning.
|
|
989
988
|
|
|
989
|
+
Every value the **review** log writes is narrowed to `reviewLogFieldMaxWidth` (1000 characters by default) and marked with an ellipsis, so a single pathological command cannot put tens of kilobytes in one entry.
|
|
990
|
+
This is a length bound, not redaction: it never inspects a value to decide what to hide, and it applies to every field alike.
|
|
991
|
+
The two compose — a sensitive-keyed value is masked whole however long it was.
|
|
992
|
+
The debug log is left unbounded, since it is opt-in and exists to be read in full.
|
|
993
|
+
|
|
990
994
|
Practical guidance:
|
|
991
995
|
|
|
992
996
|
- Treat both log files as sensitive when sharing them: scrub before pasting into an issue or a chat.
|
|
@@ -320,17 +320,21 @@ Forwarded prompts that do reach the human are not degraded: the parent emits the
|
|
|
320
320
|
The payload is lean by design — `surface`/`value` are the normalized display projection a notification consumer reads, not a mirror of the internal review log.
|
|
321
321
|
Read defensively rather than version-gating: broadcast payloads carry no `protocolVersion`.
|
|
322
322
|
|
|
323
|
+
The event carries no assembled sentence.
|
|
324
|
+
It carries `request`, the permission ask's invariant core, verbatim from the prompt payload — no evidence and no annotations.
|
|
325
|
+
The bus is the narrowest renderer: any loaded extension can observe it without the operator having named that extension, whereas every other route to an ask's evidence requires that consent (a registered tool-input formatter, or an `Authorizer` link the operator lists in `authorizerChain`).
|
|
326
|
+
|
|
323
327
|
```typescript
|
|
324
328
|
import type { PermissionUiPromptEvent } from "@gotgenes/pi-permission-system";
|
|
325
329
|
|
|
326
330
|
pi.events.on("permissions:ui_prompt", (raw) => {
|
|
327
331
|
const event = raw as PermissionUiPromptEvent;
|
|
328
332
|
// Defensive read: tolerate any shape skew between sibling extensions.
|
|
329
|
-
if (typeof event.value !== "string"
|
|
333
|
+
if (typeof event.value !== "string") {
|
|
330
334
|
return;
|
|
331
335
|
}
|
|
332
|
-
notify(event.surface, event.value, event.
|
|
333
|
-
// e.g. "bash" "git push" "
|
|
336
|
+
notify(event.surface, event.value, event.request.matchedPattern);
|
|
337
|
+
// e.g. "bash" "git push" "git *"
|
|
334
338
|
});
|
|
335
339
|
```
|
|
336
340
|
|
|
@@ -343,11 +347,33 @@ pi.events.on("permissions:ui_prompt", (raw) => {
|
|
|
343
347
|
| `surface` | `string \| null` | Normalized display surface (e.g. `"bash"`, `"skill"`), when known |
|
|
344
348
|
| `value` | `string \| null` | Normalized display value (command, path, skill name, etc.), when known |
|
|
345
349
|
| `agentName` | `string \| null` | Active/requesting agent name, when known |
|
|
346
|
-
| `
|
|
350
|
+
| `request` | `PromptRequestFacts` | The ask's invariant core — no evidence, no annotations |
|
|
347
351
|
| `forwarding` | `ForwardedPromptContext \| null` | Forwarding context, or `null` for a direct prompt |
|
|
348
352
|
|
|
349
353
|
Forwarding is orthogonal to origin: a forwarded subagent prompt keeps its original `source` and is identified by a non-null `forwarding` field, not by a dedicated source value.
|
|
350
354
|
|
|
355
|
+
#### `PromptRequestFacts`
|
|
356
|
+
|
|
357
|
+
The facts every render of the ask shows, that no renderer's budget may elide.
|
|
358
|
+
Nested rather than flattened so the event and the prompt payload share one shape: a fact added here reaches the bus without a second hand-maintained declaration.
|
|
359
|
+
|
|
360
|
+
| Field | Type | Description |
|
|
361
|
+
| ----------------- | ---------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
362
|
+
| `requester` | `PromptRequester` | Who is asking, and whether the ask arrived from a subagent |
|
|
363
|
+
| `surface` | `string` | The **gate** surface the rule fired on — `"external_directory"`, `"path"`, `"bash"`, a tool name |
|
|
364
|
+
| `toolName` | `string \| null` | The gated tool name; `null` when the ask is not tool-shaped |
|
|
365
|
+
| `invokedToolName` | `string \| null` | The invoked name when a shell alias re-exposes bash under another name |
|
|
366
|
+
| `value` | `string` | The decision-relevant value: the command, path, MCP target, or skill name |
|
|
367
|
+
| `matchedPattern` | `string \| null` | The matched rule, including a sentinel such as `<indirection-bash-wrapper>` |
|
|
368
|
+
| `commandContext` | `BashCommandContext \| null` | Where the offending bash unit runs, when it came from a substitution or subshell |
|
|
369
|
+
| `executedUnit` | `string \| null` | For bash, the unit that will actually run, including inside an unstrippable wrapper |
|
|
370
|
+
|
|
371
|
+
`PromptRequester` carries `agentName` (`string | null`), `forwarded` (`boolean`), and `sessionId` (`string | null`, the requesting session for a forwarded ask).
|
|
372
|
+
|
|
373
|
+
The top-level `surface` and `request.surface` are two different facts and both belong on the event.
|
|
374
|
+
The top-level one is the **display** projection — the child's tool name, what a notification shows.
|
|
375
|
+
`request.surface` is the **gate** surface the rule fired on: a `read` of a path outside the working directory displays as `"read"` and gates on `"external_directory"`.
|
|
376
|
+
|
|
351
377
|
#### `ForwardedPromptContext`
|
|
352
378
|
|
|
353
379
|
Present only when the prompt was forwarded from a non-UI subagent.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Migration guide: the prompt payload replaces the assembled message
|
|
2
|
+
|
|
3
|
+
Starting with the release that closes #745, a permission ask crosses two boundaries as **structured facts** rather than as a pre-rendered sentence: the forwarded-permission-request file a subagent writes for its parent, and the `permissions:ui_prompt` broadcast.
|
|
4
|
+
A third change deprecates the two tool-preview cap config fields.
|
|
5
|
+
|
|
6
|
+
This is a **breaking change** on three surfaces.
|
|
7
|
+
Each is independent — you may be affected by one and not the others.
|
|
8
|
+
|
|
9
|
+
| Surface | Break | Who is affected |
|
|
10
|
+
| -------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------- |
|
|
11
|
+
| `ForwardedPermissionRequest` (on-disk wire) | `message` removed; `payload` added | Anyone running **out-of-process** subagents across mixed versions |
|
|
12
|
+
| `PermissionUiPromptEvent` (`permissions:ui_prompt`) | `message` removed; `request` added | Any extension reading `event.message` |
|
|
13
|
+
| `toolInputPreviewMaxLength` / `toolTextSummaryMaxLength` | Accepted but ignored | Anyone who set either in `config.json` |
|
|
14
|
+
|
|
15
|
+
## Upgrade the parent session first
|
|
16
|
+
|
|
17
|
+
The forwarded-request wire is the only surface with an ordering constraint, and it only exists for an **out-of-process** subagent (one whose parent session is resolved through `PI_SUBAGENT_PARENT_SESSION` or a sibling env var).
|
|
18
|
+
An in-process child shares its parent's loaded extension, so the two can never skew.
|
|
19
|
+
|
|
20
|
+
A serving node on this version accepts an older child's `message`-only request: the field is no longer required, and the ask is rendered from the `surface`, `value`, and requester provenance the request does carry.
|
|
21
|
+
The reverse does not hold.
|
|
22
|
+
An **older** parent still demands `message` and rejects a newer child's request outright, deleting the file; the child then waits out its forwarding timeout (ten minutes by default) and reports the block as `confirmationUnavailable` rather than as a user denial.
|
|
23
|
+
|
|
24
|
+
So: upgrade the session that serves prompts before the sessions that forward to it.
|
|
25
|
+
Carrying both fields indefinitely was declined deliberately — it would keep the child's un-budgeted prose alive on the wire, which is the defect this change exists to remove.
|
|
26
|
+
|
|
27
|
+
## What changed on the wire
|
|
28
|
+
|
|
29
|
+
The child used to assemble a sentence under **its** configuration and write it into the request file; the serving node carried that string forward as a single evidence entry, so the parent's own render budget never applied to it.
|
|
30
|
+
A forwarded ask therefore could not be made consistent with a local one.
|
|
31
|
+
|
|
32
|
+
Now the child writes its complete `PromptPayload`, and the serving node renders the child's own facts under the **parent's** budget.
|
|
33
|
+
A forwarded bash ask reads `command : …` exactly as a local one does, because the serving node holds the child's real payload kind.
|
|
34
|
+
|
|
35
|
+
The request files are still mode-restricted and still not redacted — the parent reads them to render the ask.
|
|
36
|
+
The payload's evidence is the same disclosure class the `message` string already was, so this is not a widening.
|
|
37
|
+
|
|
38
|
+
## What changed on the broadcast
|
|
39
|
+
|
|
40
|
+
`permissions:ui_prompt` drops `message` and gains `request`, the ask's invariant core, verbatim from the prompt payload.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Before
|
|
44
|
+
notify(event.surface, event.value, event.message);
|
|
45
|
+
|
|
46
|
+
// After
|
|
47
|
+
notify(event.surface, event.value, event.request.matchedPattern);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`request.value` is the decision-relevant value (the command, path, MCP target, or skill name) and `request.matchedPattern` is the rule that fired, including a sentinel such as `<indirection-bash-wrapper>`.
|
|
51
|
+
See the [`PromptRequestFacts` table](../cross-extension-api.md#promptrequestfacts) for every field.
|
|
52
|
+
|
|
53
|
+
`surface`, `value`, `agentName`, `source`, `requestId`, and `forwarding` are unchanged — what narrows here is evidence, never correlation.
|
|
54
|
+
A forwarded ask's broadcast still carries its full `forwarding.requesterAgentName` / `forwarding.requesterSessionId` provenance.
|
|
55
|
+
|
|
56
|
+
For a `write`, an `edit`, or an MCP call this is a **net narrowing** of what the bus discloses: an incidental tool-input preview of up to 200 characters used to ride `message`, and now nothing from the payload's evidence reaches the bus at all.
|
|
57
|
+
That is deliberate — the bus is the narrowest renderer, observable by any loaded extension without the operator having named it, whereas every other route to an ask's evidence requires that consent.
|
|
58
|
+
|
|
59
|
+
## The deprecated tool-preview caps
|
|
60
|
+
|
|
61
|
+
`toolInputPreviewMaxLength` and `toolTextSummaryMaxLength` are still accepted by the config schema but **no longer take effect**.
|
|
62
|
+
Setting either logs a deprecation notice through the ordinary config-issue channel at session start.
|
|
63
|
+
|
|
64
|
+
They bounded one preview *inside* a prompt, never the prompt itself — which is why they never bounded it.
|
|
65
|
+
Use `promptMaxRows` (default `24`) and `promptFieldMaxWidth` (default `400`) instead: those bound what a permission prompt renders, and `Ctrl+O` still expands the prompt to the complete request.
|
|
66
|
+
|
|
67
|
+
Remove both fields from your `config.json`.
|
|
68
|
+
They remain valid so that an existing config is not rejected fail-closed — which would empty that scope's whole policy — but they will be removed in a later major.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Review-log fields and width bound (breaking)
|
|
2
|
+
|
|
3
|
+
Two changes to the permission review log (`logs/pi-permission-system-permission-review.jsonl`) take effect on upgrade without a config edit.
|
|
4
|
+
Both come from [ADR 0011] §6, which makes the log a renderer over the prompt payload with its own configured limits, rather than a place the assembled prompt sentence happened to land.
|
|
5
|
+
|
|
6
|
+
Nothing about **redaction** changes.
|
|
7
|
+
Key-name masking applies exactly as before, and the boundary is unchanged: a value bound to a sensitive key name is masked; a secret embedded in a bash command string is not.
|
|
8
|
+
See [ADR 0010].
|
|
9
|
+
|
|
10
|
+
## The `message` field is removed
|
|
11
|
+
|
|
12
|
+
Every `permission_request.*` entry previously carried `message`, the same assembled sentence the prompt showed.
|
|
13
|
+
It is gone.
|
|
14
|
+
What the log accumulated was a side effect of how a prompt happened to be worded, and the sentence duplicated facts the entry already carried in structured form.
|
|
15
|
+
|
|
16
|
+
In its place each entry carries the ask's own request facts:
|
|
17
|
+
|
|
18
|
+
| Field | Present when | Meaning |
|
|
19
|
+
| -------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `surface` | always | The gate surface the rule fired on (`bash`, `path`, `external_directory`, `skill`, a tool name) |
|
|
21
|
+
| `matchedPattern` | a rule matched | The rule that fired, including a sentinel such as `<indirection-bash-wrapper>` |
|
|
22
|
+
| `executedUnit` | a wrapper hides an inner command | The unit that will actually run, e.g. `grep foo` inside `xargs grep foo` |
|
|
23
|
+
| `commandContext` | the unit came from a substitution or subshell | `command_substitution`, `process_substitution`, or `subshell` |
|
|
24
|
+
| `invokedToolName` | a shell alias re-exposed bash | The tool name the agent actually called, e.g. `exec_command` |
|
|
25
|
+
| `forwarded` | the ask arrived from a subagent | `true` |
|
|
26
|
+
| `requesterSessionId` | the ask arrived from a subagent | The requesting session's id |
|
|
27
|
+
|
|
28
|
+
A field the ask does not carry is omitted rather than written as `null`.
|
|
29
|
+
|
|
30
|
+
The existing structured columns are unchanged: `requestId`, `source`, `agentName`, `toolCallId`, `toolName`, `skillName`, `path`, `command`, `target`, `toolInputPreview`, `resolution`, and `denialReason`.
|
|
31
|
+
|
|
32
|
+
If you parse the log, read those fields instead of `message`.
|
|
33
|
+
Two of the new ones close gaps a `message` reader never had: a `permission_request.blocked` entry recorded that policy denied the call but never which rule, and `executedUnit` had not reached the log at all.
|
|
34
|
+
|
|
35
|
+
## Every review-log value is bounded
|
|
36
|
+
|
|
37
|
+
Each string the review log writes is now narrowed to `reviewLogFieldMaxWidth` characters and marked with a trailing ellipsis.
|
|
38
|
+
The default is `1000` — the width that already bounded `toolInputPreview`, applied to every field so the log has one limit rather than one limit and an unbounded remainder.
|
|
39
|
+
|
|
40
|
+
The practical effect is on `command`: a bash command longer than the width is stored shortened, where it was previously written whole.
|
|
41
|
+
On a real 9,484-entry log, 188 of 4,325 command entries (4.3%) exceed the default.
|
|
42
|
+
|
|
43
|
+
To keep longer values, raise the setting:
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
{
|
|
47
|
+
"reviewLogFieldMaxWidth": 8000
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
There is no "unbounded" value by design — the point of the bound is that log growth is a decision rather than an accident.
|
|
52
|
+
The **debug** log is unaffected: it is opt-in and exists to be read in full.
|
|
53
|
+
|
|
54
|
+
This bound is a length cap, not redaction.
|
|
55
|
+
It narrows by length alone and never inspects a value to decide what to hide, and the two compose independently: a sensitive-keyed value is masked whole however long it was.
|
|
56
|
+
|
|
57
|
+
## Removed exports
|
|
58
|
+
|
|
59
|
+
`ToolPreviewFormatterOptions.toolInputLogPreviewMaxLength` and `TOOL_INPUT_LOG_PREVIEW_MAX_LENGTH` are removed, superseded by `reviewLogFieldMaxWidth`.
|
|
60
|
+
`ToolPreviewFormatter` no longer truncates what it produces for the log; the writer bounds it instead.
|
|
61
|
+
|
|
62
|
+
## Agent-facing denial text
|
|
63
|
+
|
|
64
|
+
Not a contract, but visible: the text returned to the agent when a call is refused no longer echoes the bash command or the tool input.
|
|
65
|
+
It names the surface, the tool, the rule that fired, the flagged path or MCP target or skill, and the operator's or human's reason.
|
|
66
|
+
[ADR 0011] §7 states the rule — the agent renderer identifies the call; it does not reproduce it — and the agent already holds its own arguments, which the harness keeps beside the refusal.
|
|
67
|
+
|
|
68
|
+
[ADR 0010]: https://github.com/gotgenes/pi-packages/blob/main/packages/pi-permission-system/docs/decisions/0010-permission-log-secret-exposure.md
|
|
69
|
+
[ADR 0011]: https://github.com/gotgenes/pi-packages/blob/main/packages/pi-permission-system/docs/decisions/0011-prompt-presentation-contract.md
|
package/docs/troubleshooting.md
CHANGED
|
@@ -51,8 +51,9 @@ This makes it easy to verify which files the extension actually loaded:
|
|
|
51
51
|
|
|
52
52
|
- If a dangerous action is possible via an allowed tool, policy must explicitly restrict it
|
|
53
53
|
- This is a permission decision layer, not a sandbox — for true isolation see [Agent Sandboxes](https://engine.build/lab/agent-sandboxes)
|
|
54
|
-
- The review log records bash command strings
|
|
54
|
+
- The review log records bash command strings unredacted.
|
|
55
55
|
Log files are created owner-only (`0600`), and values bound to a sensitive key name (`authorization`, `token`, `password`, …) are masked — but a secret embedded in a command string is not.
|
|
56
|
+
Review-log values are shortened at `reviewLogFieldMaxWidth` (1000 characters by default), which bounds the file's growth but is a length cap, not redaction.
|
|
56
57
|
See [Log file sensitivity](configuration.md#log-file-sensitivity) and [ADR 0010].
|
|
57
58
|
|
|
58
59
|
[ADR 0010]: https://github.com/gotgenes/pi-packages/blob/main/packages/pi-permission-system/docs/decisions/0010-permission-log-secret-exposure.md
|
package/package.json
CHANGED
|
@@ -55,16 +55,26 @@
|
|
|
55
55
|
"minimum": 1,
|
|
56
56
|
"maximum": 9007199254740991
|
|
57
57
|
},
|
|
58
|
+
"reviewLogFieldMaxWidth": {
|
|
59
|
+
"description": "Maximum characters of any one value written to the permission review log. Omit to use the default (1000).",
|
|
60
|
+
"markdownDescription": "Maximum characters of any one value written to the permission review log.\n\nOmit to use the default (1000). Every string the review log writes is narrowed to this width and marked with an ellipsis, so the log's growth is a decision you make rather than a side effect of how long a command happened to be. Raise it to keep longer values — a bash command exceeding the width is stored shortened.\n\nThis is a length bound, not redaction: it never inspects a value to decide what to hide. Key-name masking is unchanged and applies independently.",
|
|
61
|
+
"default": 1000,
|
|
62
|
+
"type": "integer",
|
|
63
|
+
"minimum": 1,
|
|
64
|
+
"maximum": 9007199254740991
|
|
65
|
+
},
|
|
58
66
|
"toolInputPreviewMaxLength": {
|
|
59
|
-
"
|
|
60
|
-
"
|
|
67
|
+
"deprecated": true,
|
|
68
|
+
"description": "Deprecated and ignored. Superseded by promptMaxRows and promptFieldMaxWidth, which bound the whole prompt rather than one preview. Still accepted so an existing config is not rejected; remove it.",
|
|
69
|
+
"markdownDescription": "**Deprecated and ignored.** Superseded by `promptMaxRows` and `promptFieldMaxWidth`, which bound the whole permission prompt rather than one preview inside it.\n\nStill accepted so an existing config is not rejected fail-closed, but the value no longer takes effect. Remove it.",
|
|
61
70
|
"type": "integer",
|
|
62
71
|
"minimum": 1,
|
|
63
72
|
"maximum": 9007199254740991
|
|
64
73
|
},
|
|
65
74
|
"toolTextSummaryMaxLength": {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
75
|
+
"deprecated": true,
|
|
76
|
+
"description": "Deprecated and ignored. Superseded by promptMaxRows and promptFieldMaxWidth, which bound the whole prompt rather than one summary. Still accepted so an existing config is not rejected; remove it.",
|
|
77
|
+
"markdownDescription": "**Deprecated and ignored.** Superseded by `promptMaxRows` and `promptFieldMaxWidth`, which bound the whole permission prompt rather than one summary inside it.\n\nStill accepted so an existing config is not rejected fail-closed, but the value no longer takes effect. Remove it.",
|
|
68
78
|
"type": "integer",
|
|
69
79
|
"minimum": 1,
|
|
70
80
|
"maximum": 9007199254740991
|
|
@@ -10,7 +10,7 @@ import { PATH_BEARING_TOOLS } from "./path-surfaces";
|
|
|
10
10
|
* `toolName === "bash"`/`"mcp"` re-derivation across the extraction consumers
|
|
11
11
|
* (`input-normalizer`, `tool-input-path`, the tool-call gate pipeline, and
|
|
12
12
|
* `permission-manager`'s source derivation) and the presentation consumers
|
|
13
|
-
* (`tool-preview-formatter`, `permission-prompts`,
|
|
13
|
+
* (`tool-preview-formatter`, `permission-prompts`, the payload builders, and
|
|
14
14
|
* `deriveDecisionValue`), which dispatch on {@link classifyToolKind} or
|
|
15
15
|
* {@link isMcpCheck}. Adding a tool kind means editing {@link classifyToolKind}
|
|
16
16
|
* plus the exhaustive switches the compiler flags — an OCP win over silent
|
|
@@ -36,6 +36,7 @@ import type { ServingLookup } from "#src/authority/serving-registry";
|
|
|
36
36
|
import type { SubagentSessionRegistry } from "#src/authority/subagent-registry";
|
|
37
37
|
import { createPermissionRequestId } from "#src/permission-request-id";
|
|
38
38
|
import { buildUiPrompt } from "#src/permission-ui-prompt";
|
|
39
|
+
import type { PromptPayload } from "#src/presentation/prompt-payload";
|
|
39
40
|
import type { DebugReviewLogger } from "#src/session-logger";
|
|
40
41
|
import { toRecord } from "#src/value-guards";
|
|
41
42
|
import type { TerminalAuthorizer } from "./authorizer";
|
|
@@ -68,7 +69,7 @@ function getContextSystemPrompt(ctx: ForwarderContext): string | undefined {
|
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* The facts a forwarded request relays unchanged from the child's ask: the
|
|
71
|
-
* prompt
|
|
72
|
+
* prompt payload, the optional display projection, and the optional
|
|
72
73
|
* session-approval suggestion.
|
|
73
74
|
*
|
|
74
75
|
* Bundled into one object so the two-hop private chain
|
|
@@ -82,7 +83,8 @@ interface ForwardedRequestFacts {
|
|
|
82
83
|
* decision instead of a third being minted here.
|
|
83
84
|
*/
|
|
84
85
|
requestId: string;
|
|
85
|
-
|
|
86
|
+
/** The child's complete prompt payload, relayed for the serving node to render. */
|
|
87
|
+
payload: PromptPayload;
|
|
86
88
|
display?: ForwardedPromptDisplay;
|
|
87
89
|
sessionApproval?: ForwardedSessionApproval;
|
|
88
90
|
/** The child-fixed access facts; the edge completes them into a `ForwardedAccessIntent`. */
|
|
@@ -171,7 +173,7 @@ export class ParentAuthorizer implements TerminalAuthorizer {
|
|
|
171
173
|
const uiPrompt = buildUiPrompt(details);
|
|
172
174
|
return this.waitForForwardedApproval(this.ctx, {
|
|
173
175
|
requestId: details.requestId,
|
|
174
|
-
|
|
176
|
+
payload: details.payload,
|
|
175
177
|
display: {
|
|
176
178
|
source: uiPrompt.source,
|
|
177
179
|
surface: uiPrompt.surface,
|
|
@@ -300,7 +302,7 @@ export class ParentAuthorizer implements TerminalAuthorizer {
|
|
|
300
302
|
requesterSessionId,
|
|
301
303
|
targetSessionId,
|
|
302
304
|
requesterAgentName,
|
|
303
|
-
|
|
305
|
+
payload: facts.payload,
|
|
304
306
|
...(facts.display
|
|
305
307
|
? {
|
|
306
308
|
source: facts.display.source,
|
|
@@ -49,9 +49,9 @@ export interface Authorizer {
|
|
|
49
49
|
* ADR 0007's terminal-cannot-defer invariant.
|
|
50
50
|
*
|
|
51
51
|
* One method, one responsibility. `DenyingAuthorizer` ignores `details`;
|
|
52
|
-
* `LocalUserAuthorizer`
|
|
53
|
-
* event from
|
|
54
|
-
*
|
|
52
|
+
* `LocalUserAuthorizer` renders `payload` for the human and derives the UI
|
|
53
|
+
* event from the request facts; `ParentAuthorizer` ships `payload` over the
|
|
54
|
+
* wire so the serving node renders it under its own budget.
|
|
55
55
|
*/
|
|
56
56
|
export interface TerminalAuthorizer {
|
|
57
57
|
authorize(
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "#src/authority/permission-forwarding";
|
|
15
15
|
import type { SubagentSessionRegistry } from "#src/authority/subagent-registry";
|
|
16
16
|
import { buildForwardedAskPayload } from "#src/presentation/forwarded-ask-payload";
|
|
17
|
-
import { renderLegacyMessage } from "#src/presentation/legacy-message";
|
|
18
17
|
import { SessionApproval } from "#src/session-approval";
|
|
19
18
|
import type { SessionApprovalRecorder } from "#src/session-approval-recorder";
|
|
20
19
|
import type { DebugReviewLogger } from "#src/session-logger";
|
|
@@ -100,7 +99,6 @@ function buildForwardedAskDetails(
|
|
|
100
99
|
requestId: request.id,
|
|
101
100
|
source: request.source ?? "tool_call",
|
|
102
101
|
agentName: request.requesterAgentName || null,
|
|
103
|
-
message: renderLegacyMessage(payload),
|
|
104
102
|
payload,
|
|
105
103
|
surface: request.surface ?? null,
|
|
106
104
|
value: request.value ?? null,
|