import { z } from "zod"; import type { ModelConfig } from "./model.config.js"; import type { ToolScopeType } from "./tool.scope.js"; import type { UserIdentity } from "../schemas/identity.schema.js"; import type { ChatGraphCompositeDatabase, CreateOpportunityData, NetworkMembership, UserRecord, UserDatabase, SystemDatabase, NegotiationGraphDatabase } from "../interfaces/database.interface.js"; import type { Scraper } from "../interfaces/scraper.interface.js"; import type { Cache, HydeCache } from "../interfaces/cache.interface.js"; import type { IntegrationAdapter } from "../../integrations/ports/index.js"; import type { ContactServiceAdapter } from "../../contacts/ports/index.js"; import type { ProfileEnricher } from "../interfaces/enrichment.interface.js"; import type { IntentGraphQueue } from "../interfaces/queue.interface.js"; import type { ChatSessionReader } from "../interfaces/chat-session.interface.js"; import type { ChatSummaryReader } from "../interfaces/chat-summary.interface.js"; import type { ChatMessageWriter } from "../interfaces/chat-message-writer.interface.js"; import type { QuestionGeneratorReader } from "../../questions/ports/question.generator.port.js"; import type { NegotiationSummaryReader } from "../interfaces/negotiation-summary.interface.js"; import type { Embedder } from "../interfaces/embedder.interface.js"; import type { AgentDatabase } from "../../participant-agents/ports/index.js"; import type { NegotiationTimeoutQueue } from "../interfaces/negotiation-events.interface.js"; import type { AgentDispatcher } from "../interfaces/agent-dispatcher.interface.js"; import type { DeliveryLedger } from "../interfaces/delivery-ledger.interface.js"; import type { ChatQuestionsHost, QuestionerDatabase } from "../../questions/ports/index.js"; import type { NegotiatorMemoryToolsHost } from "../interfaces/negotiator-memory.interface.js"; import type { QuestionerEnqueueFn } from "../../questions/application/question.input.js"; import type { PendingQuestionSummary } from "../schemas/pending-question.schema.js"; import type { QuestionMode, QuestionPurpose } from "../../questions/domain/question.schema.js"; import type { EnrichmentRunQueue, EnrichmentRunStore } from "../interfaces/enrichment-run.interface.js"; import type { McpActivityCaller } from "./activity-projection.js"; export type IdentityContext = UserIdentity | null; export interface ToolErrorReport { operation: string; subsystem?: string; toolName?: string; userId?: string; tags?: Record; context?: Record; } /** Minimal interface for an invokable compiled LangGraph. */ export type CompiledGraph = { invoke: (input: any) => Promise; }; /** Composition-only hook kept structural so shared contracts do not own opportunity runtime. */ export type StampNewbornOpportunitiesFn = (input: { ownerUserId: string; intentId: string; items: CreateOpportunityData[]; }) => Promise; /** * Resolved context available to every tool handler. * Contains the current user and optional network identity, resolved from DB at init. * The LLM can see this context (via system prompt) but cannot change it. */ export interface ResolvedToolContext { userId: string; userName: string; userEmail: string; /** Legacy focused network alias. Prefer `scopeType`/`scopeId` in new code. */ networkId?: string; /** Focused request scope type: `network` for community focus, `intent` for selected-intent focus. */ scopeType?: ToolScopeType; /** Focused request scope id. Network scope uses a network id; intent scope uses an intent id. */ scopeId?: string; indexName?: string; /** True when chat is network-scoped and the user owns the index. */ isOwner?: boolean; user: UserRecord; userProfile: IdentityContext; userNetworks: NetworkMembership[]; /** * @deprecated indexScope is legacy concrete network reach. New code should derive reach * from `scopeType`/`scopeId` plus `userNetworks` via `tool.scope.ts`. * Removed after call sites are migrated in this plan. */ indexScope: string[]; scopedIndex?: { id: string; title: string; prompt: string | null; permissions?: Record; }; scopedMembershipRole?: "owner" | "member"; /** True when user has not completed onboarding (onboarding.completedAt is null). */ isOnboarding: boolean; /** True when the user has a non-empty name. */ hasName: boolean; /** Chat session ID when tools are used in a chat; used for draft opportunities (context.conversationId). */ sessionId?: string; /** True when the request originates from an MCP transport (no interactive UI available). */ isMcp?: boolean; /** Agent ID when the request originates from an API key linked to an agent. */ agentId?: string; /** * Typed resolved MCP caller context, set only by the MCP server after the * capability subject is resolved. Tools with permission-projected output * (currently `read_activity_summary`) pass it into the centralized * projection in `activity-projection.ts`. Absent on REST/chat surfaces, * which are owner-trusted and receive the full owner view. */ mcpCaller?: McpActivityCaller; /** * True when the CONTACTS_ENABLED feature flag is on. Carried from the * composition root so prompt modules can gate contact-import guidance — * when false/unset, the contacts prompt module is not injected, so the * orchestrator never advertises Gmail import / add_contact (whose tools * are also de-registered). Fail-closed: treat only `true` as enabled. */ contactsEnabled?: boolean; /** True only when the gated reporter cleanup-action proposal tool is registered. */ actionToolsEnabled?: boolean; } /** * Dependencies passed when creating tools for a user session. * Includes DB adapters, embedder, and scraper. * * Note: userDb and systemDb are optional inputs - if not provided, createChatTools * will create them internally from the chatDatabaseAdapter singleton. */ /** Complete host binding set used only to derive request and composition ports. */ interface ToolContextBindings { userId: string; /** @deprecated Use userDb or systemDb instead. Kept for backwards compatibility. */ database: ChatGraphCompositeDatabase; /** Context-bound database for accessing the authenticated user's own resources. Created internally if not provided. */ userDb?: UserDatabase; /** Context-bound database for LLM/system operations on cross-user resources within shared networks. Created internally if not provided. */ systemDb?: SystemDatabase; embedder: Embedder; scraper: Scraper; /** When set, chat is scoped to this network; converted to `{ scopeType: 'network', scopeId: networkId }` at the boundary. */ networkId?: string; /** Focused request scope type: `network` or `intent`. */ scopeType?: ToolScopeType; /** Focused request scope id. Network scope uses a network id; intent scope uses an intent id. */ scopeId?: string; /** @deprecated indexScope is legacy; use `scopeType`/`scopeId`, retained until wiring phases migrate call sites. */ indexScope?: string[]; /** Chat session ID when creating tools for a chat; enables draft opportunities with context.conversationId. */ sessionId?: string; /** General-purpose cache (e.g. for tool results). */ cache: Cache; /** Dedicated cache for HyDE graph (may be same instance as cache). */ hydeCache: HydeCache; /** External integration platform adapter (OAuth, tool actions). */ integration: IntegrationAdapter; /** Queue for enqueuing follow-up intent processing (HyDE generation/deletion). */ intentQueue: IntentGraphQueue; /** Contact management operations. */ contactService: ContactServiceAdapter; /** * When false (or unset), the contact import / manual-add tools * (import_contacts, add_contact, import_gmail_contacts) are not registered. * Injected by the composition root from CONTACTS_ENABLED. Read/remove/search * contact tools are always registered. */ contactsEnabled?: boolean; /** True only when the gated reporter cleanup-action proposal tool is registered. */ actionToolsEnabled?: boolean; /** Chat session reader for loading conversation history. */ chatSession: ChatSessionReader; /** Read-through chat-session digest. Optional; consumers fall back to undefined `chatContext`. */ chatSummary?: ChatSummaryReader; /** Writes user messages into the user's most-recent chat session (Slice 5 MCP elicitation). */ chatMessageWriter?: ChatMessageWriter; /** Decision-question generator. Optional; consumers fall back to no `questions`. */ questionGenerator?: QuestionGeneratorReader; /** * Optional async question enqueue callback. When provided, question generation * is dispatched asynchronously to the QuestionerQueue instead of running inline. * Injected by the composition root when QUESTIONER_ENABLED=true. */ questionerEnqueue?: QuestionerEnqueueFn; /** * Lookup pending questions for a user, optionally filtered by source, * detection mode, selected intent scope, or capped by count. */ findPendingQuestions?: (userId: string, filters?: { sourceType?: string; sourceId?: string; scopeType?: 'intent'; scopeId?: string; networkId?: string; modes?: QuestionMode[]; purpose?: QuestionPurpose; limit?: number; }) => Promise; /** * Record the client's explicit answer to a pending question through the * host's question-answer pipeline (atomic pending→answered flip + answered * events). Returns false when the question is not pending for this user * (already answered/dismissed, expired, or not theirs). Injected by the * composition root — absent when question delivery is disabled. */ answerPendingQuestion?: (userId: string, questionId: string, answer: { selectedOptions: string[]; freeText?: string; }) => Promise; /** Negotiation-digest summarizer. Optional; consumers fall back to deterministic digests. */ negotiationSummary?: NegotiationSummaryReader; /** * Host bridge for the orchestrator's blocking `ask_user_question` tool * (synchronous chat-question persist + in-stream answer wait). Injected by * the backend composition root; when absent the tool is not registered. */ chatQuestions?: ChatQuestionsHost; /** Optional durable persistence for reporter cleanup-action proposals. */ actionProposalStore?: import('../../chat/reporter.action.contracts.js').AgentActionProposalStore; /** Durable host persistence for verified intent proposals shown in chat. */ intentProposalStore?: import('../../signals/domain/intent.proposal.js').IntentProposalStore; /** * Host bridge for the negotiator persona's `remember`/`forget` memory * tools (P5.4). Injected by the composition root only when negotiator * memory writes are enabled; when absent the tools are not registered. * Consumed exclusively by the negotiator persona toolset — the * orchestrator registry never sees these tools. */ negotiatorMemoryTools?: NegotiatorMemoryToolsHost; /** * Resolve a user's global user_context paragraph (profile-replacing identity * text), generating it on demand when absent. Mirrors `ToolDeps.getUserContextText` * so chat-path tool factories can forward it. */ getUserContextText?: (userId: string) => Promise; /** Profile enrichment from external data sources. */ enricher: ProfileEnricher; /** Database adapter for negotiation/conversation operations. */ negotiationDatabase: NegotiationGraphDatabase; /** Integration importer for bulk contact import from toolkits. */ integrationImporter: { importContacts(userId: string, toolkit: string): Promise<{ imported: number; skipped: number; newContacts: number; existingContacts: number; }>; }; /** Factory for user-scoped database access. */ createUserDatabase: (db: ChatGraphCompositeDatabase, userId: string) => UserDatabase; /** Factory for system-scoped database access. */ createSystemDatabase: (db: ChatGraphCompositeDatabase, userId: string, indexScope: string[], embedder?: Embedder) => SystemDatabase; /** Optional runtime LLM config. Pass to override env vars for API key, model, etc. */ modelConfig?: ModelConfig; /** Manages negotiation timeout jobs (optional — enables AI fallback on external agent timeout). */ negotiationTimeoutQueue?: NegotiationTimeoutQueue; /** Agent registry database adapter (optional — absent when host does not support agents). */ agentDatabase?: AgentDatabase; /** Grants the default system-agent permissions after onboarding (optional). */ grantDefaultSystemPermissions?: (userId: string) => Promise; /** Dispatcher for routing negotiation turns to personal agents (optional — falls back to system AI). */ agentDispatcher?: AgentDispatcher; /** Enqueue a negotiate_existing job after introducer approval (optional). */ queueNegotiateExisting?: (opportunityId: string, userId: string) => Promise; /** Host callback for pre-insert newborn pool-preference stamping (optional). */ stampNewbornOpportunities?: StampNewbornOpportunitiesFn; /** Delivery ledger for committing opportunity delivery rows (optional — absent in chat context). */ deliveryLedger?: DeliveryLedger; /** Persistence for async MCP profile runs (optional — absent in non-MCP/test contexts). */ enrichmentRuns?: EnrichmentRunStore; /** Queue for async MCP profile run execution (optional — absent in non-MCP/test contexts). */ enrichmentRunQueue?: EnrichmentRunQueue; /** Frontend base URL for building profile links (e.g. https://index.network, optional). */ frontendUrl?: string; /** API base URL for building opportunity accept links (e.g. https://protocol.index.network, optional). */ apiBaseUrl?: string; /** Persistence for structured questions generated by the QuestionerAgent (optional). */ questionerDatabase?: QuestionerDatabase; /** Optional host-side error reporter for swallowed protocol/tool errors. */ reportToolError?: (error: unknown, report: ToolErrorReport) => void; /** * Optional host-side per-principal MCP call throttle. Invoked once per MCP * tool dispatch (after identity resolves, before any DB work). When the * returned decision is `allowed: false`, the dispatch short-circuits with a * rate-limit error carrying `retryAfterSec`. Absent in chat/test contexts. */ mcpRateLimiter?: (input: { userId: string; agentId?: string; toolName: string; }) => Promise<{ allowed: boolean; retryAfterSec?: number; limit?: number; scope?: 'tool' | 'principal'; }>; } /** Per-request chat identity, scope, and adapter inputs. */ export type ChatToolRequest = Pick; /** Host-owned bindings injected into a chat request at the composition boundary. */ export type ChatToolHostDeps = Omit; /** * Compatibility context for the chat factory. * * New tool factories receive capability-specific `*ToolDeps` ports, while * this request-plus-host intersection keeps existing chat/persona consumers * structurally compatible during incremental migration. */ export type ToolContext = ChatToolRequest & ChatToolHostDeps; /** * All host dependencies needed to initialize the protocol chat engine. * User and system database views are created per request unless supplied by a * compatibility caller. */ export type ProtocolDeps = ChatToolHostDeps; /** * Thrown when a requested chat scope is invalid for the authenticated user. * Controllers can map this to an HTTP status code. */ export declare class ChatContextAccessError extends Error { readonly statusCode: number; readonly code: "USER_NOT_FOUND" | "INDEX_NOT_FOUND" | "INDEX_MEMBERSHIP_REQUIRED"; constructor(message: string, statusCode: number, code: "USER_NOT_FOUND" | "INDEX_NOT_FOUND" | "INDEX_MEMBERSHIP_REQUIRED"); } /** * Resolve the canonical context used by chat tools and system prompt. * This preloads user identity, profile, network memberships, and scoped index role. */ export declare function resolveChatContext(params: { database: Pick; userId: string; networkId?: string; /** Chat session ID for draft opportunities (stored as context.conversationId). */ sessionId?: string; /** CONTACTS_ENABLED flag, forwarded onto the resolved context for prompt gating. */ contactsEnabled?: boolean; /** Reporter action gate forwarded into the persona prompt/context. */ actionToolsEnabled?: boolean; }): Promise; /** * Type for the `defineTool` closure created in `createChatTools`. * Auto-injects resolved context and provides uniform logging / error handling. */ export type DefineTool = (opts: { name: string; description: string; querySchema: T; handler: (input: { context: ResolvedToolContext; query: z.infer; }) => Promise; }) => any; /** * A raw tool definition before LangChain wrapping. * Used by the tool registry for direct HTTP invocation. */ export interface RawToolDefinition { name: string; description: string; schema: z.ZodType; handler: (input: { context: ResolvedToolContext; query: unknown; }) => Promise; } /** * Registry mapping tool names to their raw definitions. */ export type ToolRegistry = Map; /** * Shared dependencies available to all tool domain factories. * Passed by `createChatTools` after compiling all subgraphs. */ /** * Host bindings available while composing the protocol tool registry. * * This is deliberately not exported as a consumer contract. Individual tool * factories receive the use-case ports below; `ToolDeps` remains the complete * compatibility shape only for registry/composition callers during migration. */ interface ToolDepsBindings { /** @deprecated Use userDb or systemDb instead. Kept for backwards compatibility. */ database: ChatGraphCompositeDatabase; /** Context-bound database for accessing the authenticated user's own resources. */ userDb: UserDatabase; /** Context-bound database for LLM/system operations on cross-user resources within shared networks. */ systemDb: SystemDatabase; /** Durable host persistence for verified intent proposals shown in chat. */ intentProposalStore?: import('../../signals/domain/intent.proposal.js').IntentProposalStore; scraper: Scraper; embedder: import('../interfaces/embedder.interface.js').Embedder; cache: Cache; integration: IntegrationAdapter; contactService: ContactServiceAdapter; /** * When false (or unset), the contact import / manual-add tools * (import_contacts, add_contact, import_gmail_contacts) are not registered. * Injected by the composition root from CONTACTS_ENABLED. Read/remove/search * contact tools are always registered. */ contactsEnabled?: boolean; integrationImporter: { importContacts(userId: string, toolkit: string): Promise<{ imported: number; skipped: number; newContacts: number; existingContacts: number; }>; }; enricher: ProfileEnricher; /** Database adapter for negotiation/conversation operations. */ negotiationDatabase: NegotiationGraphDatabase; /** Chat session reader for exposing the caller's past conversations as MCP tools. */ chatSession?: ChatSessionReader; /** Read-through chat-session digest. Optional; consumers fall back to undefined `chatContext`. */ chatSummary?: ChatSummaryReader; /** * Test seam for opportunity card presentation helpers. Production * compositions leave this unset so tools construct the real presenter. */ opportunityPresentation?: { createPresenter?: () => { presentCard(input: unknown): Promise; }; gatherPresenterContext?: (...args: unknown[]) => Promise; }; /** Writes user messages into the user's most-recent chat session (Slice 5 MCP elicitation). */ chatMessageWriter?: ChatMessageWriter; /** Decision-question generator. Optional; consumers fall back to no `questions`. */ questionGenerator?: QuestionGeneratorReader; /** * Optional async question enqueue callback. When provided, question generation * is dispatched asynchronously to the QuestionerQueue instead of running inline * via the `questionGenerator`. Injected by the composition root when * QUESTIONER_ENABLED=true. */ questionerEnqueue?: QuestionerEnqueueFn; /** * Lookup pending questions for a user, optionally filtered by source, * detection mode, or capped by count (hosts apply `limit` SQL-side). * Used by tools to attach contextually relevant questions to their results. * Injected by the composition root — absent when question delivery is disabled. */ findPendingQuestions?: (userId: string, filters?: { sourceType?: string; sourceId?: string; /** Optional selected-intent scope. When `scopeType === 'intent'`, `scopeId` is the selected intent id. */ scopeType?: 'intent'; scopeId?: string; /** Restrict to questions whose actor carries this network id. */ networkId?: string; /** Restrict to questions whose detection mode is in this set. */ modes?: QuestionMode[]; /** Restrict to an internal generation purpose. */ purpose?: QuestionPurpose; /** Maximum rows to return; hosts should apply this in the query. */ limit?: number; }) => Promise; /** * Record the client's explicit answer to a pending question through the * host's question-answer pipeline (atomic pending→answered flip + answered * events). Returns false when the question is not pending for this user. * Injected by the composition root — absent when question delivery is disabled. */ answerPendingQuestion?: (userId: string, questionId: string, answer: { selectedOptions: string[]; freeText?: string; }) => Promise; /** Negotiation-digest summarizer. Optional; consumers fall back to deterministic digests. */ negotiationSummary?: NegotiationSummaryReader; /** * Host bridge for the orchestrator's blocking `ask_user_question` tool * (synchronous chat-question persist + in-stream answer wait). Injected by * the backend composition root; when absent the tool is not registered. */ chatQuestions?: ChatQuestionsHost; /** Manages negotiation timeout jobs (optional — enables AI fallback on external agent timeout). */ negotiationTimeoutQueue?: NegotiationTimeoutQueue; /** Agent registry database adapter (optional — absent when host does not support agents). */ agentDatabase?: AgentDatabase; /** Grants the default system-agent permissions after onboarding (optional). */ grantDefaultSystemPermissions?: (userId: string) => Promise; /** Dispatcher for routing negotiation turns to personal agents (optional — falls back to system AI). */ agentDispatcher?: AgentDispatcher; /** Host callback for pre-insert newborn pool-preference stamping (optional). */ stampNewbornOpportunities?: StampNewbornOpportunitiesFn; /** Delivery ledger for committing opportunity delivery rows (optional — absent in chat context). */ deliveryLedger?: DeliveryLedger; /** Persistence for async MCP profile runs (optional — absent in non-MCP/test contexts). */ enrichmentRuns?: EnrichmentRunStore; /** Queue for async MCP profile run execution (optional — absent in non-MCP/test contexts). */ enrichmentRunQueue?: EnrichmentRunQueue; /** Frontend base URL for building profile links (e.g. https://index.network, optional). */ frontendUrl?: string; /** API base URL for building opportunity accept links (e.g. https://protocol.index.network, optional). */ apiBaseUrl?: string; /** Optional host-side error reporter for swallowed protocol/tool errors. */ reportToolError?: (error: unknown, report: ToolErrorReport) => void; /** * Optional host-side per-principal MCP call throttle. Invoked once per MCP * tool dispatch (after identity resolves, before any DB work). When the * returned decision is `allowed: false`, the dispatch short-circuits with a * rate-limit error carrying `retryAfterSec`. Absent in chat/test contexts. */ mcpRateLimiter?: (input: { userId: string; agentId?: string; toolName: string; }) => Promise<{ allowed: boolean; retryAfterSec?: number; limit?: number; scope?: 'tool' | 'principal'; }>; graphs: { profile: CompiledGraph; intent: CompiledGraph; index: CompiledGraph; networkMembership: CompiledGraph; intentIndex: CompiledGraph; opportunity: CompiledGraph; premise: CompiledGraph; }; /** * Optional network ranking override for `read_networks`. Injected by tests or custom compositions. * When absent, defaults to `NetworkRecommender.invoke()` with a lazy module-level singleton. */ networkRanker?: (input: { userContext: string; networks: Array<{ networkId: string; renderedContext: string; }>; }) => Promise<{ rankedNetworkIds: string[]; } | null>; /** * Resolve a user's global user_context paragraph (profile-replacing identity text), * generating it on demand when absent. Injected by the backend composition root * (`ensureGlobalUserContext`). When absent, onboarding network ranking is skipped. */ getUserContextText?: (userId: string) => Promise; } /** * Shared backing shape for the registry composition boundary. Capability-local * ports may Pick from this type, but it is intentionally not a root export. */ export type ToolRegistryCompositionDeps = Omit; /** Runtime-only hooks retained for MCP and existing host composition. */ type ToolRuntimeCompatibilityDeps = Pick; /** * Legacy complete tool composition contract. * * New capability factories must accept their named `*ToolDeps` port above, * rather than this aggregate. Keeping the intersection preserves structural * compatibility for the registry and host composition while consumers migrate. */ export type ToolDeps = ToolRegistryCompositionDeps & ToolRuntimeCompatibilityDeps; export declare function success(data: T): string; export declare function error(message: string, debugSteps?: Array<{ step: string; detail?: string; data?: Record; }>): string; /** Return needsClarification for missing required fields. */ export declare function needsClarification(params: { missingFields: string[]; message: string; }): string; /** UUID v4 format: 8-4-4-4-12 hex chars (e.g. c2505011-2e45-426e-81dd-b9abb9b72023) */ export declare const UUID_REGEX: RegExp; /** * Resolves an array of network IDs to their display titles. * Skips any IDs that don't resolve (deleted or invalid networks). */ export declare function resolveIndexNames(database: { getNetwork(id: string): Promise<{ id: string; title: string; } | null>; }, networkIds: string[]): Promise; /** * Normalize a URL string: if it lacks a protocol, prepend "https://". * Returns the normalized URL or null if the result is not a valid URL. */ export declare function normalizeUrl(raw: string): string | null; /** * Extract unique, valid URLs from a string (e.g. user message or details). * Handles both full URLs (https://...) and bare domains (github.com/...). */ export declare function extractUrls(text: string): string[]; /** * Recursively redacts sensitive field values from an arbitrary payload before * it is passed to a structured logger. Matches field names case-insensitively * and ignoring underscores, so `api_key`, `apiKey`, and `API_KEY` all match. * Non-sensitive fields are passed through unchanged. Never mutates the input — * returns a new value. * * Intended for structured-log redaction only. Do NOT use as a security * boundary for data in motion. * * @param value - Arbitrary JSON-like payload (query object, config blob, etc.) * @returns A new value with sensitive fields replaced by `"[redacted]"`. */ export declare function redactSensitiveFields(value: unknown): unknown; export {};