/** * src/extension/tools.ts — the 5 delegation tools wired onto the pi-subagents * engine (mirror of the pi-mesh adapter pattern). This is the only adapter * layer that touches the local pi-types.ts; the engine/core stay Pi-free. * * The 6 tools: * 1. zob_delegation_catalog — read-only body-free agent + contract catalog. * 2. delegate_agent — single / parallel / chain dispatch. * 3. delegate_task — structured single task (canonical + safe aliases). * 4. get_delegation_run — inspect a background run (body-free). * 5. await_delegation_run — bounded passive wait on a background run. * 6. continue_run — resume a terminal run with its original session. * * Zero @earendil-works/* imports (I9). */ import type { AgentScope, ChildResult, ChildThinkingLevel } from "../core/types.js"; import { DispatchEngine } from "../engine/dispatch.js"; import type { ChildProgressEvent } from "../engine/index.js"; import { BackgroundRunRegistry } from "../engine/background.js"; import { type AgentCatalog } from "../registry/index.js"; import type { EnabledModelsReader, ModelByClassMap, VerifiedModelCatalog } from "../models/index.js"; import type { SpawnFn } from "../lanes/index.js"; import type { ExtensionAPI, SessionContext, ToolResult } from "./pi-types.js"; /** Shared per-session runtime: the engine plus its background run registry. */ export interface SubagentsRuntime { engine: DispatchEngine; background: BackgroundRunRegistry; repoRoot: string; startedAt: number; /** F3: parent/session model the engine inherits from (undefined = none). */ parentModel?: string; } export type EnsureRuntime = (ctx: SessionContext) => SubagentsRuntime; export type GetRuntime = () => SubagentsRuntime | null; /** * F3/P1 session-model reference: a `"provider/model"` (or bare id) STRING, * or — pi >= 0.84 — the live Model OBJECT `{ provider, id }` the host exposes * on `ctx.model`. Normalized to a plain `"provider/id"` string via * normalizeParentModelInput BEFORE any string use (an object reaching * `.trim()` was the P1 TypeError regression). */ export type SessionModelRef = string | { provider: string; id: string; }; /** Options for {@link createSubagentsRuntime} (everything injectable for tests). */ export interface CreateSubagentsRuntimeOptions { spawn?: SpawnFn; onLedger?: (entry: Record) => void; enabledModelsReader?: EnabledModelsReader; /** * F3 parent-model inheritance: explicit parent/session model (string or * `{provider,id}` object — pi >= 0.84 `ctx.model`), wins over * `parentModelReader`. Normalized before use; see SessionModelRef. */ parentModel?: SessionModelRef; /** * F3 injectable parent-model source; wins over the pi settings * `defaultModel` fallback (project `.pi/settings.json` over the global * `~/.pi/agent/settings.json`). Should never throw; a throwing reader is * caught and degrades to the settings fallback. */ parentModelReader?: () => string | undefined; /** * F3 default class models. Default: every class (cheap/balanced/capable) * maps to the resolved parent model — quota-safe, the parent model is the * one model guaranteed available for this session. An explicit map REPLACES * the default entirely (unmapped classes fall back to the parent model). */ classModels?: ModelByClassMap; /** * F2 verified-catalog source evaluated at every preflight. Default: read * `/.pi/model-catalog.json` (schema zob.model-catalog.v1). */ verifiedCatalogReader?: () => VerifiedModelCatalog; /** * F6: persist the hash-only delegation ledger + attestation sidecars under * `/.pi/logs/runs` (default TRUE — real sessions leave an * observable trail). Explicit `false` disables persistence. */ persistLedger?: boolean; } /** * P1: normalize a parent/session model value BEFORE any string use. * `typeof m === 'string'` → trimmed passthrough; a `{ provider, id }` object * (pi >= 0.84 `ctx.model`, cf. pi types.d.ts Model) → `provider/id`; * anything else (empty string, object without both fields, undefined) → * undefined (fallback to the settings defaultModel). NEVER throws — this is * the mirror of the zob-harness child-runner child-model normalization. */ export declare function normalizeParentModelInput(value: SessionModelRef | undefined): string | undefined; /** Resolve the F3 parent model: explicit (string|object) > injectable reader > settings defaultModel. */ export declare function resolveParentModel(repoRoot: string, opts: CreateSubagentsRuntimeOptions): string | undefined; /** Build a runtime bound to a repo root. `spawn` injectable for tests. */ export declare function createSubagentsRuntime(repoRoot: string, opts?: CreateSubagentsRuntimeOptions): SubagentsRuntime; /** True when a child result is failed/incomplete. */ export declare function isFailed(result: ChildResult): boolean; export type { ChildProgressEvent }; /** Compact token count: 12340 -> "12.3k", 2100000 -> "2.1M". */ export declare function formatTokenCount(tokens: number): string; /** Sliding window: how many of the child's last actions a feed block shows. */ export declare const FEED_ACTION_WINDOW = 5; /** * Live feed header line for a completed turn (STRICTLY bounded): * `▸ explore · turn 2 · zai/glm-5.3 · 12.4k tok` * Actions no longer render standalone — they live inside the feed block * below; `text` never streams (that is per-token spam). */ export declare function formatChildProgressLine(event: ChildProgressEvent): string | undefined; /** Per-run feed state: latest header snapshot + sliding window of actions. */ export interface ChildFeedState { agent: string; turns: number; model?: string; contextTokens?: number; /** Last actions, oldest -> newest (capped at FEED_ACTION_WINDOW). */ actions: string[]; } /** * Render one SELF-CONTAINED multi-line feed block (pi `onUpdate` REPLACES * the previous display, it does not append — so every block must carry the * full context): header line, then the last actions as an aligned tree with * the most recent action last, closed by `└─`. Strictly bounded: * 1 header + at most FEED_ACTION_WINDOW action lines. */ export declare function formatChildFeedBlock(state: ChildFeedState): string; /** * Build the live `onChildEvent` consumer that streams ONE self-contained * multi-line block per child event — header `▸ agent · turn N · model · tok` * plus the sliding window of the last FEED_ACTION_WINDOW actions — into the * parent conversation via pi `onUpdate`. One update per event, no text * deltas. Parallel children keep independent per-runId blocks. Returns * undefined when there is no onUpdate — nothing is wired, zero overhead * (the feed:false path stays completely silent). */ export declare function makeChildEventFeed(onUpdate: ((partial: ToolResult) => void) | undefined): ((event: ChildProgressEvent) => void) | undefined; /** Compact text rendering of a child result (no raw bodies persisted). */ export declare function formatChildResultText(result: ChildResult): string; /** Body-free catalog summary text (mirrors harness formatDelegationCatalogSummary). */ export declare function formatDelegationCatalogSummary(catalog: AgentCatalog): string; /** 1) zob_delegation_catalog — read-only, no dispatch. */ export declare function registerDelegationCatalog(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; /** 2) delegate_agent — single / parallel / chain. */ export declare function registerDelegateAgent(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; /** 3) delegate_task — structured single task (canonical + safe aliases). */ export declare function registerDelegateTask(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; /** 4) get_delegation_run — inspect a background / monitor run (body-free). */ export declare function registerGetDelegationRun(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; /** 5) await_delegation_run — bounded passive wait on a background run. */ export declare function registerAwaitDelegationRun(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; /** 6) continue_run — resume a terminal run with its original session + byte-offset. */ export declare function registerContinueRun(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; /** Register all 6 delegation tools on the Pi API. */ export declare function registerTools(pi: ExtensionAPI, ensureRuntime: EnsureRuntime): void; export interface NormalizedDelegateTaskParams { agent: string; task: string; context: string; expected_outcome?: string; required_tools?: string[]; must_do?: string[]; must_not_do?: string[]; original_user_ask?: string; allowed_paths?: string[]; forbidden_paths?: string[]; output_contract?: string; run_in_background?: boolean; /** F8: fresh per-run session (default true); false = legacy stable lane. */ fresh?: boolean; child_goal?: unknown; cwd?: string; scope?: AgentScope; model?: string; thinking?: ChildThinkingLevel; load_skills?: string[]; /** Live child-actions feed switch (default true). */ feed?: boolean; /** C6 child filesystem isolation ("worktree"; default none). */ isolation?: "none" | "worktree"; } export interface DelegateTaskNormalizedResult { params: NormalizedDelegateTaskParams; errors: string[]; } /** Merge canonical + safe aliases; block non-equal conflicts before launch. */ export declare function normalizeDelegateTaskParams(raw: Record): DelegateTaskNormalizedResult; /** Build the six-part structured task text from normalized delegate_task params. */ export declare function buildStructuredTask(params: NormalizedDelegateTaskParams): string;