Live code reference

This page is generated from the package runtime TypeScript during the site build. Signatures are source text, not a hand-maintained API copy.

Key components

Important TypeBox schemas

Runtime exports

extensions/plannotator/index.ts

function: piSychPlannotator

export default async function piSychPlannotator(pi: ExtensionAPI): Promise<void>;

extensions/plannotator/runtime.ts

interface: PlannotatorRuntime

export interface PlannotatorRuntime {
    preload(): Promise<unknown>;
    last(ctx: ExtensionCommandContext): ReturnType<typeof startLastMessageAnnotation>;
    file(
        ctx: ExtensionCommandContext,
        path: string,
        content: string,
    ): ReturnType<typeof startFileAnnotation>;
    review(
        ctx: ExtensionCommandContext,
        args: ReturnType<typeof parseCodeReviewArgs>,
    ): ReturnType<typeof startCodeReview>;
}

function: registerPlannotator

export async function registerPlannotator(
    pi: ExtensionAPI,
    runtime: PlannotatorRuntime = defaultRuntime,
): Promise<void>;

extensions/workbench/index.ts

constant: PACKAGE_ROOT

export const PACKAGE_ROOT = resolve(
    process.env.PI_PACKAGE_DIR ?? resolve(import.meta.dirname, "../.."),
);

constant: SUPERVISOR_GUIDANCE

export const SUPERVISOR_GUIDANCE = [
    "Pi Sych is a small mechanical substrate; skills and humans own semantic judgment.",
    "Keep replies concise. Use direct work and read-only retrieval (literature_search; web when active) for small or tightly connected exploration; dispatch for independent context, breadth, specialization, or substantial execution.",
    "Use project_status for mechanical state; changed content is not conceptual drift.",
    `For Pi Sych questions, read ${PACKAGE_ROOT}/README.md and its linked documentation.`,
    "Before dispatch, inspect the available skill catalogue and select only skills valuable for the assignment. dispatch_worker defaults to clean context and 90 seconds. Use trajectory context only when prior conversation materially helps; choose context, model role, and timeout deliberately. Worker modes are not sandboxes.",
    "Treat the configured proposal inbox as human-review proposal state: report its pending count through project_status and read it only when the user requests inbox review.",
].join("\n");

function: formatDispatchWorkerCallSummary

export function formatDispatchWorkerCallSummary(
    args: Pick<
        DispatchRequest,
        | "task"
        | "contextMode"
        | "modelRole"
        | "thinkingLevel"
        | "mode"
        | "skills"
        | "remoteResearch"
        | "timeoutMs"
    >,
);

function: formatDispatchWorkerOutcome

export function formatDispatchWorkerOutcome(outcome: DispatchOutcome);

constant: shouldCompactAt100k

export const shouldCompactAt100k = (enabled: boolean, tokens?: number | null) =>
    enabled && (tokens ?? 0) >= 100_000;

function: configuredSupervisorInstructions

export async function configuredSupervisorInstructions(cwd: string, existing = "");

function: piSychWorkbench

export default async function piSychWorkbench(pi: ExtensionAPI): Promise<void>;

export: re-export

export { MAX_TIMEOUT_MS };

extensions/workbench/src/compaction.ts

interface: Memory

export interface Memory {
    task: string;
    constraints: string[];
    active: string[];
    blockers: string[];
    next: string;
    files: string[];
}

typealias: PromotionTarget

export type PromotionTarget =
    | "project"
    | "agents"
    | "personal-agents"
    | "style"
    | "evidence"
    | "decisions"
    | "todo";

interface: Promotion

export interface Promotion {
    target: PromotionTarget;
    proposal: string;
}

function: validateWorkingMemory

export function validateWorkingMemory(value: unknown): Memory;

function: parseCompactionModelOutput

export function parseCompactionModelOutput(raw: string): {
    workingMemory: Memory;
    promotions: Promotion[];
};

function: renderWorkingMemory

export function renderWorkingMemory(memory: Memory): string;

constant: COMPACTION_FILE_BYTE_LIMIT

export const COMPACTION_FILE_BYTE_LIMIT = 16 * 1024;

