/** * The agent facade — one `session.agent(id)` handle over the agent-scope * services the wire exposes. Turn-driving calls (prompt / steer / cancel), * skill activation, permission mode, and commands go straight to their domain * services, as do shell commands, model, usage, plan, and task calls; * `getContext` merges two reads client-side. Prompt streaming is * NOT on this interface: it flows through the agent's `events` hub * (`turn.*`, `assistant.delta`, `tool.call.*`, `prompt.completed`, …). */ import type { IAgentCommandService } from '@moonshot-ai/agent-core-v2/agent/command/agentCommand'; import type { IAgentContextMemoryService } from '@moonshot-ai/agent-core-v2/agent/contextMemory/contextMemory'; import type { IAgentMcpService } from '@moonshot-ai/agent-core-v2/agent/mcp/mcp'; import type { IAgentRuntimeBindingService } from '@moonshot-ai/agent-core-v2/agent/runtimeBinding/runtimeBinding'; import type { ISessionTokenCountingService } from '@moonshot-ai/agent-core-v2/session/tokenCounting/sessionTokenCounting'; import type { IAgentPlanService } from '@moonshot-ai/agent-core-v2/features/plan/plan'; import type { IAgentProfileService } from '@moonshot-ai/agent-core-v2/agent/profile/profile'; import type { IAgentShellCommandService } from '@moonshot-ai/agent-core-v2/agent/shellCommand/shellCommand'; import type { IAgentSkillService } from '@moonshot-ai/agent-core-v2/features/skill/skillService'; import type { IAgentTaskService } from '@moonshot-ai/agent-core-v2/agent/task/task'; import type { ISessionUsageService } from '@moonshot-ai/agent-core-v2/session/usage/sessionUsage'; import type { ContentPart } from '@moonshot-ai/agent-core-v2/human/llm/message'; import type { PermissionMode } from '@moonshot-ai/agent-core-v2/agent/permissionPolicy/types'; import type { ScopeRef } from '../channel.js'; import type { ScopedCaller } from './session.js'; // Wire-type aliases derived through the engine service interfaces (keeps // klient free of protocol-package imports). export type PromptLaunchResult = import('@moonshot-ai/agent-core-v2/agent/loop/loop').PromptLaunchResult | undefined; export type PromptWithSkillsInput = Parameters[0]; export type PromptWithSkillsResult = Awaited>; export type ShellCommandResult = Awaited>; export type SetModelResult = Awaited>; export type ThinkingLevel = ReturnType; export type UsageStatus = Awaited>; export type AgentContextData = { history: ReturnType; tokenCount: ReturnType; }; export type AgentCommandInfo = Awaited>[number]; export type RuntimeBinding = ReturnType; export type PlanData = Awaited>; export type AgentTaskInfo = Awaited>[number]; export type McpServerEntry = ReturnType[number]; export interface AgentFacade { prompt(input: { input: readonly ContentPart[]; promptId?: string; }): Promise; /** * Submit one prompt with one or more skill activations bundled into the * same user message: the skills are validated up front (an unknown name or * an empty list rejects the whole submission), rendered ahead of the * caller's parts in the same turn, and the bundle undoes as a single * anchor. Resolves with the submitted bundle's queue identity (`prompt_id` * / `created_at` / `state`), plus `turn_id` once launched — `state` is * `queued` when the submission queued behind a running turn. */ promptWithSkills(input: PromptWithSkillsInput): Promise; steer(input: { input: readonly ContentPart[] }): Promise; /** * Activate a skill as a user-slash activation: the engine renders the skill * prompt and drives it as a normal turn (same settlement/event flow as * `prompt`). Resolves with the launched turn id; rejects when the skill is * unknown or the agent is busy. */ activateSkill(input: { name: string; args?: string }): Promise; cancel(input?: { turnId?: number }): Promise; runShellCommand(input: { command: string; commandId?: string }): Promise; cancelShellCommand(input: { commandId: string }): Promise; getModel(): Promise; setModel(model: string): Promise; getThinking(): Promise; setThinking(level: string): Promise; setPermission(mode: PermissionMode): Promise; getUsage(): Promise; getContext(): Promise; listCommands(): Promise; runCommand(input: { name: string; args?: string }): Promise; getRuntime(): Promise; switchRuntime(runtimeId: string): Promise; getPlan(): Promise; enterPlan(): Promise; clearPlan(): Promise; cancelPlan(input?: { id?: string }): Promise; getTasks(input?: { activeOnly?: boolean; limit?: number }): Promise; stopTask(input: { taskId: string; reason?: string }): Promise; getTaskOutput(input: { taskId: string; tail?: number }): Promise; /** * Session-merged MCP server entries (workspace set + ephemeral session * overlay). This is a live snapshot, so entries may still be pending while * the initial connection attempt runs. */ getMcpServers(): Promise; /** * Trigger a manual full compaction. Async: `true` means the compaction was * started (it runs in the background); `false` means one is already running. * Throws when there is nothing to compact or a turn is active. */ compact(input?: { instruction?: string }): Promise; } export function createAgentFacade(call: ScopedCaller, scope: ScopeRef): AgentFacade { return { prompt: (input) => call(scope, 'agentPromptService', 'submit', [input]) as Promise, promptWithSkills: (input) => call(scope, 'agentSkillService', 'promptWithSkills', [input]) as Promise, steer: (input) => call(scope, 'agentPromptService', 'submitSteer', [input]) as Promise, activateSkill: (input) => call(scope, 'agentSkillService', 'activate', [input]) as Promise, cancel: (input) => // No turnId sends an empty arg list: `[undefined]` would cross the wire // as `[null]`, and `cancelFromUser(null)` would not match the active turn. call(scope, 'agentLoopService', 'cancelFromUser', input?.turnId === undefined ? [] : [input.turnId]) as Promise, runShellCommand: (input) => call(scope, 'agentShellCommandService', 'run', [input]) as Promise, cancelShellCommand: (input) => call(scope, 'agentShellCommandService', 'cancel', [input.commandId]) as Promise, getModel: () => call(scope, 'agentProfileService', 'getModel', []) as Promise, setModel: (model) => call(scope, 'agentProfileService', 'setModel', [model]) as Promise, getThinking: () => call(scope, 'agentProfileService', 'getEffectiveThinkingLevel', []) as Promise, setThinking: (level) => call(scope, 'agentProfileService', 'setThinking', [level]) as Promise, setPermission: (mode) => call(scope, 'agentPermissionModeService', 'setModeAndBroadcast', [mode]) as Promise, getUsage: () => call(scope, 'agentUsageService', 'status', []) as Promise, getContext: async () => { const [history, tokenCount] = await Promise.all([ call(scope, 'agentContextMemoryService', 'get', []), call(scope, 'agentTokenCountingService', 'statusSize', []), ]); return { history, tokenCount } as AgentContextData; }, listCommands: () => call(scope, 'agentCommandService', 'list', []) as Promise, runCommand: (input) => // Same `[undefined]` → `[null]` wire hazard as `cancel`: the engine's // `args = ''` default only applies to a missing arg. call( scope, 'agentCommandService', 'run', input.args === undefined ? [input.name] : [input.name, input.args], ) as Promise, getRuntime: () => call(scope, 'agentRuntimeBindingService', 'get', []) as Promise, switchRuntime: (runtimeId) => call(scope, 'agentRuntimeBindingService', 'switch', [runtimeId]) as Promise, getPlan: () => call(scope, 'agentPlanService', 'status', []) as Promise, enterPlan: () => call(scope, 'agentPlanService', 'enter', []) as Promise, clearPlan: () => call(scope, 'agentPlanService', 'clear', []) as Promise, cancelPlan: (input) => call(scope, 'agentPlanService', 'cancel', [input?.id]) as Promise, getTasks: (input) => call(scope, 'agentTaskService', 'list', [ input?.activeOnly ?? false, input?.limit, ]) as Promise, stopTask: async (input) => { if (input.reason === undefined) { await call(scope, 'agentTaskService', 'stopByUser', [input.taskId]); return; } await call(scope, 'agentTaskService', 'stop', [input.taskId, input.reason]); }, getTaskOutput: (input) => call(scope, 'agentTaskService', 'readOutput', [input.taskId, input.tail]) as Promise, getMcpServers: () => call(scope, 'agentMcpService', 'list', []) as Promise, compact: (input) => call(scope, 'agentFullCompactionService', 'begin', [ { source: 'manual', instruction: input?.instruction }, ]) as Promise, }; }