/** * Runtime build & reload: the session's tool-registry assembly and the self-modification-safe * extension reload path. Owns building the base tool definitions, wrapping them into the live tool * registry (`_refreshToolRegistry`), constructing the {@link ExtensionRunner} for a rebuilt runtime * (`_buildRuntime`), and the repo's self-modification safety crown jewel: the preflight-guarded * `reload()` with its snapshot / doctor / commit-or-rollback sequence, plus the single-extension * live load/unload/reconcile operations that rebuild the runtime in place. * * Extracted verbatim from agent-session.ts (god-file decomposition). Owns ONLY the tool-registry * state — the base tool definitions, the wrapped tool registry, the definition registry, and the * per-tool prompt snippet/guideline maps. Everything else the build & reload touch — the live * {@link ExtensionRunner}, the agent's tool/system-prompt state, the base system prompt, the * resource loader, the session/settings managers, the model registry, the profile tool filter and * its warning sinks, the requested-active-tool-names request, the memory subsystem, and the many * host callbacks the rebuilt tools/runtime are wired to — is reached through narrow deps accessors * that read and write the SAME storage the host owns today. * * Snapshot-ownership boundary (deliberate, load-bearing for reload safety): the reload snapshot * spans state owned by OTHER collaborators — the extension runner and its ref, `agent.state.tools` * / `agent.state.systemPrompt`, and `_baseSystemPrompt`. Those are captured and restored through * {@link RuntimeBuilderDeps} get/set accessors so save/restore mutate exactly the host/agent fields * they mutated before extraction; only the five tool-registry maps are captured by direct field * reference here (this builder owns them). The `_extensionRunnerRef.current` update folds into * {@link RuntimeBuilderDeps.setExtensionRunner} (the same field-then-ref pattern the host used at * every assignment site), so no observer runs between the two writes. * * Host-binding boundary (deliberate): `_bindExtensionCore` — which exposes the SESSION's own public * surface (sendMessage, setModel, compact, abort, reload, …) to the extension runner — stays * host-side and is invoked from {@link buildRuntime} via {@link RuntimeBuilderDeps.bindExtensionCore}; * it is host identity, not build logic, and moving it would only re-export ~30 host methods through * deps to hand them straight back. The host keeps one-line delegations for the public reload / * load / unload / reconcile API and for `getAllTools` / `getToolDefinition`, and a private * `_refreshToolRegistry` delegation for its internal callers. */ import type { Agent, AgentContext, AgentMessage, AgentTool } from "@caupulican/pi-agent-core"; import type { SessionManager } from "@caupulican/pi-agent-core/node"; import type { Api, Model, Usage } from "@caupulican/pi-ai"; import type { IsolatedCompletionOptions, IsolatedCompletionResult, WorkerDelegationRunOutcome } from "./agent-session-contracts.ts"; import type { CapabilityEnvelope, WorkerClaim } from "./autonomy/contracts.ts"; import type { LaneRecord } from "./autonomy/lane-tracker.ts"; import type { ArtifactStore } from "./context/context-artifacts.ts"; import type { MemoryPromptInclusionReport, MemoryRetrievalDiagnostics } from "./context/memory-diagnostics.ts"; import type { ContextGcReport } from "./context-gc.ts"; import type { WorkerAgentControlPort } from "./delegation/worker-agent-control.ts"; import type { WorkerDelegationRequest } from "./delegation/worker-delegation-request.ts"; import type { ExtensionImportAuthority } from "./extension-import-authority.ts"; import { type ContextUsage, type Extension, type ExtensionCommandContextActions, type ExtensionErrorListener, ExtensionRunner, type ExtensionUIContext, type ShutdownHandler, type ToolDefinition, type ToolInfo } from "./extensions/index.ts"; import type { GoalStateRevision } from "./goals/goal-lifecycle.ts"; import type { GoalState } from "./goals/goal-state.ts"; import type { OpenTaskStepRef } from "./goals/goal-tool-core.ts"; import type { MemoryManager } from "./memory/memory-manager.ts"; import type { MemoryControllerReloadSnapshot } from "./memory-controller.ts"; import type { LaneWorkerRefusal } from "./model-capability.ts"; import type { ModelRegistry } from "./model-registry.ts"; import type { OrchestrationProfile, WorkerResultContract } from "./orchestration/contracts.ts"; import type { TaskProfileWriterPort } from "./orchestration/task-profile-writer.ts"; import { type PipelineRun } from "./pipelines/types.ts"; import type { ProfileFilterReloadSnapshot } from "./profile-filter-controller.ts"; import type { ModelFitnessReport } from "./research/model-fitness.ts"; import type { ResourceLoader } from "./resource-loader.ts"; import { CredentialManager } from "./secrets/credential-manager.ts"; import type { SessionImageStore } from "./session-image-store.ts"; import { type ResourceProfileFilterSettings, type SettingsManager } from "./settings-manager.ts"; import type { SkillVaultController } from "./skill-vault.ts"; import type { TaskStepsState } from "./tasks/task-state.ts"; import { type GoalToolInput } from "./tools/goal.ts"; import { type ToolTaskDependencies } from "./tools/tool-task.ts"; /** * Is `toolCallId` a real, answered tool call on `sessionManager`'s active branch? A * toolCallId is "real" iff a toolResult message on that branch responded to it -- the same * toolResult/toolCallId match `context-pipeline.ts`'s `_buildSessionEntryIdLookup` uses, and * branch-scoped (via `getBranch()`) so a sibling branch's tool calls never count. Exported (pure, * no `this`) so the goal tool's `hasToolCallId` wiring below is directly testable against a real * `SessionManager` without constructing the whole `RuntimeBuilder`. */ export declare function hasAnsweredToolCallOnBranch(sessionManager: SessionManager, toolCallId: string): boolean; /** * Goal-tool adapter over the canonical open-task projection. Exported (pure, no `this`) so the * production `getOpenTaskSteps` wiring below remains directly testable without another filter. */ export declare function deriveOpenTaskStepRefs(taskStepsState: TaskStepsState | undefined): OpenTaskStepRef[]; /** Bind goal-selected requirement identity as runtime metadata, outside the model-facing delegate schema. */ export declare function createGoalWorkerDelegationRequest(args: { requirementId: string; instructions: string; }): WorkerDelegationRequest; export interface RuntimeBuilderDeps { /** Live agent — the snapshot/doctor read and restore its `state.tools` / `state.systemPrompt`. */ getAgent(): Agent; /** Workspace root, passed to the tool-definition factory and the extension runner. */ getCwd(): string; /** Per-agent persistent shell session key; stable across reloads so the shell survives them. */ getShellSessionKey(): string; /** Agent state root, including the host-keyed fitness store. */ getAgentDir(): string; /** Session log, passed to the extension runner. */ getSessionManager(): SessionManager; /** Tool/shell/toolkit/resource settings + reload target (settingsManager.reload()). */ getSettingsManager(): SettingsManager; /** Model registry, passed to the extension runner and profile model re-resolution. */ getModelRegistry(): ModelRegistry; /** Session-scoped provider/model quota exhaustion guard. */ isModelExhausted(model: Model): boolean; /** Extension/skill/prompt/theme discovery + the reload/commit/rollback generation swap. */ getResourceLoader(): ResourceLoader; /** Host-owned transient skill lifecycle shared by tool execution and context projection. */ getSkillVault(): SkillVaultController; /** Live extension runner (host-owned; pervasive). */ getExtensionRunner(): ExtensionRunner; /** Store the extension runner AND update the Agent's mutable `_extensionRunnerRef.current` (both together). */ setExtensionRunner(runner: ExtensionRunner): void; /** Base (extension-free) system prompt; captured/restored by the reload snapshot. */ getBaseSystemPrompt(): string; setBaseSystemPrompt(prompt: string): void; /** SDK-provided plain tools, synthesized into the registry alongside built-ins. */ getCustomTools(): ToolDefinition[]; /** Optional plain-tool override; when set, the built-in factory + core diagnostics are skipped. */ getBaseToolsOverride(): Record | undefined; /** Pre-filter tool REQUEST (never the capability/profile-filtered active set) preserved across a rebuild. */ getRequestedActiveToolNames(): string[] | undefined; setRequestedActiveToolNames(names: string[] | undefined): void; /** Resource-profile tool allow/block filter + the raw allow/exclude sets applied during registry build. */ getToolProfileFilter(): Required | undefined; setToolProfileFilter(filter: Required | undefined): void; getAllowedToolNames(): Set | undefined; getCapabilityEnvelope?(): CapabilityEnvelope | undefined; /** Immutable active orchestration profile; absent sessions never construct run_process. */ getOrchestrationProfile?(): OrchestrationProfile | undefined; getExcludedToolNames(): Set | undefined; /** Re-derive the profile tool filter from freshly reloaded settings (reload only). */ deriveToolProfileFilter(): Required; /** True when a tool/command name survives the active profile's allow/block + user allow/exclude. */ isToolOrCommandAllowedByProfile(name: string): boolean; /** Import-boundary decision for an exact owner load or an automatic profile-driven load. */ isExtensionPathAllowed(path: string, authority: ExtensionImportAuthority, baseDir?: string): boolean; /** Filter the loaded extensions through the active resource profile (records inert/denied warnings host-side). */ filterExtensionsForRuntime(extensions: Extension[], explicitLiveExtensionPaths?: ReadonlySet): Extension[]; /** Sink for the unbound-profile-tool-grant warnings surfaced in /context. */ setUnboundToolGrantWarnings(warnings: string[]): void; getUnboundToolGrantWarnings(): string[]; /** * Optional sink for delegate provider-prompt-guideline bounding diagnostics (a guideline dropped * or truncated to fit the provider prompt budget), surfaced in /context alongside the other * construction-time tool diagnostics above. Optional so embeddings/tests that hand-build * RuntimeBuilderDeps without this seam keep working; the diagnostic is then silently dropped * rather than required for correct delegate operation. */ setDelegatePromptGuidelineWarnings?(warnings: string[]): void; createProfileFilterReloadSnapshot(): ProfileFilterReloadSnapshot; restoreProfileFilterReloadSnapshot(snapshot: ProfileFilterReloadSnapshot): void; /** Currently-active tool names (reads agent.state.tools; the pre-filter fallback for a rebuild). */ getActiveToolNames(): string[]; /** Apply the recomputed active set (capability filter + required artifact/delegation companions live here). */ setActiveToolsByName(toolNames: string[]): void; /** Normalize a tool's prompt snippet / guidelines through the system-prompt builder. */ normalizePromptSnippet(text: string | undefined): string | undefined; normalizePromptGuidelines(guidelines: string[] | undefined): string[]; /** Wire the session's own public surface into a freshly-built runner (host identity; stays host-side). */ bindExtensionCore(runner: ExtensionRunner): void; /** Re-apply UI context / mode / command-context / error subscription to a runner. */ applyExtensionBindings(runner: ExtensionRunner): void; /** Re-run resource discovery for the active extensions (reload only). */ extendResourcesFromExtensions(reason: "startup" | "reload"): Promise; /** Re-apply the active profile's model/thinking from reloaded settings (reload only). */ reapplyActiveProfileModelSettings(): Promise; /** Notify extensions-changed listeners after a single-extension live op. */ notifyExtensionsChanged(): void; /** Session-scoped tool-output artifact store for artifact-producing tools and artifact_retrieve (gated on the profile). */ getToolArtifactStore(): ArtifactStore; /** Session-owned slow-tool registry. Optional only for narrow RuntimeBuilder test/embedding seams. */ getToolTaskDependencies?(): ToolTaskDependencies; /** Lazily resolve durable image storage only for a persisted/configured session. */ getSessionImageStore(): Pick | undefined; /** Live memory manager — its provider tools join the registry. */ getMemoryManager(): MemoryManager; /** Memory retrieval + prompt-inclusion diagnostics for the core diagnostics tool. */ getMemoryAuditDiagnostics(): { retrieval: MemoryRetrievalDiagnostics; promptInclusion: MemoryPromptInclusionReport; }; /** Drop extension-contributed pending memory providers before a reload re-registers them. */ clearPendingMemoryProviders(): void; createMemoryReloadSnapshot(): MemoryControllerReloadSnapshot; restoreMemoryReloadSnapshot(snapshot: MemoryControllerReloadSnapshot): void; /** (Re)derive the memory subsystem from reloaded settings/providers. */ initializeMemory(): Promise; /** Goal-tool state accessors. */ getGoalStateSnapshot(): GoalState | undefined; saveGoalStateSnapshot(state: GoalState, expected?: GoalStateRevision): string; /** Authorize model-facing goal creation and its exact owner-requested token ceiling. */ authorizeGoalStartFromTool?(input: Pick): string | number | null | undefined; /** Native task-step state accessors. */ getTaskStepsStateSnapshot(): TaskStepsState | undefined; saveTaskStepsStateSnapshot(state: TaskStepsState): string; getPipelineRunSnapshot(): PipelineRun | undefined; savePipelineRunSnapshot(run: PipelineRun): string; /** Context-gc report for the core diagnostics tool. */ getContextGcReport(messages: AgentMessage[]): ContextGcReport; /** Non-blocking worker-delegation starter for the delegate tool. */ startWorkerDelegation(request: WorkerDelegationRequest): { started: false; skipReason: string; } | { started: true; record: LaneRecord; }; workerAgentControl?: WorkerAgentControlPort & Partial; getOrchestrationProfileCatalog(): Array<{ profileId: string; role: string; description: string; }>; getWorkerLaneRecords(): LaneRecord[]; getWorkerClaimSnapshots(): WorkerClaim[]; getWorkerResult?(laneId: string): WorkerResultContract | undefined; /** Confirm a managed dispatch's caller-stable canonical lane id was registered durably before the * goal binds it (`BackgroundLaneController.resolveManagedLaneId`). */ resolveManagedLaneId(callerLaneId: string): string | undefined; /** Worker-delegation runner for SDK/test through-completion calls. */ runWorkerDelegationOnce(request: WorkerDelegationRequest): Promise; /** Model-fitness probe for the model_fitness tool. `toolCallId` is the idempotency token * for spawned-usage reportId — present only for the LLM tool-call path (see model-fitness.ts). */ runModelFitness(args: { model: string; trials?: number; toolCallId?: string; }): Promise<{ started: true; model: string; report: ModelFitnessReport; } | { started: false; skipReason: string; }>; /** Fitness-gated reflex-brain model resolver (run_toolkit_script interpretation). */ resolveCurationModelIfFit(): Model | undefined; /** One-shot, tool-less LLM call — the reflex-brain interpreter rides this. */ runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise; /** Roll reflex-brain spend into spawned-usage accounting. `reportId` is REQUIRED: every * caller derives a stable id from the work unit's identity so a retry cannot double-count. */ addSpawnedUsage(usage: Usage, opts: { label?: string; sourceSessionId?: string; reportId: string; }): string | undefined; /** Whether the CURRENT session model may drive a worktree-sync lane worker (`AgentSession. * getLaneWorkerRefusal`) -- consulted BEFORE a goal→tmux dispatch so an ineligible model's * dispatch is refused before any lane/pane side effect (`tools/tmux-dispatch.ts`'s * `evaluateWorkerLaneRefusal`). `undefined` means eligible. */ getLaneWorkerRefusal(): LaneWorkerRefusal | undefined; /** Post-rebuild doctor helpers (validate the rebuilt runtime can render a context). */ createAgentContextSnapshot(): AgentContext; getContextUsage(): ContextUsage | undefined; /** Reload/live-op preflight refusal guards (identical refusal behavior; host-checked). */ isStreaming(): boolean; isCompacting(): boolean; /** Extension bindings present — gate for re-emitting session_start on reload. */ getExtensionUIContext(): ExtensionUIContext | undefined; getExtensionCommandContextActions(): ExtensionCommandContextActions | undefined; getExtensionShutdownHandler(): ShutdownHandler | undefined; getExtensionErrorListener(): ExtensionErrorListener | undefined; /** * Stop any pi-spawned local (Ollama/Transformers/prism llama.cpp) runtime the host's * LocalRuntimeController is holding open that no longer applies under the just-reloaded * configuration — e.g. `LocalRuntimeController.reconcile(eligibleModels)`, with `eligibleModels` * derived from whatever the host still routes to post-reload (foreground model + any configured * router tier). Called ONLY after `_reloadOnce()` commits successfully — never on a rolled-back * reload, since a rollback restores the PREVIOUS configuration and nothing became ineligible. * Optional: a host that hasn't wired a LocalRuntimeController through here yet sees no behavior * change — a local runtime this builder doesn't know about is simply left alone, never guessed at. */ reconcileLocalRuntimes?(): void; } /** * Owns the tool-registry build and the self-modification-safe extension reload extracted from * {@link AgentSession}. See the module header for the snapshot-ownership and host-binding boundaries. */ export declare class RuntimeBuilder { private _toolRegistry; private _toolDefinitions; private _toolPromptSnippets; private _toolPromptGuidelines; private _baseToolDefinitions; /** Exact extensions the owner approved through the live-load API. Discovery never populates this set. */ private readonly _explicitLiveExtensionPaths; private _reloadPromise; private _reloadRequested; private readonly _credentialManager; private readonly _credentialExposureBoundary; private readonly _credentialBootstrapFiles; private readonly _credentialDiscoveryRoots; private readonly _fileMutationIntents; private readonly _workerSessionPrivatePathEnvelope; private readonly deps; constructor(deps: RuntimeBuilderDeps); /** * Resolve the construction and activation policy once for a runtime generation. This is the * UAC choke point: callers must consult it before invoking a tool factory, not merely before * placing an already-created definition in the live registry. */ private _createToolAccessPolicy; private _runContextScout; /** Whether a tool name is present in the live wrapped registry. */ hasTool(name: string): boolean; /** The live wrapped tool for a name, if registered (activation lookup). */ getRegisteredTool(name: string): AgentTool | undefined; /** A registered tool's normalized prompt snippet, if any. */ getToolPromptSnippet(name: string): string | undefined; /** A registered tool's normalized prompt guidelines, if any. */ getToolPromptGuidelines(name: string): string[] | undefined; /** * Get all configured tools with name, description, parameter schema, prompt guidelines, and source metadata. */ getAllTools(): ToolInfo[]; getToolDefinition(name: string): ToolDefinition | undefined; get credentialManager(): CredentialManager; /** Release session-owned mutation payload leases retained by the write/edit tool pair. */ dispose(): Promise; refreshToolRegistry(options?: { activeToolNames?: string[]; includeAllExtensionTools?: boolean; }): void; private _createReloadRuntimeSnapshot; private _restoreReloadRuntimeSnapshot; private _doctorReloadRuntime; buildRuntime(options: { activeToolNames?: string[]; flagValues?: Map; includeAllExtensionTools?: boolean; onError?: ExtensionErrorListener; }): (() => void) | undefined; reload(): Promise; private _drainReloadRequests; /** * Unified reload-gate quiescence check: refuses the caller's action (message-prefixed by * `action`) while the agent is streaming, is compacting, or ANY background work unit is still * registered in the in-process quiesce registry — background lanes (research/worker/ * model-fitness), a context-scout run, or an isolated completion (see reload-blockers.ts). Every * live-op entry point below calls this so they refuse identically; this is a synchronous refusal * (not a wait/poll) — callers retry via the same coalescing `reload()` already provides. */ private _assertReloadQuiescent; private _reloadOnce; /** * Unload a single extension without full reload. * Runs the extension's session_shutdown lifecycle, unregisters its providers, * disposes its event subscriptions, and rebuilds the runtime. * Falls back to full reload on error. */ unloadExtensionLive(extensionPath: string): Promise; private _unloadExtensionLive; /** * Load a single extension without full reload. * Loads the extension with fresh import, rebuilds the runtime, * and runs the extension's session_start lifecycle. * Falls back to full reload on error. */ loadExtensionLive(extensionPath: string): Promise; private _loadExtensionLive; private _importExtension; private _assertExtensionLoadAllowed; private _recoverLiveExtensionFailure; /** * Reconcile loaded extensions with the active profile. * Loads extensions that should be enabled but aren't, and unloads extensions that shouldn't be. * Falls back to full reload if any individual load/unload fails. */ reconcileLoadedExtensions(): Promise; } export declare function resolveScoutModel(modelRegistry: ModelRegistry, modelSetting: string, agentDir: string, isModelExhausted?: (model: Model) => boolean, textProtocolOverride?: boolean): Promise<{ failure: string; model?: undefined; apiKey?: undefined; headers?: undefined; textToolCallProtocol?: undefined; } | { failure?: undefined; model: Model; apiKey: string | undefined; headers: Record | undefined; textToolCallProtocol: boolean | import("@caupulican/pi-ai").TextToolProtocolOptions | undefined; }>; //# sourceMappingURL=runtime-builder.d.ts.map