constant: COMPACTION_TOTAL_BYTE_LIMIT

export const COMPACTION_TOTAL_BYTE_LIMIT = 48 * 1024;

function: pendingPromotions

export async function pendingPromotions(
    project: Pick<ResolvedProject, "canonical">,
): Promise<number>;

function: filterWorkingMemoryFiles

export async function filterWorkingMemoryFiles(
    project: ResolvedProject,
    snapshotPaths: Set<string>,
    files: string[],
): Promise<string[]>;

function: compactionSnapshot

export async function compactionSnapshot(
    project: ResolvedProject,
    state: Awaited<ReturnType<typeof checkProjectStatus>>,
);

function: buildCompactionPrompt

export function buildCompactionPrompt(
    event: SessionBeforeCompactEvent,
    snapshot: Awaited<ReturnType<typeof compactionSnapshot>>,
    status: Awaited<ReturnType<typeof checkProjectStatus>>,
    inboxPath: string,
);

function: compact

export async function compact(
    event: SessionBeforeCompactEvent,
    ctx: ExtensionContext,
    completeModel = complete,
);

extensions/workbench/src/config-directory.ts

interface: PiSychConfig

export interface PiSychConfig {
    version: 1;
    workerAgentDir: string;
    modelCatalog: string;
    mcporterConfig: string;
    literatureDatabase?: string;
    compaction: { custom: boolean; compactAt100k: boolean };
    review: { mode: "plannotator" | "manual" };
}

constant: DEFAULT_CONFIG

export const DEFAULT_CONFIG = {
    version: 1,
    workerAgentDir: "worker-agent",
    modelCatalog: "models.json",
    mcporterConfig: "mcp/mcporter.json",
    compaction: { custom: true, compactAt100k: false },
    review: { mode: "plannotator" },
} satisfies PiSychConfig;

interface: ConfigDirectoryOptions

export interface ConfigDirectoryOptions {
    projectRoot?: string;
    env?: NodeJS.ProcessEnv;
    home?: string;
    exists?: (path: string) => boolean;
    configDirectory?: string;
}

function: piConfigRoot

export function piConfigRoot({
    projectRoot,
    env = process.env,
    home = homedir(),
    exists = existsSync,
}: ConfigDirectoryOptions = {}): string;

constant: piSychConfigDirectory

export const piSychConfigDirectory = (options: ConfigDirectoryOptions = {}) =>
    options.configDirectory ?? resolve(piConfigRoot(options), "pi-sych");

constant: piSkillDirectory

export const piSkillDirectory = (options: ConfigDirectoryOptions = {}) =>
    resolve(piConfigRoot(options), "skills");

function: loadPiSychConfig

export function loadPiSychConfig(options: ConfigDirectoryOptions = {}): PiSychConfig;

function: piSychConfigPath

export function piSychConfigPath(
    key: "workerAgentDir" | "modelCatalog" | "mcporterConfig",
    options: ConfigDirectoryOptions = {},
): string;

function: ensurePiSychConfig

export async function ensurePiSychConfig(options: ConfigDirectoryOptions = {}): Promise<string>;

extensions/workbench/src/literature-search.ts

interface: LiteratureResult

export interface LiteratureResult {
    metadata: {
        title: unknown;
        itemType: string | null;
        creators: unknown;
        year: unknown;
        doi: unknown;
    };
    snippet: unknown;
    score: unknown;
    sourcePath: string;
}

function: literatureDatabasePath

export function literatureDatabasePath(projectRoot: string, configDirectory?: string): string;

function: searchLiterature

export function searchLiterature(
    projectRoot: string,
    queryText: string,
    limit = 10,
    configDirectory?: string,
): LiteratureResult[];

function: registerLiteratureSearch

export function registerLiteratureSearch(
    pi: ExtensionAPI,
    configDirectory?: string,
    resolveProjectRoot: (cwd: string) => string | Promise<string> = (cwd) => cwd,
): void;

extensions/workbench/src/mcporter.ts

constant: remoteResearchExtensionPaths

