// ── 类型唯一真源(vendored from packages/shared/src/types.ts)── // 为让 npm 包自包含(运行时无需 @mipham/shared workspace 依赖), // 此处从 packages/shared/src/types.ts 同步。改动共享类型时两边同步。 // ── Provider Types ── export type ProtocolType = 'openai-compatible' | 'anthropic' | 'custom' export interface ModelInfo { id: string name: string providerId: string contextWindow: number maxOutput: number vision: boolean status: 'active' | 'upcoming' | 'deprecated' } export interface ProviderConfig { id: string name: string protocol: ProtocolType baseUrl?: string apiKey: string models: ModelInfo[] status?: 'active' | 'upcoming' } // ── Message Types ── export interface TextContent { type: 'text' text: string } export interface ImageContent { type: 'image_url' image_url: { url: string } } export interface ToolUseContent { type: 'tool_use' id: string name: string input: Record } export interface ToolResultContent { type: 'tool_result' tool_use_id: string content: string /** * Failed tool call. Set **only on failure** — absent means success, matching * Anthropic's own `tool_result` block. Unlike `StreamChunk.isError`, this value * crosses persistence (session JSONL, daemon `messages` table), where rows * written before this field existed stay readable, so `undefined` cannot be * eliminated — always compare with `=== true`. */ is_error?: boolean } export interface ThinkingContent { type: 'thinking' thinking: string } export type ContentBlock = TextContent | ImageContent | ToolUseContent | ToolResultContent | ThinkingContent export interface Message { role: 'system' | 'user' | 'assistant' content: string | ContentBlock[] /** DeepSeek reasoning tokens — must be passed back to the API in multi-turn conversations. */ reasoning_content?: string } // ── Tool Types ── /** * Legacy 3-level tool permission (predates `PermissionMode`). * `'self'` means "let the tool self-decide" — i.e. allowed, but not flagged as `'bypass'`. * Renamed from `'auto'` (2026-09-22) so that `auto` can name the *classifier mode* * without a second meaning living in `PermissionLevel` — see * `docs/superpowers/specs/2026-09-22-permission-classifier-design.md` §3.1. */ export type ToolPermission = 'self' | 'ask' | 'bypass' export type ToolCategory = 'file' | 'exec' | 'agent' | 'network' | 'system' | 'artifact' | 'scheduling' // ── Artifact Types ── export interface ArtifactEntry { name: string path: string url: string size: number type: 'html' | 'svg' createdAt: string sessionId: string versions?: string[] // version tags e.g. ['v1', 'v2'] versionCount?: number } export interface ArtifactManifest { version: 1 artifacts: ArtifactEntry[] port?: number } export interface ToolResult { success: boolean content: string error?: string } // ── Task Notification Types ── export interface TaskNotification { taskId: string status: 'started' | 'completed' | 'failed' description: string content?: string error?: string } // ── Stream Types ── export interface StreamChunk { type: | 'text' | 'tool_use' | 'tool_result' | 'thinking' | 'stop' | 'error' | 'warning' | 'task_notification' | 'usage' content?: string toolUse?: ToolUseContent tool_use_id?: string /** * Failed tool result (type: 'tool_result'). The engine always sets it — `false` * for success, so consumers never have to treat `undefined` as a third state. * Without it the success bit is unrecoverable downstream: `content` carries * either the output or the error text, and the two are indistinguishable. */ isError?: boolean error?: string /** DeepSeek reasoning tokens accumulated during this stream. */ reasoning_content?: string /** Anthropic thinking block content (DeepSeek Anthropic endpoint). */ thinking?: string /** Background task notification payload (type: 'task_notification'). */ taskNotification?: TaskNotification /** API-reported input token count (type: 'usage'). */ inputTokens?: number /** API-reported output token count (type: 'usage'). */ outputTokens?: number /** * The provider stopped because it hit the output token ceiling * (OpenAI `finish_reason: 'length'` / Anthropic `stop_reason: 'max_tokens'`). * Set **only when true** — absent on every normal stop, so the success path * stays byte-identical. Without it a truncated turn is indistinguishable * from a turn the model chose to end: the provider emits its terminal stop * either way, and tool calls cut off mid-arguments are dropped silently. */ truncated?: boolean } // ── Config Types ── export interface MiphamConfig { version: string defaultProvider: string defaultModel: string permission: PermissionLevel /** Org-level permission restrictions (forbiddenModes, maxAllowedMode). */ permissionRestrictions?: PermissionRestrictions /** User-defined permission rules (allow/deny patterns), wired into the runtime PermissionSystem. */ permissionRules?: { allow?: string[]; deny?: string[] } /** * How to render the model's reasoning/thinking before the answer: * - `off` → hide entirely (default — clean output) * - `minimal` → content-free "thinking…" indicator * - `full` → last 200 chars of the actual thinking text */ showThinking?: 'off' | 'minimal' | 'full' /** When false, hide the `⏰ Wakeup scheduled` scheduling confirmation notices. Default false. */ showSchedulingNotices?: boolean /** When false, disable the slash-command picker auto-popup on `/`. Default false. */ showCommandPicker?: boolean providers: ProviderConfig[] skills?: { paths: string[] mcpServers: McpServerConfig[] /** Startup skill-list budget: full (default) | compact (one-line desc) | off. */ reminder?: 'full' | 'compact' | 'off' } marketplace?: { /** If set, only allow installs from matching repos (e.g. ["One-Mipham/*"]) */ strictKnownMarketplaces?: string[] /** Block installs from matching repos (e.g. ["malicious-org/*"]) */ blockedMarketplaces?: string[] } /** Phase 9 feature flags. All default to true. */ features?: Partial /** Phase 10 CRSI feature flags. All default to true. */ crsi?: Partial /** Ghost-text 自动补全(输入续写)。默认 enabled: true、debounceMs: 400。 */ autocomplete?: Partial } export interface FeatureFlags { mcp: { oauthEnabled: boolean } context: { adaptiveThresholds: boolean } } export interface CrsiConfig { /** Extract and inject experience rules into agent system prompts. */ ruleInjection: boolean /** Intercept tool calls and auto-fix known failure patterns before execution. */ preToolHook: boolean /** Analyze agent outcomes for recurring failure patterns. */ autoPatternAnalysis: boolean /** Auto-degrade/disable low-effectiveness rules based on success-rate tracking. */ autoRuleManagement: boolean } export interface AutocompleteConfig { enabled: boolean debounceMs: number } export interface McpServerConfig { name: string /** stdio: executable to spawn (mutually exclusive with `url`). */ command?: string /** stdio: args passed to `command`. */ args?: string[] /** HTTP: Streamable HTTP endpoint (mutually exclusive with `command`). */ url?: string /** HTTP: extra request headers (e.g. Authorization). */ headers?: Record env?: Record /** Per-server tool-call request timeout (ms). Overrides the 60s default. */ request_timeout_ms?: number auth?: { type: 'oauth' authorizationUrl: string tokenUrl: string clientId: string scopes?: string[] redirectPort?: number } } // ── Hook Types ── export type HookEvent = | 'PreToolUse' | 'PostToolUse' | 'PostToolUseFailure' | 'SessionStart' | 'SessionEnd' | 'Notification' | 'Stop' | 'UserPromptSubmit' | 'PreCompact' | 'PostCompact' | 'ConfigChange' | 'SubagentStart' | 'SubagentStop' | 'PreInference' export type HookType = 'command' | 'http' | 'code' | 'mcp_tool' export interface HookConfig { type: HookType command?: string args?: string[] url?: string method?: 'GET' | 'POST' headers?: Record mcpServer?: string mcpTool?: string /** Command timeout in seconds (Claude Code default is 60). */ timeout?: number continueOnBlock?: boolean } export interface HookDefinition { event: HookEvent toolName?: string handler: (context: HookContext) => Promise } export interface HookContext { event: HookEvent /** * The workspace this invocation is for. * * Stamped by `HookEngine` from its own cwd; hooks read it out of stdin and run * in it. It is the *session's* cwd, which is not `process.cwd()` in the daemon * (many sessions, one process). */ cwd?: string toolName?: string toolInput?: Record toolResult?: ToolResult sessionId: string userPrompt?: string configKey?: string configValue?: unknown /** PreInference: full conversation messages for DLP inspection. */ messages?: Array<{ role: string; content: string }> /** PreInference: recent tool calls and their results. */ toolCalls?: Array<{ name: string input: Record resultPreview: string }> /** PreInference: current provider ID. */ provider?: string /** PreInference: current model ID. */ model?: string } export interface HookResult { allowed: boolean reason?: string modifiedInput?: Record decision?: 'allow' | 'block' permissionDecision?: 'allow' | 'deny' | 'ask' | 'defer' additionalContext?: string updatedOutput?: string } // ── Instruction Types ── export interface InstructionFile { path: string level: 'group' | 'company' | 'project' | 'directory' | 'user' privacy: 'public' | 'project' | 'private' language: string content: string frontmatter: Record } // ── Permission Types ── /** * Permission modes, matching Claude Code's permission architecture. * * `bypassPermissions` is legal but **off the Shift+Tab cycle** — the cycle is the * four slots Claude Code's own cycle array lists (`default`, `acceptEdits`, * `plan`, `auto`), while this union is the full set the config may request. See * `ALL_MODES` / `MODE_CYCLE` in `core/permission-config.ts` for why those two * arrays are separate, and why merging them silently demotes * `bypassPermissions`. * * `auto` behaves unlike the other four: it does not decide anything statically. * Its baseline answers `'ask'` for **every** call, and the ruling is delegated to * the LLM permission classifier (`core/permission-classifier.ts`). Two * consequences worth knowing before reading code that switches on this union: * * - `auto` has **no static width**, so a "which mode is narrower" measurement * taken from the static chain reads it as narrower than `plan` — a * measurement of the wrong object, not a fact about `auto`. * - A mode whose static baseline is `'ask'` must not be *reached* by a caller * that never consults the classifier, or it degrades into "refuse everything". * * This copy is kept in step with `packages/shared/src/types.ts` mechanically: * `test/integrity/shared-types-parity.test.ts` asserts that every declaration * the two files share has the same member set (and, for union aliases, the same * literal set). Member *lists* are all it covers — a stale prose default is * still a human's to catch. */ export type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'auto' | 'bypassPermissions' /** * Backward-compatible alias: `PermissionMode` plus the legacy 3-level * `'self'`/`'ask'`/`'bypass'`. The legacy level was named `'auto'` until 2026-09-22; * that name now belongs to the classifier mode, so the two can no longer be * confused for one another (the `fc5afd3a` incident was exactly that confusion: * a config `permission: auto` meaning "tool self-decides" silently becoming * "run everything"). */ export type PermissionLevel = PermissionMode | 'self' | 'ask' | 'bypass' /** Org-level restrictions that cap or forbid specific permission modes. */ export interface PermissionRestrictions { /** Modes that may not be entered (cycle skips them). */ forbiddenModes?: PermissionMode[] /** Ceiling — modes ranked higher (more permissive) than this are treated as forbidden. */ maxAllowedMode?: PermissionMode } export interface PermissionConfig { mode: PermissionMode allow: string[] deny: string[] /** Optional org-level restrictions enforced on every mode transition. */ restrictions?: PermissionRestrictions } export interface PermissionRuleEntry { pattern: string // e.g., "Bash(git:*)" level: 'allow' | 'deny' | 'ask' compiled: RegExp /** Set when the pattern is structurally invalid and can never match. */ invalid?: string } export interface PermissionRule { toolName: string level: PermissionLevel pattern?: string } // ── Inference Hook (DLP) Types ── /** Configuration for the PreInference DLP hook, loaded from config.yml. */ export interface InferenceHookConfig { /** DLP server endpoint (HTTPS). Empty = feature disabled. */ endpoint: string /** HMAC signing secret (format: mis_). */ signing_secret: string /** Request timeout in milliseconds. Default 5000. */ timeout: number /** Failure posture: 'fail-closed' blocks on error, 'fail-open' allows. */ on_failure: 'fail-closed' | 'fail-open' /** Organization identifier (optional, sent in payload). */ organization_id: string /** Additional custom headers to send with each request. */ headers: Record } /** Outgoing request to the DLP server. */ export interface InferenceCheckRequest { type: 'inference_check' id: string created_at: string data: { type: 'pre_inference' session_id: string organization_id?: string provider: string model: string messages: Array<{ role: string; content: string }> tool_calls: Array<{ name: string input: Record result_preview: string }> } } /** Response from the DLP server. */ export interface InferenceCheckResponse { verdict: 'allow' | 'deny' reason?: string } // ── Credential Masking Types ── /** Full-file masking rule: entire file content replaced with sentinel. */ export interface CredentialFullMaskRule { path: string mode: 'full' } /** Per-extract-pattern configuration with optional field-based extraction. */ export interface CredentialExtractPattern { /** Regex pattern to match. When `field` is set, applied to field value only. */ pattern: string /** Optional replacement string. Defaults to CREDENTIAL_SENTINEL. */ replacement?: string /** 🆕 JSON key to extract before applying pattern. If unset, pattern runs against full content. */ field?: string } /** Extract-based masking rule: only regex-matched tokens are replaced. */ export interface CredentialExtractRule { path: string mode: 'extract' extract: CredentialExtractPattern[] /** 🆕 Behavior when no extract pattern matches: 'mask' (replace all) or 'passthrough' (keep). Default: 'mask'. */ onExtractNoMatch?: 'mask' | 'passthrough' } /** 🆕 JWT-aware masking rule: decode payload and mask specified claims. */ export interface JwtMaskingRule { path: string type: 'jwt' decode: 'jwt' /** Claim names to mask in the JWT payload (e.g. ["sub", "email"]). */ maskClaims: string[] } /** 🆕 AWS credential pair masking rule: detect key pairs and optionally re-sign. */ export interface AwsMaskingRule { path: string type: 'aws' awsPairs: boolean sigv4: boolean } export type CredentialFileRule = CredentialFullMaskRule | CredentialExtractRule | JwtMaskingRule | AwsMaskingRule /** Configuration for credential masking, loaded from config.yml. */ export interface CredentialMaskingConfig { enabled: boolean files: CredentialFileRule[] output_scrubbing: { enabled: boolean patterns: string[] } env_filter: { enabled: boolean patterns: string[] } } // ── Cross-Session Messaging Types ── /** Controls how inbound cross-session messages are handled. */ export type CrossSessionInbound = 'allow' | 'ask' | 'deny' /** 🆕 Configuration for cross-session messaging. */ export interface CrossSessionConfig { crossSessionInbound: CrossSessionInbound dialogExpiry: number // seconds } /** Session information exposed via ListAgents. */ export interface SessionInfo { id: string name: string machine: string pid: number startedAt: string cwd?: string provider?: string model?: string crossSessionInbound?: CrossSessionInbound } // ── Background Agent Types ── export interface BackgroundAgentConfig { auto_commit: boolean auto_push: boolean auto_worktree: boolean commit_coauthors: boolean } // ── CLI 内部类型(引用活服务,不可共享)── export interface ToolContext { cwd: string sessionId: string provider: string model: string skillsLoader?: import('../skills/seam').Skills registry?: import('../providers/registry').ProviderRegistry toolRegistry?: Map artifactServer?: import('../artifacts/server').ArtifactServer agentRegistry?: import('../agent/agent-registry').AgentRegistry backgroundAgentRegistry?: import('../agent/background-registry').BackgroundAgentRegistry permissionSystem?: import('../core/permission').PermissionSystem ruleEngine?: import('../core/rule-engine').ExperienceRuleEngine llm?: import('../providers/llm').Llm /** Files read this session — used by Write tool to check read-before-write */ readFiles?: Set } export interface ToolDefinition { name: string description: string category: ToolCategory permission: ToolPermission parameters: Record execute: (params: Record, ctx: ToolContext) => Promise } export interface SkillDefinition { name: string description: string version: string type: 'standard' | 'mipham' tools?: ToolDefinition[] hooks?: HookDefinition[] prompts?: Record /** Frontmatter: 'fork' means execute in isolated subagent, undefined means inline */ context?: string /** Model override for fork execution */ model?: string /** Tool whitelist for fork mode */ allowedTools?: string[] /** When true, the skill is NOT shown in system-reminder for AI auto-triggering */ disableModelInvocation?: boolean /** External command-line binaries the skill requires (frontmatter: requires-bins). */ requiresBins?: string[] /** The markdown body content of the skill file (instructions for the AI to follow). */ body?: string }