mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
feat: enable permission-aware subagents
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import type { AgentSession, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
/**
|
||||
* Process-local lifecycle contract consumed by pi-permission-system.
|
||||
*
|
||||
* Keep these string channels local rather than importing permission-system
|
||||
* internals: pi-subagents only publishes child identity, while the permission
|
||||
* extension remains the sole owner of policy and authorization decisions.
|
||||
*/
|
||||
export const PERMISSION_CHILD_CREATED_CHANNEL = "subagents:child:session-created";
|
||||
export const PERMISSION_CHILD_DISPOSED_CHANNEL = "subagents:child:disposed";
|
||||
|
||||
interface EventPublisher {
|
||||
events?: {
|
||||
emit(channel: string, payload: unknown): void;
|
||||
};
|
||||
}
|
||||
|
||||
interface SessionIdentity {
|
||||
sessionManager?: {
|
||||
getSessionId?: () => string | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
interface ChildRegistration {
|
||||
events: NonNullable<EventPublisher["events"]>;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
const childRegistrations = new WeakMap<object, ChildRegistration>();
|
||||
const ACTIVE_AGENT_TAG = /<active_agent\s+name=["'][^"']+["'][^>]*>\s*/gi;
|
||||
const SAFE_AGENT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
||||
|
||||
function normalizedSessionId(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") return undefined;
|
||||
const normalized = value.trim();
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace inherited agent identity with this child's stable config key.
|
||||
* The permission system reads the first active_agent tag, so inherited parent
|
||||
* tags must be removed rather than merely appending another one.
|
||||
*/
|
||||
export function withActiveAgentIdentity(systemPrompt: string, agentName: string): string {
|
||||
const withoutInheritedIdentity = systemPrompt.replace(ACTIVE_AGENT_TAG, "").trimEnd();
|
||||
const normalizedAgentName = agentName.trim();
|
||||
if (!SAFE_AGENT_NAME.test(normalizedAgentName)) return withoutInheritedIdentity;
|
||||
return `${withoutInheritedIdentity}\n\n<active_agent name="${normalizedAgentName}"/>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a child synchronously before bindExtensions(). This is deliberately
|
||||
* fail-closed when an event publisher exists: a thrown lifecycle listener must
|
||||
* abort child startup rather than bind a headless permission system as if it
|
||||
* were an ordinary top-level session.
|
||||
*/
|
||||
export function registerPermissionChildSession(
|
||||
pi: Pick<ExtensionAPI, "events"> | EventPublisher,
|
||||
session: Pick<AgentSession, "sessionManager"> | SessionIdentity,
|
||||
parentSessionId: string | undefined,
|
||||
): boolean {
|
||||
const events = (pi as EventPublisher).events;
|
||||
const childSessionId = normalizedSessionId(
|
||||
(session as SessionIdentity).sessionManager?.getSessionId?.(),
|
||||
);
|
||||
const normalizedParentSessionId = normalizedSessionId(parentSessionId);
|
||||
if (!events || !childSessionId || !normalizedParentSessionId) return false;
|
||||
|
||||
const sessionKey = session as object;
|
||||
if (childRegistrations.has(sessionKey)) return true;
|
||||
|
||||
events.emit(PERMISSION_CHILD_CREATED_CHANNEL, {
|
||||
sessionId: childSessionId,
|
||||
parentSessionId: normalizedParentSessionId,
|
||||
});
|
||||
childRegistrations.set(sessionKey, { events, sessionId: childSessionId });
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Publish disposal at most once, after the child session has actually closed. */
|
||||
export function unregisterPermissionChildSession(session: object | undefined): void {
|
||||
if (!session) return;
|
||||
const registration = childRegistrations.get(session);
|
||||
if (!registration) return;
|
||||
childRegistrations.delete(session);
|
||||
registration.events.emit(PERMISSION_CHILD_DISPOSED_CHANNEL, {
|
||||
sessionId: registration.sessionId,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user