@gotgenes/pi-permission-system 38.0.0 → 38.0.1

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 CHANGED
@@ -5,6 +5,13 @@ 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
+ ## [38.0.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v38.0.0...pi-permission-system-v38.0.1) (2026-10-02)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **pi-permission-system:** tolerate a non-Pi before_agent_start payload ([#860](https://github.com/gotgenes/pi-packages/issues/860)) ([33f6a24](https://github.com/gotgenes/pi-packages/commit/33f6a24c820f23d43a56c9377245e804c2fd8ba2)), closes [#860](https://github.com/gotgenes/pi-packages/issues/860)
14
+
8
15
  ## [38.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v37.0.0...pi-permission-system-v38.0.0) (2026-10-02)
9
16
 
10
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "38.0.0",
3
+ "version": "38.0.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -22,8 +22,8 @@ import type { TurnPreparation } from "./session-turn-prep";
22
22
 
23
23
  /** Minimal subset of BeforeAgentStartEvent used by this handler. */
24
24
  interface BeforeAgentStartPayload {
25
- /** The prompt rendered from `systemPromptOptions` as it stands right now. */
26
- readonly systemPrompt: string;
25
+ /** Pi renders a string; a host may supply ordered prompt fragments instead. */
26
+ readonly systemPrompt: string | readonly string[];
27
27
  /**
28
28
  * The mutable parts Pi renders the prompt from; later handlers, and Pi
29
29
  * itself, see what this handler writes here. `customPrompt` says whether Pi
@@ -32,7 +32,7 @@ interface BeforeAgentStartPayload {
32
32
  * renders from, and `sections` is where it is stated. `skills` is the
33
33
  * catalogue Pi renders, which policy narrows.
34
34
  */
35
- systemPromptOptions: Pick<
35
+ systemPromptOptions?: Pick<
36
36
  NormalizedBuildSystemPromptOptions,
37
37
  "customPrompt" | "toolSnippets" | "promptGuidelines" | "sections" | "skills"
38
38
  >;
@@ -56,10 +56,12 @@ export function shouldExposeTool(
56
56
  /**
57
57
  * Handles the `before_agent_start` event: tool filtering + prompt sanitization.
58
58
  *
59
- * Every change to the prompt is stated through `event.systemPromptOptions`,
59
+ * When provided, prompt changes are stated through `event.systemPromptOptions`,
60
60
  * never as a returned `systemPrompt`: a returned prompt is frozen for the run,
61
61
  * so the sections an extension later in the chain adds (Pi's own
62
62
  * `<mcp_servers>` among them) would never reach the provider (#999).
63
+ * Without those options, only tool filtering and skill path resolution run;
64
+ * there is no mutable section or skill catalogue to narrow.
63
65
  *
64
66
  * Narrowing the active set is enough for a prompt Pi wrote: Pi renders its
65
67
  * `<tools>` and `<rules>` from the reconciled active set. A subagent child's
@@ -97,7 +99,12 @@ export class AgentPrepHandler {
97
99
  ): Promise<BeforeAgentStartEventResult> {
98
100
  this.turnPrep.prepare(ctx);
99
101
 
100
- const agentName = this.session.resolveAgentName(ctx, event.systemPrompt);
102
+ // Normalize once at the boundary, before either prompt consumer reads it.
103
+ const systemPrompt =
104
+ typeof event.systemPrompt === "string"
105
+ ? event.systemPrompt
106
+ : event.systemPrompt.join("\n");
107
+ const agentName = this.session.resolveAgentName(ctx, systemPrompt);
101
108
  const registered = readRegisteredTools(this.toolRegistry.getAll());
102
109
  const surface = this.session.resolveExposedTools(
103
110
  this.observeToolSurface(registered),
@@ -118,7 +125,7 @@ export class AgentPrepHandler {
118
125
  }
119
126
 
120
127
  const options = event.systemPromptOptions;
121
- if (this.isSubagentUnderCustomPrompt(event, ctx)) {
128
+ if (options && this.isSubagentUnderCustomPrompt(event, ctx)) {
122
129
  const sections = renderToolSurfaceSections({
123
130
  allowedTools,
124
131
  toolSnippets: options.toolSnippets,
@@ -134,12 +141,14 @@ export class AgentPrepHandler {
134
141
  // read before the skill list is narrowed.
135
142
  this.session.setActiveSkillEntries(
136
143
  visibleSkillPromptEntries(
137
- event.systemPrompt,
144
+ systemPrompt,
138
145
  this.resolver,
139
146
  agentName,
140
147
  this.session.getPathNormalizer(),
141
148
  ),
142
149
  );
150
+ if (!options) return {};
151
+
143
152
  // Denials are judged on the list Pi renders `<skills>` from, not on the
144
153
  // rendered prompt: that prompt predates this turn's tool changes, and on
145
154
  // the turn `read`/`bash` return from a full denial it lists no catalogue.
@@ -181,5 +190,5 @@ export class AgentPrepHandler {
181
190
  * test, so an empty string reads here the way it reads there: as none.
182
191
  */
183
192
  function hasCustomPrompt(event: BeforeAgentStartPayload): boolean {
184
- return Boolean(event.systemPromptOptions.customPrompt);
193
+ return Boolean(event.systemPromptOptions?.customPrompt);
185
194
  }