mirror of
https://github.com/SikongJueluo/pi-extensions.git
synced 2026-10-05 20:02:55 +08:00
- resolve the permissions service by the live session id in both tryRegister paths, so a mid-session republish re-keys via the re-emitted permissions:ready channel - read the session probe instead of the start-time snapshot for inner-cmd - pass the session key to publish/unpublishPermissionsService in tests and drop the removed PromptPermissionDetails.message field - raise the ai-judge peer floor to @gotgenes/pi-permission-system >=32.0.0 - bump dev deps: pi-coding-agent 0.85.1, vitest 5, typescript 7
89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
import {
|
|
getPermissionsService,
|
|
PERMISSIONS_READY_CHANNEL,
|
|
} from "@gotgenes/pi-permission-system";
|
|
import { authorizeInnerCommand, type SessionProbe } from "./authorizer";
|
|
|
|
const LINK_NAME = "inner-cmd";
|
|
|
|
/** Captured UI-root session: the live probe plus its identity provenance. */
|
|
interface CapturedRootSession {
|
|
readonly session: SessionProbe;
|
|
readonly sessionId: string;
|
|
}
|
|
|
|
export default function permissionInnerCmd(pi: ExtensionAPI): void {
|
|
let rootSession: CapturedRootSession | undefined;
|
|
let disposeAuthorizer: (() => void) | undefined;
|
|
|
|
/**
|
|
* Register the inner-command Authorizer once a proven UI-root session is
|
|
* captured and the permission service is ready.
|
|
*
|
|
* `rootSession` is set only from a UI-present `session_start` with a
|
|
* non-empty captured session id, so a headless or in-process subagent child
|
|
* that can still resolve the published parent service never registers.
|
|
* Either the extension or the permission system may start first; whichever
|
|
* satisfies the second condition completes registration.
|
|
*/
|
|
function tryRegister(): void {
|
|
if (disposeAuthorizer || !rootSession) {
|
|
return;
|
|
}
|
|
|
|
// Resolve by the live session id, not the start-time snapshot: a
|
|
// mid-session id change republishes under the new key and re-emits
|
|
// the ready channel, so the dynamic read re-keys onto it.
|
|
const service = getPermissionsService(
|
|
rootSession.session.getSessionId(),
|
|
);
|
|
if (!service) {
|
|
return;
|
|
}
|
|
|
|
const { session, sessionId } = rootSession;
|
|
disposeAuthorizer = service.registerAuthorizer(
|
|
LINK_NAME,
|
|
async (details, query, log) =>
|
|
authorizeInnerCommand({
|
|
details,
|
|
query,
|
|
log,
|
|
session,
|
|
expectedSessionId: sessionId,
|
|
}),
|
|
);
|
|
}
|
|
|
|
pi.on("session_start", (_event, ctx) => {
|
|
// Root-ownership gate: register only from the proven UI-present root.
|
|
// Headless/in-process children resolve the parent's process-global
|
|
// service but must not register with child-captured context.
|
|
if (!ctx.hasUI) {
|
|
return;
|
|
}
|
|
|
|
// Snapshot the session identity as registration provenance. A non-empty
|
|
// id is required so authorization can revalidate ownership later;
|
|
// without it, do not register.
|
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
if (!sessionId) {
|
|
return;
|
|
}
|
|
|
|
rootSession = { session: ctx.sessionManager, sessionId };
|
|
tryRegister();
|
|
});
|
|
|
|
pi.events.on(PERMISSIONS_READY_CHANNEL, () => {
|
|
tryRegister();
|
|
});
|
|
|
|
pi.on("session_shutdown", () => {
|
|
disposeAuthorizer?.();
|
|
disposeAuthorizer = undefined;
|
|
rootSession = undefined;
|
|
});
|
|
}
|