@gotgenes/pi-permission-system 0.7.0 → 1.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 +34 -0
- package/package.json +1 -1
- package/src/config-modal.ts +18 -60
- package/src/index.ts +1 -4
- package/tests/config-modal.test.ts +0 -12
- package/tests/permission-system.test.ts +4 -1
- package/src/model-option-compatibility.ts +0 -182
- package/src/zellij-modal.ts +0 -1117
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ 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
|
+
## [1.0.0](https://github.com/gotgenes/pi-permission-system/compare/v0.8.0...v1.0.0) (2026-05-03)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* The bundled temperature-stripping shim for OpenAI Responses-style APIs (openai-codex-responses, openai-responses, azure-openai-responses) has been removed. This module monkey-patched the provider stack at the process level and had no connection to permission enforcement. Users who need the shim can extract it into a standalone extension.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* remove out-of-scope model-option-compatibility provider shim ([#17](https://github.com/gotgenes/pi-permission-system/issues/17)) ([b390896](https://github.com/gotgenes/pi-permission-system/commit/b39089611fe565f81dacf7fa0bff3af36d50f7ce))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* plan removal of out-of-scope model-option-compatibility shim ([#17](https://github.com/gotgenes/pi-permission-system/issues/17)) ([a48f2ef](https://github.com/gotgenes/pi-permission-system/commit/a48f2ef025cbe970d21071b94552fcb9a4f7de89))
|
|
23
|
+
* **retro:** add retro notes for issue [#16](https://github.com/gotgenes/pi-permission-system/issues/16) ([ee710cb](https://github.com/gotgenes/pi-permission-system/commit/ee710cba9b1fe92a39016c61348d2a0eb2895d1f))
|
|
24
|
+
|
|
25
|
+
## [0.8.0](https://github.com/gotgenes/pi-permission-system/compare/v0.7.0...v0.8.0) (2026-05-03)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* replace vendored zellij-modal with direct pi-tui SettingsList ([#16](https://github.com/gotgenes/pi-permission-system/issues/16)) ([868675f](https://github.com/gotgenes/pi-permission-system/commit/868675ff2df1ab429dc48160c7e545ddc8a451e1))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Documentation
|
|
34
|
+
|
|
35
|
+
* plan delete vendored zellij-modal and rebuild settings UI ([#16](https://github.com/gotgenes/pi-permission-system/issues/16)) ([35274da](https://github.com/gotgenes/pi-permission-system/commit/35274da005b90023de29bbc144abfa6ad27bb73c))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Miscellaneous Chores
|
|
39
|
+
|
|
40
|
+
* add project-local pi-autoformat config ([13a6f33](https://github.com/gotgenes/pi-permission-system/commit/13a6f339a52a546812432bc055bdb32cc2bd6d90))
|
|
41
|
+
|
|
8
42
|
## [0.7.0](https://github.com/gotgenes/pi-permission-system/compare/v0.6.1...v0.7.0) (2026-05-02)
|
|
9
43
|
|
|
10
44
|
|
package/package.json
CHANGED
package/src/config-modal.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
1
|
+
import {
|
|
2
|
+
type ExtensionAPI,
|
|
3
|
+
type ExtensionCommandContext,
|
|
4
|
+
getSettingsListTheme,
|
|
4
5
|
} from "@mariozechner/pi-coding-agent";
|
|
5
|
-
import type
|
|
6
|
+
import { type SettingItem, SettingsList } from "@mariozechner/pi-tui";
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_EXTENSION_CONFIG,
|
|
9
10
|
type PermissionSystemExtensionConfig,
|
|
10
11
|
} from "./extension-config.js";
|
|
11
|
-
import { ZellijModal, ZellijSettingsModal } from "./zellij-modal.js";
|
|
12
12
|
|
|
13
13
|
interface PermissionSystemConfigController {
|
|
14
14
|
getConfig(): PermissionSystemExtensionConfig;
|
|
@@ -19,10 +19,6 @@ interface PermissionSystemConfigController {
|
|
|
19
19
|
getConfigPath(): string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
interface SettingValueSyncTarget {
|
|
23
|
-
updateValue(id: string, value: string): void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
22
|
const ON_OFF = ["on", "off"];
|
|
27
23
|
const COMMAND_ARGUMENTS = [
|
|
28
24
|
{
|
|
@@ -118,7 +114,7 @@ function applySetting(
|
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
function syncSettingValues(
|
|
121
|
-
settingsList:
|
|
117
|
+
settingsList: SettingsList,
|
|
122
118
|
config: PermissionSystemExtensionConfig,
|
|
123
119
|
): void {
|
|
124
120
|
settingsList.updateValue("yoloMode", toOnOff(config.yoloMode));
|
|
@@ -155,60 +151,22 @@ async function openSettingsModal(
|
|
|
155
151
|
};
|
|
156
152
|
|
|
157
153
|
await ctx.ui.custom<void>(
|
|
158
|
-
(
|
|
154
|
+
(_tui, _theme, _keybindings, done) => {
|
|
159
155
|
let current = controller.getConfig();
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
current = applySetting(current, id, newValue);
|
|
170
|
-
controller.setConfig(current, ctx);
|
|
171
|
-
current = controller.getConfig();
|
|
172
|
-
if (settingsModal) {
|
|
173
|
-
syncSettingValues(settingsModal, current);
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
onClose: () => done(),
|
|
177
|
-
helpText: `/permission-system show • /permission-system reset • ${controller.getConfigPath()}`,
|
|
178
|
-
enableSearch: true,
|
|
179
|
-
},
|
|
180
|
-
theme,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
const modal = new ZellijModal(
|
|
184
|
-
settingsModal,
|
|
185
|
-
{
|
|
186
|
-
borderStyle: "rounded",
|
|
187
|
-
titleBar: {
|
|
188
|
-
left: "Permission System Settings",
|
|
189
|
-
right: "pi-permission-system",
|
|
190
|
-
},
|
|
191
|
-
helpUndertitle: {
|
|
192
|
-
text: "Esc: close | ↑↓: navigate | Space: toggle",
|
|
193
|
-
color: "dim",
|
|
194
|
-
},
|
|
195
|
-
overlay: overlayOptions,
|
|
156
|
+
const settingsList = new SettingsList(
|
|
157
|
+
buildSettingItems(current),
|
|
158
|
+
10,
|
|
159
|
+
getSettingsListTheme(),
|
|
160
|
+
(id, newValue) => {
|
|
161
|
+
current = applySetting(current, id, newValue);
|
|
162
|
+
controller.setConfig(current, ctx);
|
|
163
|
+
current = controller.getConfig();
|
|
164
|
+
syncSettingValues(settingsList, current);
|
|
196
165
|
},
|
|
197
|
-
|
|
166
|
+
() => done(),
|
|
198
167
|
);
|
|
199
168
|
|
|
200
|
-
return
|
|
201
|
-
render(width: number) {
|
|
202
|
-
return modal.renderModal(width).lines;
|
|
203
|
-
},
|
|
204
|
-
invalidate() {
|
|
205
|
-
modal.invalidate();
|
|
206
|
-
},
|
|
207
|
-
handleInput(data: string) {
|
|
208
|
-
modal.handleInput(data);
|
|
209
|
-
tui.requestRender();
|
|
210
|
-
},
|
|
211
|
-
};
|
|
169
|
+
return settingsList;
|
|
212
170
|
},
|
|
213
171
|
{ overlay: true, overlayOptions },
|
|
214
172
|
);
|
package/src/index.ts
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
savePermissionSystemConfig,
|
|
36
36
|
} from "./extension-config.js";
|
|
37
37
|
import { createPermissionSystemLogger, safeJsonStringify } from "./logging.js";
|
|
38
|
-
import { registerModelOptionCompatibilityGuard } from "./model-option-compatibility.js";
|
|
39
38
|
import {
|
|
40
39
|
isPermissionDecisionState,
|
|
41
40
|
type PermissionPromptDecision,
|
|
@@ -297,7 +296,7 @@ function getActiveAgentNameFromSystemPrompt(
|
|
|
297
296
|
}
|
|
298
297
|
|
|
299
298
|
const match = systemPrompt.match(ACTIVE_AGENT_TAG_REGEX);
|
|
300
|
-
if (!match
|
|
299
|
+
if (!match?.[1]) {
|
|
301
300
|
return null;
|
|
302
301
|
}
|
|
303
302
|
|
|
@@ -1338,8 +1337,6 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
1338
1337
|
|
|
1339
1338
|
setLoggingWarningReporter(notifyWarning);
|
|
1340
1339
|
refreshExtensionConfig();
|
|
1341
|
-
registerModelOptionCompatibilityGuard(pi);
|
|
1342
|
-
|
|
1343
1340
|
registerPermissionSystemCommand(pi, {
|
|
1344
1341
|
getConfig: () => extensionConfig,
|
|
1345
1342
|
setConfig: saveExtensionConfig,
|
|
@@ -16,14 +16,6 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
|
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
18
|
vi.mock("@mariozechner/pi-tui", () => ({
|
|
19
|
-
Box: class {},
|
|
20
|
-
Container: class {
|
|
21
|
-
addChild(): void {}
|
|
22
|
-
render(): string[] {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
invalidate(): void {}
|
|
26
|
-
},
|
|
27
19
|
SettingsList: class {
|
|
28
20
|
handleInput(): void {}
|
|
29
21
|
updateValue(): void {}
|
|
@@ -32,10 +24,6 @@ vi.mock("@mariozechner/pi-tui", () => ({
|
|
|
32
24
|
}
|
|
33
25
|
invalidate(): void {}
|
|
34
26
|
},
|
|
35
|
-
Spacer: class {},
|
|
36
|
-
Text: class {},
|
|
37
|
-
truncateToWidth: (text: string) => text,
|
|
38
|
-
visibleWidth: (text: string) => text.length,
|
|
39
27
|
}));
|
|
40
28
|
|
|
41
29
|
type Notification = { message: string; level: "info" | "warning" | "error" };
|
|
@@ -92,7 +92,10 @@ function createManager(
|
|
|
92
92
|
type MockHandler = (
|
|
93
93
|
event: Record<string, unknown>,
|
|
94
94
|
ctx: Record<string, unknown>,
|
|
95
|
-
) =>
|
|
95
|
+
) =>
|
|
96
|
+
| Promise<Record<string, unknown> | undefined>
|
|
97
|
+
| Record<string, unknown>
|
|
98
|
+
| undefined;
|
|
96
99
|
|
|
97
100
|
type ExtensionHarness = {
|
|
98
101
|
baseDir: string;
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Api,
|
|
3
|
-
type AssistantMessageEventStream,
|
|
4
|
-
getApiProvider,
|
|
5
|
-
type Context as LlmContext,
|
|
6
|
-
type Model,
|
|
7
|
-
type SimpleStreamOptions,
|
|
8
|
-
} from "@mariozechner/pi-ai";
|
|
9
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
10
|
-
|
|
11
|
-
const GUARDED_TEMPERATURE_APIS = [
|
|
12
|
-
"openai-codex-responses",
|
|
13
|
-
"openai-responses",
|
|
14
|
-
"azure-openai-responses",
|
|
15
|
-
] as const satisfies readonly Api[];
|
|
16
|
-
const OPENAI_RESPONSES_APIS = new Set<Api>([
|
|
17
|
-
"openai-responses",
|
|
18
|
-
"azure-openai-responses",
|
|
19
|
-
]);
|
|
20
|
-
const TEMPERATURE_UNSUPPORTED_APIS = new Set<Api>(["openai-codex-responses"]);
|
|
21
|
-
const TEMPERATURE_UNSUPPORTED_PROVIDERS = new Set<string>(["openai-codex"]);
|
|
22
|
-
|
|
23
|
-
export type ApiStreamSimpleDelegate = (
|
|
24
|
-
model: Model<Api>,
|
|
25
|
-
context: LlmContext,
|
|
26
|
-
options?: SimpleStreamOptions,
|
|
27
|
-
) => AssistantMessageEventStream;
|
|
28
|
-
|
|
29
|
-
type GlobalWithPermissionSystemProviderGuard = typeof globalThis & {
|
|
30
|
-
__piPermissionSystemModelOptionBaseStreams?: Map<
|
|
31
|
-
string,
|
|
32
|
-
ApiStreamSimpleDelegate
|
|
33
|
-
>;
|
|
34
|
-
__piPermissionSystemModelOptionGuardedApis?: Set<string>;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
function getBaseApiStreams(): Map<string, ApiStreamSimpleDelegate> {
|
|
38
|
-
const globalScope = globalThis as GlobalWithPermissionSystemProviderGuard;
|
|
39
|
-
if (!globalScope.__piPermissionSystemModelOptionBaseStreams) {
|
|
40
|
-
globalScope.__piPermissionSystemModelOptionBaseStreams = new Map<
|
|
41
|
-
string,
|
|
42
|
-
ApiStreamSimpleDelegate
|
|
43
|
-
>();
|
|
44
|
-
}
|
|
45
|
-
return globalScope.__piPermissionSystemModelOptionBaseStreams;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function getGuardedApis(): Set<string> {
|
|
49
|
-
const globalScope = globalThis as GlobalWithPermissionSystemProviderGuard;
|
|
50
|
-
if (!globalScope.__piPermissionSystemModelOptionGuardedApis) {
|
|
51
|
-
globalScope.__piPermissionSystemModelOptionGuardedApis = new Set<string>();
|
|
52
|
-
}
|
|
53
|
-
return globalScope.__piPermissionSystemModelOptionGuardedApis;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function normalizeIdentifier(value: string | undefined): string {
|
|
57
|
-
return (value ?? "").trim().toLowerCase();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function hasModelToken(modelId: string, token: string): boolean {
|
|
61
|
-
return normalizeIdentifier(modelId)
|
|
62
|
-
.split(/[^a-z0-9]+/)
|
|
63
|
-
.includes(token);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function getUnsupportedTemperatureReason(
|
|
67
|
-
model: Pick<Model<Api>, "api" | "id" | "provider" | "reasoning">,
|
|
68
|
-
): string | undefined {
|
|
69
|
-
if (TEMPERATURE_UNSUPPORTED_APIS.has(model.api)) {
|
|
70
|
-
return `api '${model.api}' does not support temperature`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const provider = normalizeIdentifier(model.provider);
|
|
74
|
-
if (TEMPERATURE_UNSUPPORTED_PROVIDERS.has(provider)) {
|
|
75
|
-
return `provider '${model.provider}' does not support temperature`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
OPENAI_RESPONSES_APIS.has(model.api) &&
|
|
80
|
-
hasModelToken(model.id, "codex")
|
|
81
|
-
) {
|
|
82
|
-
return `model '${model.id}' does not support temperature`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (OPENAI_RESPONSES_APIS.has(model.api) && model.reasoning) {
|
|
86
|
-
return `reasoning model '${model.id}' accepts only the provider default temperature`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
93
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function stripUnsupportedTemperatureFromPayload(
|
|
97
|
-
payload: unknown,
|
|
98
|
-
): unknown {
|
|
99
|
-
if (!isRecord(payload) || !("temperature" in payload)) {
|
|
100
|
-
return payload;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const { temperature: _temperature, ...rest } = payload;
|
|
104
|
-
return rest;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function composeTemperatureSanitizer(
|
|
108
|
-
options: SimpleStreamOptions | undefined,
|
|
109
|
-
model: Model<Api>,
|
|
110
|
-
): SimpleStreamOptions | undefined {
|
|
111
|
-
const reason = getUnsupportedTemperatureReason(model);
|
|
112
|
-
if (!reason && options?.temperature === undefined) {
|
|
113
|
-
return options;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!reason) {
|
|
117
|
-
return options;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const existingOnPayload = options?.onPayload;
|
|
121
|
-
const nextOptions: SimpleStreamOptions = options
|
|
122
|
-
? { ...options, temperature: undefined }
|
|
123
|
-
: {};
|
|
124
|
-
|
|
125
|
-
nextOptions.onPayload = async (payload, payloadModel) => {
|
|
126
|
-
const transformedPayload = existingOnPayload
|
|
127
|
-
? await existingOnPayload(payload, payloadModel)
|
|
128
|
-
: undefined;
|
|
129
|
-
return stripUnsupportedTemperatureFromPayload(
|
|
130
|
-
transformedPayload ?? payload,
|
|
131
|
-
);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return nextOptions;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function ensureModelOptionGuardForApi(pi: ExtensionAPI, api: Api): boolean {
|
|
138
|
-
const guardedApis = getGuardedApis();
|
|
139
|
-
if (guardedApis.has(api)) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const baseStreams = getBaseApiStreams();
|
|
144
|
-
let baseStream = baseStreams.get(api);
|
|
145
|
-
if (!baseStream) {
|
|
146
|
-
const currentProvider = getApiProvider(api);
|
|
147
|
-
if (!currentProvider) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
baseStream = currentProvider.streamSimple as ApiStreamSimpleDelegate;
|
|
151
|
-
baseStreams.set(api, baseStream);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const providerName = `pi-permission-system-model-option-compatibility-${api.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`;
|
|
155
|
-
pi.registerProvider(providerName, {
|
|
156
|
-
api,
|
|
157
|
-
streamSimple: (model, context, options) => {
|
|
158
|
-
const typedModel = model as Model<Api>;
|
|
159
|
-
const delegate = baseStreams.get(typedModel.api);
|
|
160
|
-
if (!delegate) {
|
|
161
|
-
throw new Error(
|
|
162
|
-
`No base stream provider available for api '${typedModel.api}'.`,
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return delegate(
|
|
167
|
-
typedModel,
|
|
168
|
-
context,
|
|
169
|
-
composeTemperatureSanitizer(options, typedModel),
|
|
170
|
-
);
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
guardedApis.add(api);
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function registerModelOptionCompatibilityGuard(pi: ExtensionAPI): void {
|
|
179
|
-
for (const api of GUARDED_TEMPERATURE_APIS) {
|
|
180
|
-
ensureModelOptionGuardForApi(pi, api);
|
|
181
|
-
}
|
|
182
|
-
}
|