export const remoteResearchExtensionPaths = (
    enabled: boolean,
    resolveExtension = () => require.resolve("pi-mcporter/dist/index.js"),
) => {
    if (!enabled) return [];
    try {
        return [resolveExtension()];
    } catch {
        throw new Error("pi-mcporter is not installed; run npm install pi-mcporter@latest");
    }
};

interface: McporterDiagnostic

export interface McporterDiagnostic {
    available: boolean;
    configPath: string;
    configExists: boolean;
    servers: string[];
    configError?: string;
}

constant: mcporterConfigPath

export const mcporterConfigPath = (projectRoot?: string) =>
    piSychConfigPath("mcporterConfig", projectRoot ? { projectRoot } : {});

function: inspectMcporter

export function inspectMcporter(configPath = mcporterConfigPath()): McporterDiagnostic;

constant: formatMcporterDiagnostic

export const formatMcporterDiagnostic = (value: McporterDiagnostic) =>
    [
        "Pi Sych MCPorter diagnostics",
        `extension: ${value.available ? "available" : "unavailable"}`,
        `config: ${value.configPath} (${value.configExists ? "present" : "missing"})`,
        `servers: ${value.servers.join(", ") || "none"}`,
        ...(value.configError ? [`config error: ${value.configError}`] : []),
    ].join("\n");

extensions/workbench/src/model-catalog.ts

interface: ModelEntry

export interface ModelEntry {
    model: string;
    cost?: string;
    notes?: string;
}

interface: ModelCatalog

export interface ModelCatalog {
    default: string;
    models: Record<string, ModelEntry>;
}

function: parseModelCatalog

export function parseModelCatalog(value: unknown): ModelCatalog;

function: modelCatalogPath

export function modelCatalogPath(
    projectRoot?: string,
    env: NodeJS.ProcessEnv = process.env,
): string;

function: loadModelCatalog

export function loadModelCatalog(
    projectRoot?: string,
    env: NodeJS.ProcessEnv = process.env,
): ModelCatalog;

function: loadOptionalModelCatalog

export function loadOptionalModelCatalog(
    projectRoot?: string,
    env: NodeJS.ProcessEnv = process.env,
): ModelCatalog | undefined;

extensions/workbench/src/pew-pew.ts

function: enabledPewPewExtension

export async function enabledPewPewExtension(
    tools: readonly ToolInfo[],
    activeTools: readonly string[],
): Promise<string | undefined>;

extensions/workbench/src/plannotator.ts

interface: AnnotationDecision

export interface AnnotationDecision {
    feedback?: string;
    exit?: boolean;
}

interface: CodeReviewDecision

export interface CodeReviewDecision extends AnnotationDecision {
    approved?: boolean;
}

interface: AnnotationSession

export interface AnnotationSession {
    url: string;
    waitForDecision(): Promise<AnnotationDecision>;
}

interface: CodeReviewSession

export interface CodeReviewSession {
    url: string;
    waitForDecision(): Promise<CodeReviewDecision>;
}

interface: CodeReviewRequest

export interface CodeReviewRequest {
    prUrl?: string;
    vcsType?: "git" | "gitbutler";
    useLocal?: boolean;
}

constant: plannotatorUnavailable

export const plannotatorUnavailable = (reason?: string) =>
    new Error(
        `Plannotator unavailable; ensure its integration is installed${reason ? ` and compatible: ${reason}` : ""}`,
    );

function: loadPlannotator

export async function loadPlannotator(): Promise<Plannotator>;

constant: startFileAnnotation

export const startFileAnnotation = async (ctx: ExtensionContext, path: string, content: string) =>
    (await loadPlannotator()).startMarkdownAnnotationSession(ctx, path, content, "annotate");

constant: startLastMessageAnnotation

export const startLastMessageAnnotation = async (ctx: ExtensionContext) => {
    const api = await loadPlannotator(),
        text = api.getLastAssistantMessageText(ctx);
    return text ? api.startLastMessageAnnotationSession(ctx, text) : undefined;
};

function: parseCodeReviewArgs

export function parseCodeReviewArgs(input = ""): CodeReviewRequest;

