/** * openlore Pi extension — src/pi/extension.ts * * Compiled to dist/pi/extension.js and declared in package.json "pi" field so * `pi install npm:openlore` drops it into the Pi extension registry automatically. * * Two halves: * C — context injection (before_agent_start): model starts grounded with the * architecture digest + spec index + task-grounded orient call, so weak * tool-callers benefit even without calling a tool. * B — native tools (registerTool): the substrate surface for on-demand structural * queries — NAV_TOOLS spans navigate + change + remember + verify + governance, * each round-tripping to the warm daemon via fetch. Every tool is registered, but a * session starts with only the lean set active (the MCP `substrate` preset, the same * default Claude Code gets); openlore_activate_tools turns on the task groups in * PI_TOOL_GROUPS. `pi.toolSurface: "all"` keeps every tool active. * (change: add-pi-lean-tool-surface) * * Uses ctx.mode (0.78.1+): full injection in tui/rpc (interactive), none in * json/print (one-shot). rpc = headless interactive over stdin/stdout (IDE, * custom UI) — same injection needs as tui. * * Config onboarding: runs on first session when .openlore/config.json is absent; * also available anytime via the openlore_configure tool. * * change: harden-pi-config-and-daemon-fidelity */ import type { AgentToolResult, ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent'; import { type TObject } from 'typebox'; import type { ContextInjectionConfig } from '../types/index.js'; import { type HealthResult } from '../api/health.js'; interface OpenLoreConfig { [key: string]: unknown; version: string; projectType: string; openspecPath: string; analysis: { maxFiles: number; includePatterns: string[]; excludePatterns: string[]; }; generation: { [key: string]: unknown; provider?: string; model?: string; openaiCompatBaseUrl?: string; skipSslVerify?: boolean; domains?: string | string[]; }; embedding?: { [key: string]: unknown; baseUrl: string; model: string; apiKey?: string; skipSslVerify?: boolean; }; /** Task-scoped context injection settings (gate + token budget + opt-out). */ contextInjection?: ContextInjectionConfig; createdAt: string; lastRun: string | null; } type ExistingOpenLoreConfig = Record; export type ExistingConfigLoad = { state: 'absent'; } | { state: 'invalid'; detail: string; } | { state: 'valid'; config: ExistingOpenLoreConfig; }; /** Treat a config as absent unless it has the minimum viable fields. */ export declare function isUsableConfig(raw: unknown): raw is OpenLoreConfig; export declare function readConfig(cwd: string): Promise; /** * Read just the `contextInjection` block, independent of `isUsableConfig`. * The injection opt-out must work even before an LLM provider is configured — * `readConfig` returns null until `generation.provider` is set (a headless/rpc * session may never run the wizard), which would silently drop `mode: "off"`. * Mirrors the CLI path, which reads config unconditionally. */ export declare function readContextInjection(cwd: string): Promise; /** * May this extension spawn a daemon of its own? * * Read like `readContextInjection` and NOT through `readConfig`: the opt-out must work before an * LLM provider is configured, and `readConfig` returns null until `generation.provider` is set — * which would silently restore spawn authority the operator revoked. The environment variable wins * over the config key, because it is the host's per-process statement about who owns the process. * Only the exact `false` disables spawning; an absent or malformed key leaves today's default. */ export declare function piMaySpawnDaemon(cwd: string): Promise; export type PiToolSurface = 'lean' | 'all'; /** * Which OpenLore tools a Pi session starts with. Read like `piMaySpawnDaemon`, so it works before * a provider is configured. Only the exact string `"all"` widens the surface; an absent, * malformed, or unreadable value keeps the lean default. */ export declare function piToolSurface(cwd: string): Promise; /** * Build the `/v1/models` URL for a provider base URL, tolerating a trailing * slash and an already-present `/v1` segment (e.g. https://api.mistral.ai/v1/). */ export declare function modelsUrl(baseUrl: string): string; /** Strip the trailing " *" current-value marker added to select-list entries. */ export declare function stripMarker(label: string): string; export declare function loadExistingConfig(cwd: string): Promise; export declare function runConfigWizard(ctx: ExtensionContext, existing?: ExistingOpenLoreConfig | null, hooks?: { afterAnalyze?: (ctx: ExtensionContext) => Promise; }): Promise; interface Daemon { baseUrl: string; token?: string; incompatibility?: string; } export declare const PI_ORIENT_TIMEOUT_MS = 4000; export declare const PI_SPEC_INDEX_MAX_DOMAINS = 50; export declare const PI_DAEMON_PRESET = "full"; /** * Auditable mapping from the Generate/Repair protocol observations to the * existing daemon primitives used by Pi's task entry points. Keep this list * closed in tests: adding a protocol observation must either wire it here or * add a documented exclusion below. */ export declare const PI_SPEC_WORKFLOW_OBSERVATIONS: { readonly generation: { readonly domainEvidence: "prepare_spec_generation"; readonly domainBehavior: "prepare_spec_generation"; readonly specValidation: "prepare_spec_generation"; }; readonly repair: { readonly domainEvidence: "prepare_spec_repair"; readonly existingSpec: "prepare_spec_repair"; readonly coveredFunction: "prepare_spec_repair"; readonly uncoveredFunction: "prepare_spec_repair"; readonly staleMapping: "prepare_spec_repair"; readonly orphanRequirement: "prepare_spec_repair"; readonly structuralChange: "prepare_spec_repair"; readonly mappingCoverage: "prepare_spec_repair"; readonly specValidation: "prepare_spec_repair"; readonly domainBehavior: "prepare_spec_repair"; }; }; export declare const PI_SPEC_WORKFLOW_EXCLUSIONS: Readonly>; export declare function missingDaemonTools(available: readonly string[], required: readonly string[]): string[]; /** Launch the packaged CLI without a command shell so repository paths stay data. */ export declare function piDaemonSpawnCommand(cwd: string): { command: string; args: string[]; }; export type EnsureDaemonResult = { daemon: Daemon; failure?: undefined; } | { daemon: null; failure: string; failureKind: 'draining' | 'launch' | 'preparation' | 'early-exit' | 'health-timeout' | 'spawn-disabled'; }; export declare function shouldNegativeCacheDaemonFailure(kind: Exclude['failureKind']): boolean; export declare function ensureDaemonResult(cwd: string, options?: { timeoutMs?: number; launch?: { command: string; args: string[]; }; }): Promise; export declare function ensureDaemon(cwd: string): Promise; export declare function callTool(daemon: Daemon, name: string, args: Record, cwd: string, signal?: AbortSignal): Promise; export declare class PiDaemonConnectionError extends Error { constructor(message: string); } /** Bound a best-effort Pi operation without abandoning useful background work. */ export declare function awaitWithSignal(work: Promise, signal: AbortSignal): Promise; export declare function isUsableDaemon(daemon: Daemon): boolean; /** The Pi footer status key this extension owns. */ export declare const PI_STATUS_KEY = "openlore"; /** What the extension last learned about the daemon for one working tree. */ export type PiDaemonView = 'connecting' | 'usable' | 'incompatible' | 'spawn-disabled' | 'unavailable'; /** Map a daemon resolution to the view the status reports. @internal */ export declare function piDaemonView(result: EnsureDaemonResult): PiDaemonView; export interface PiStatusFacts { daemon: PiDaemonView; /** Absent when the health read failed: readiness is then unknown, never assumed. */ health?: Pick; } /** * Render the footer status (spec: PiStatusReportsFunctionalReadiness). Precedence: connecting, then * an index that is not ready — no daemon can serve an absent index, so that is the actionable * condition — then the daemon, then a watcher the daemon reported stopped. "ready" needs both a * ready index and a usable daemon. @internal */ export declare function formatPiStatus(facts: PiStatusFacts): string; /** * The cache key for one health read: the stat of every required artifact, the ownership lock, and * the daemon view. The full read parses every artifact (tens of MB on a large repository), so it * reruns only when one of these moves. @internal */ export declare function piHealthCacheKey(cwd: string, daemon: PiDaemonView): Promise; type PiHealthReader = (cwd: string) => Promise; type PiWatcherReader = (cwd: string) => Promise; export declare function readSpecIndex(cwd: string): Promise; interface NavToolSpec { name: string; label: string; description: string; guideline: string; parameters: TObject; } export declare const NAV_TOOLS: NavToolSpec[]; export declare const PI_TOOL_SNIPPETS: Record; export declare const PI_LEAN_TOOLS: readonly string[]; export declare const PI_TOOL_GROUPS: Record; export declare const PI_ACTIVATOR_TOOL = "openlore_activate_tools"; /** Every tool name the extension registers, with the `openlore_` prefix. */ export declare function piRegisteredToolNames(): string[]; /** * The active set a session starts with. Non-OpenLore tools keep their state; OpenLore * tools the host had already turned off (`hostExcluded`) stay off. */ export declare function piSessionActiveTools(surface: PiToolSurface, active: readonly string[], hostExcluded: ReadonlySet): string[]; export type PiActivationPlan = { ok: false; error: string; } | { ok: true; nextActive: string[]; activated: string[]; alreadyActive: string[]; hostExcluded: string[]; }; /** * Resolve activator names (a group, or a tool with or without the `openlore_` prefix) into the * next active set. All-or-nothing: one unknown name activates nothing. A lean tool name is * valid and changes nothing, because it is already on. */ export declare function planPiToolActivation(names: readonly string[], active: readonly string[], hostExcluded: ReadonlySet): PiActivationPlan; /** The activator description: every group with the tools it turns on. */ export declare function piActivatorDescription(): string; /** * Reviewed ceilings for the standing context OpenLore tools add to the Pi prompt, estimated by * `estimatePiStandingTokens`. Each entry records its measured baseline and bounded headroom, so * raising a budget changes both a number and its rationale (the STANDING_CONTEXT_BUDGETS pattern). */ export declare const PI_STANDING_CONTEXT_BUDGETS: Record; /** Deterministic estimate (characters / 4) of what the given registered tools add to the prompt. */ export declare function estimatePiStandingTokens(tools: ReadonlyArray<{ description: string; promptSnippet?: string; promptGuidelines?: readonly string[]; parameters: unknown; }>): number; export declare const PI_EXCLUDED_CONCLUSION_TOOLS: Record; /** * Forward a spec-workflow composite envelope WITHOUT generic clipping. * * The daemon already packed the page to `PI_COMPOSITE_RESPONSE_BYTES`, so a * valid envelope fits by construction and its completeness receipt is * meaningful. Clipping it here would silently invalidate that receipt — the * exact failure this change removes. A page that is somehow still oversized is a * transport fault, so it returns a typed error instead of clipped JSON the model * would try to parse as evidence. */ export declare function compositeToolResult(result: unknown): AgentToolResult; /** * One-line summary of a tool call's arguments for renderCall — the descriptive * arg, quoted (e.g. orient "add rate limiting"). Pathfinding reads as `a → b`. * Returns '' when there's no descriptive arg (e.g. get_health_map) so the caller * shows the bare tool title. */ export declare function formatCallArgs(args: Record): string; /** * Turn a structured tool result into readable text. Strings pass through; * `{ error }` becomes a warning line; objects render as labelled sections with * arrays shown as bounded bullet lists. `toolName` selects per-tool skips so an * ambient tool (orient) can hide enrichment a deliberate one (analyze_impact) * keeps. Resilient to schema drift — unknown shapes degrade to key/value lines. */ export declare function formatToolResult(result: unknown, toolName?: string): string; /** Bounded runtime overrides for the extension. @internal */ export interface PiExtensionRuntime { orientTimeoutMs?: number; /** Replaces the functional-readiness read behind the footer status. */ readHealth?: PiHealthReader; /** Replaces the watcher-only probe used on a cached health read. */ readWatcher?: PiWatcherReader; /** Replaces daemon discovery/spawn. */ resolveDaemon?: (cwd: string) => Promise; } /** Build an extension registration function with bounded runtime overrides. @internal */ export declare function createPiExtension(runtime?: PiExtensionRuntime): (pi: ExtensionAPI) => void; /** Pi package entry point. */ export default function openlore(pi: ExtensionAPI): void; export declare const installPaths: { project: (cwd: string) => string; global: () => string; }; export {}; //# sourceMappingURL=extension.d.ts.map