mirror of
https://github.com/SikongJueluo/pi-extensions.git
synced 2026-10-05 11:52:55 +08:00
feat(pi-permission-ai-judge): add deferring authorizer extension
- add pnpm workspace scaffold with shared TypeScript config - register an ai-bash-judge authorizer with pi-permission-system - log permission request details and deterministic policy verdicts - record review entries and defer to the next authorizer
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
"../packages/pi-permission-ai-judge"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "pi-extensions",
|
||||||
|
"description": "Persional pi code agent extentsions",
|
||||||
|
"scripts": {
|
||||||
|
"check": "pnpm -r check",
|
||||||
|
"test": "pnpm -r test"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@sikongjueluo/pi-permission-ai-judge",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"pi-package",
|
||||||
|
"pi-extension"
|
||||||
|
],
|
||||||
|
"pi": {
|
||||||
|
"extensions": [
|
||||||
|
"./src/index.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@earendil-works/pi-ai": "*",
|
||||||
|
"@earendil-works/pi-coding-agent": "*",
|
||||||
|
"@gotgenes/pi-permission-system": ">=20.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@earendil-works/pi-ai": "*",
|
||||||
|
"@earendil-works/pi-coding-agent": "*",
|
||||||
|
"@gotgenes/pi-permission-system": ">=20.10.0",
|
||||||
|
"typescript": "^5",
|
||||||
|
"vitest": "^3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"check": "tsc --noEmit",
|
||||||
|
"test": "vitest run"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||||
|
import {
|
||||||
|
getPermissionsService,
|
||||||
|
PERMISSIONS_READY_CHANNEL,
|
||||||
|
} from "@gotgenes/pi-permission-system";
|
||||||
|
|
||||||
|
const LINK_NAME = "ai-bash-judge";
|
||||||
|
|
||||||
|
export default function permissionAiJudge(pi: ExtensionAPI): void {
|
||||||
|
let sessionStarted = false;
|
||||||
|
let disposeAuthorizer: (() => void) | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试向 pi-permission-system 注册我们的 Authorizer。
|
||||||
|
*
|
||||||
|
* 之所以不能只在 session_start 里注册,是因为:
|
||||||
|
* - 可能我们的 extension 先启动;
|
||||||
|
* - 也可能 pi-permission-system 先启动。
|
||||||
|
*
|
||||||
|
* 所以同时监听 session_start 和 permissions:ready,
|
||||||
|
* 谁后满足条件,谁完成注册。
|
||||||
|
*/
|
||||||
|
function tryRegister(): void {
|
||||||
|
if (!sessionStarted || disposeAuthorizer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = getPermissionsService();
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
console.debug(
|
||||||
|
`[${LINK_NAME}] permission service not ready; waiting`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeAuthorizer = service.registerAuthorizer(
|
||||||
|
LINK_NAME,
|
||||||
|
|
||||||
|
async (details, query, log) => {
|
||||||
|
const surface =
|
||||||
|
details.accessIntent?.surface ??
|
||||||
|
details.surface ??
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
console.error(`[${LINK_NAME}] permission ask received`, {
|
||||||
|
requestId: details.requestId,
|
||||||
|
surface,
|
||||||
|
toolName: details.toolName,
|
||||||
|
command: details.command,
|
||||||
|
path: details.path,
|
||||||
|
value: details.value,
|
||||||
|
agentName: details.agentName,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 PermissionQuery。
|
||||||
|
*
|
||||||
|
* 这不会再次触发 Authorizer。
|
||||||
|
* 它只是询问 pi-permission-system 的确定性规则:
|
||||||
|
* “如果检查这个 bash command,规则本身会怎么判?”
|
||||||
|
*/
|
||||||
|
if (surface === "bash" && details.command) {
|
||||||
|
const result = query.checkPermission(
|
||||||
|
"bash",
|
||||||
|
details.command,
|
||||||
|
details.agentName ?? undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`[${LINK_NAME}] deterministic policy says`,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入 permission-system 自己的 review log。
|
||||||
|
*
|
||||||
|
* 以后 AI 的 decision trail 也应该写这里。
|
||||||
|
*/
|
||||||
|
log.review("ai_bash_judge.test", {
|
||||||
|
requestId: details.requestId,
|
||||||
|
surface,
|
||||||
|
command: details.command ?? null,
|
||||||
|
verdict: "defer",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第一版永远不审批。
|
||||||
|
*
|
||||||
|
* defer = 我不知道 / 我不处理,
|
||||||
|
* 请 Authorizer Chain 继续交给下一个审批者。
|
||||||
|
*
|
||||||
|
* 正常情况下最终就是 LocalUserAuthorizer,
|
||||||
|
* 所以你还是会看到原来的 permission prompt。
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
kind: "defer",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.error(`[${LINK_NAME}] registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
pi.on("session_start", () => {
|
||||||
|
sessionStarted = true;
|
||||||
|
tryRegister();
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.events.on(PERMISSIONS_READY_CHANNEL, () => {
|
||||||
|
tryRegister();
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.on("session_shutdown", () => {
|
||||||
|
disposeAuthorizer?.();
|
||||||
|
|
||||||
|
disposeAuthorizer = undefined;
|
||||||
|
sessionStarted = false;
|
||||||
|
|
||||||
|
console.error(`[${LINK_NAME}] unregistered`);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+2543
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
packages:
|
||||||
|
- "packages/*"
|
||||||
|
allowBuilds:
|
||||||
|
'@google/genai': false
|
||||||
|
esbuild: true
|
||||||
|
protobufjs: false
|
||||||
|
tree-sitter-bash: true
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2024",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user