constant: startCodeReview

export const startCodeReview = async (ctx: ExtensionContext, options: CodeReviewRequest = {}) =>
    (await loadPlannotator()).startCodeReviewBrowserSession(ctx, options);

extensions/workbench/src/project-files.ts

constant: CANONICAL_FILES

export const CANONICAL_FILES = [
    "project",
    "agents",
    "style",
    "evidence",
    "decisions",
    "todo",
    "inbox",
] as const;

typealias: CanonicalFile

export type CanonicalFile = (typeof CANONICAL_FILES)[number];

constant: DEFAULT_CANONICAL_PATHS

export const DEFAULT_CANONICAL_PATHS = {
    project: "PROJECT.md",
    agents: "AGENTS.md",
    style: "STYLE.md",
    evidence: "EVIDENCE.md",
    decisions: "DECISIONS.md",
    todo: "TODO.md",
    inbox: "INBOX.md",
} satisfies Record<CanonicalFile, string>;

interface: SyncManifest

export interface SyncManifest {
    version: 2;
    projectRoot?: string;
    canonical?: Partial<Record<CanonicalFile, string>>;
    confirmedAt: string;
    artifacts: unknown[];
    [key: string]: unknown;
}

interface: ResolvedProject

export interface ResolvedProject {
    cwd: string;
    workspaceRoot: string;
    projectRoot: string;
    syncPath: string;
    manifest?: SyncManifest;
    syncError?: string;
    canonical: Record<CanonicalFile, string>;
}

interface: ProjectValidation

export interface ProjectValidation {
    valid: boolean;
    errors: string[];
    headings: string[];
}

constant: showPath

export const showPath = (root: string, path: string) => {
    const display = relative(root, path);
    return display && display !== ".." && !display.startsWith(`..${sep}`) && !isAbsolute(display)
        ? display
        : path;
};

function: parseSyncManifest

export function parseSyncManifest(value: string): SyncManifest;

constant: formatSyncManifest

export const formatSyncManifest = (manifest: SyncManifest) =>
    `${JSON.stringify(manifest, null, 2)}\n`;

function: resolveProject

export async function resolveProject(startPath: string): Promise<ResolvedProject>;

function: validateProjectMarkdown

export function validateProjectMarkdown(markdown: string): ProjectValidation;

constant: readAndValidateProject

export const readAndValidateProject = async (path: string) =>
    validateProjectMarkdown(await readFile(path, "utf8"));

function: resolveProjectPath

export function resolveProjectPath(root: string, path: string);

function: resolveConfiguredPath

export async function resolveConfiguredPath(path: string);

function: resolveExistingProjectPath

export async function resolveExistingProjectPath(root: string, path: string);

function: resolveExistingProjectContextPath

export async function resolveExistingProjectContextPath(root: string, path: string);

function: writeAtomicFile

export async function writeAtomicFile(path: string, content: string);

extensions/workbench/src/project-status.ts

constant: PROJECT_STATUSES

export const PROJECT_STATUSES = [
    "current",
    "stale",
    "needs-review",
    "conflicted",
    "missing",
] as const;

typealias: ProjectStatus

export type ProjectStatus = (typeof PROJECT_STATUSES)[number];

typealias: Dependency

export type Dependency = string | { path: string; reason: string };

interface: ProjectArtifact

export interface ProjectArtifact {
    path: string;
    fingerprint: string;
    status: ProjectStatus;
    role?: string;
    authoritativeFor?: string[];
    updateFrom?: Dependency[];
    dependsOn?: Dependency[];
    acknowledgement?: { at: string; reason: string };
    [key: string]: unknown;
}

interface: ProjectStatusManifest

export interface ProjectStatusManifest extends Omit<SyncManifest, "artifacts"> {
    artifacts: ProjectArtifact[];
}

typealias: Observation

export type Observation =
    | { state: "current" | "changed"; fingerprint: string }
    | { state: "missing" }
    | { state: "error"; message: string };

interface: CheckedArtifact

export interface CheckedArtifact extends ProjectArtifact {
    observation: Observation;
}

interface: ProjectStatusCheck

