import { randomBytes } from "node:crypto"; import type { OperationArtifacts } from "./operation-artifacts.ts"; import { retainAcrossReload } from "./reload-boundary.ts"; /** * Process-local identity for one parent extension runtime. * * This is intentionally an opaque string rather than a session or filesystem * identity. It is never a recovery key and must not be persisted. */ export type ParentRuntimeId = string; /** Identity for one independent Fresh Completion operation. */ export type CompletionOperationId = string; const OPERATION_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/; const WINDOWS_DEVICE_NAMES = new Set([ "CON", "PRN", "AUX", "NUL", ...Array.from({ length: 9 }, (_, index) => `COM${index + 1}`), ...Array.from({ length: 9 }, (_, index) => `LPT${index + 1}`), ]); /** * Validate the wire-safe operation identity used in artifact filenames. * * Operation IDs are generated by this module, but the child reconstructs one * from environment variables. Keeping the grammar here prevents separators, * control characters, and Windows device names from becoming path components. */ export function isValidOperationId(value: unknown): value is CompletionOperationId { if ( typeof value !== "string" || value.length === 0 || value.length > 128 || !OPERATION_ID_PATTERN.test(value) || value.endsWith(".") ) return false; const deviceStem = value.split(".", 1)[0].toUpperCase(); return !WINDOWS_DEVICE_NAMES.has(deviceStem); } /** * Operation identity and its private protocol namespace. Artifact paths locate * protocol data; the operation ID remains the sole logical identity. */ export interface OperationReference { operationId: CompletionOperationId; artifacts: OperationArtifacts; } /** Build one operation reference without repeating namespace assembly. */ export function createOperationReference( operationId: CompletionOperationId, artifacts: OperationArtifacts, ): OperationReference { return { operationId, artifacts }; } /** Allocate a unique identity for one parent runtime. */ export function createParentRuntimeId(): ParentRuntimeId { return `parent-${randomBytes(16).toString("hex")}`; } /** Allocate a unique identity for one Fresh Completion operation. */ export function createCompletionOperationId(): CompletionOperationId { return randomBytes(16).toString("hex"); } const PROCESS_PARENT_RUNTIME_KEY = Symbol.for("pi-subagents/process-parent-runtime-id"); /** * Return the identity for the current process's default parent runtime. * * The value is retained across extension module reloads but is never persisted * as a session or recovery key. Independent ParentRuntime instances can still * inject their own identity through their construction seam. */ export function getProcessParentRuntimeId(): ParentRuntimeId { return retainAcrossReload(PROCESS_PARENT_RUNTIME_KEY, createParentRuntimeId); }