export interface ProjectStatusCheck {
    projectRoot: string;
    syncPath: string;
    manifest?: ProjectStatusManifest;
    syncError?: string;
    artifacts: CheckedArtifact[];
    changed: string[];
    missing: string[];
    errors: Array<{ path: string; message: string }>;
    impacted: Array<{ path: string; from: string[]; direct: boolean }>;
    cycles: string[][];
    missingCore: string[];
    projectErrors: string[];
}

function: parseProjectStatusManifest

export function parseProjectStatusManifest(value: string | SyncManifest): ProjectStatusManifest;

function: fingerprintFile

export async function fingerprintFile(path: string);

function: checkProjectStatus

export async function checkProjectStatus(
    startPath: string,
    project?: ResolvedProject,
): Promise<ProjectStatusCheck>;

function: formatProjectStatusCheck

export function formatProjectStatusCheck(
    state: ProjectStatusCheck,
    pending = 0,
    inboxPath = "INBOX.md",
): string;

function: verifyAcknowledgementObservation

export async function verifyAcknowledgementObservation(
    state: ProjectStatusCheck,
    selected: Set<string>,
);

function: acknowledgeProjectStatus

export async function acknowledgeProjectStatus(
    startPath: string,
    files: string[],
    reason: string,
    now = new Date(),
);

extensions/workbench/src/validation.ts

constant: nonEmptyString

export const nonEmptyString = (value: unknown, label: string) => {
    if (typeof value !== "string" || !value.trim())
        throw new Error(`${label} must be a non-empty string`);
    return value.trim();
};

constant: stringArray

export const stringArray = (value: unknown, label: string) => {
    if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
        throw new Error(`${label} must be an array of strings`);
    return value.map((item) => item.trim()).filter(Boolean);
};

extensions/workbench/src/worker-engine.ts

constant: WORKER_MODES

export const WORKER_MODES = ["read-only", "edit", "full-host"] as const;

typealias: WorkerMode

export type WorkerMode = (typeof WORKER_MODES)[number];

constant: CONTEXT_MODES

export const CONTEXT_MODES = ["clean", "trajectory"] as const;

typealias: ContextMode

export type ContextMode = (typeof CONTEXT_MODES)[number];

constant: THINKING_LEVELS

export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;

constant: DEFAULT_TIMEOUT_MS

export const DEFAULT_TIMEOUT_MS = 90_000;

constant: MAX_TIMEOUT_MS

export const MAX_TIMEOUT_MS = 30 * 60_000;

constant: PI_SYCH_PACKAGE_ROOT

export const PI_SYCH_PACKAGE_ROOT = resolve(
    process.env.PI_PACKAGE_DIR ?? resolve(import.meta.dirname, "../../.."),
);

interface: ContextFile

export interface ContextFile {
    path: string;
    purpose: string;
}

typealias: DispatchRequest

export type DispatchRequest = Static<typeof dispatchSchema>;

typealias: WorkerResult

export type WorkerResult = Static<typeof workerResultSchema>;

interface: WorkerLaunchSpec

export interface WorkerLaunchSpec {
    id: string;
    request: DispatchRequest;
    workerAgentDir: string;
    piSychConfigDirectory?: string;
    resultPath: string;
    projectRoot: string;
    model: string;
    prompt: string;
    packageRoot: string;
    extraExtensionPaths: string[];
    webExtensionPath?: string;
    sessionPath?: string;
    onActivity?: (activity: readonly string[]) => void;
    signal?: AbortSignal;
}

interface: WorkerLaunchOutcome

export interface WorkerLaunchOutcome {
    exitCode: number | null;
    stderr: string;
    classification?: "cancelled" | "timeout" | "spawn-failure";
    terminationSignal?: NodeJS.Signals | null;
}

interface: DispatchOutcome

export interface DispatchOutcome {
    id: string;
    model: string;
    timeoutMs: number;
    launch: WorkerLaunchOutcome;
    result?: WorkerResult;
    error?: string;
}

typealias: WorkerLauncher

export type WorkerLauncher = (spec: WorkerLaunchSpec) => Promise<WorkerLaunchOutcome>;

constant: dispatchSchema

export const dispatchSchema = Type.Object({
    task: Type.String({ description: "One explicit bounded assignment for the worker" }),
    mode: StringEnum(WORKER_MODES, { description: "Visible worker tool capability" }),
    expectedOutput: Type.String({ description: "Required terminal result or file deliverable" }),
    contextMode: Type.Optional(
        StringEnum(CONTEXT_MODES, {
            description: "Conversation context: clean by default, or persisted pre-dispatch trajectory",
        }),
    ),
    contextFiles: Type.Array(
        Type.Object({
            path: Type.String({ description: "Existing context file; relative paths are project-local" }),
            purpose: Type.String({ description: "Why the worker needs this file" }),
        }),
        { description: "Smallest complete explicit file packet" },
    ),
    skills: Type.Optional(
        Type.Array(Type.String(), {
            description: "Exact selectors chosen after inspecting the available skill catalogue",
        }),
    ),
    modelRole: Type.Optional(Type.String({ description: "Exact configured worker model role" })),
    thinkingLevel: Type.Optional(
        StringEnum(THINKING_LEVELS, {
            description: "Pi thinking level; omission leaves the selected model's default intact",
        }),
    ),
    remoteResearch: Type.Optional(
        Type.Boolean({ description: "Expose configured remote-research integrations for this task" }),
    ),
    timeoutMs: Type.Optional(
        Type.Integer({
            minimum: 1,
            maximum: MAX_TIMEOUT_MS,
            description: "Bounded runtime in milliseconds; defaults to 90000",
        }),
    ),
});

constant: toolsForRequest

export const toolsForRequest = (
    request: Pick<DispatchRequest, "mode" | "remoteResearch" | "skills">,
    webEnabled = false,
) => [
    ...MODE_TOOLS[request.mode],
    ...(request.skills?.includes("research") ? ["literature_search"] : []),
    ...(request.remoteResearch ? ["mcporter"] : []),
    ...(request.remoteResearch && webEnabled ? ["web"] : []),
];

function: skillPaths

export function skillPaths(
    selectors: string[] = [],
    projectRoot: string,
    packageRoot: string,
    userRoot?: string,
): string[];

constant: modelFor

export const modelFor = (catalog: ModelCatalog, role?: string) => {
    const key = role ?? catalog.default,
        model = catalog.models[key]?.model;
    if (!model) throw new Error(`Unknown worker model: ${key}`);
    return model;
};

function: taskPrompt

export function taskPrompt(spec: WorkerLaunchSpec, files: ContextFile[]);

function: prepareTrajectorySession

export async function prepareTrajectorySession(options: {
    manager: SupervisorSession;
    toolCallId: string;
    runtimePath: string;
}): Promise<string>;

function: writeImmutableResult

export async function writeImmutableResult(path: string, result: WorkerResult);

constant: workerResultSchema

export const workerResultSchema = Type.Object({
    status: StringEnum(["complete", "partial", "failed"] as const),
    summary: Type.String(),
    files: Type.Array(Type.String()),
    limitations: Type.Array(Type.String()),
});

function: validateWorkerResult

export function validateWorkerResult(value: unknown): WorkerResult;

function: launchPiWorker

export async function launchPiWorker(
    spec: WorkerLaunchSpec,
    spawnWorker = spawn,
): Promise<WorkerLaunchOutcome>;

function: dispatchWorker

export async function dispatchWorker(options: {
    project: ResolvedProject;
    workerAgentDir: string;
    piSychConfigDirectory?: string;
    request: DispatchRequest;
    catalog: ModelCatalog;
    packageRoot?: string;
    extraExtensionPaths?: string[];
    webExtensionPath?: string;
    trajectory?: { manager: SupervisorSession; toolCallId: string };
    launcher?: WorkerLauncher;
    onActivity?: (activity: readonly string[]) => void;
    signal?: AbortSignal;
}): Promise<DispatchOutcome>;

extensions/worker/index.ts

function: piSychWorker

export default function piSychWorker(pi: ExtensionAPI): void;