{"version":3,"file":"plan-types.mjs","names":[],"sources":["../../../src/services/plan-types.ts"],"sourcesContent":["/**\n * Plan IR (Intermediate Representation) — the structured format for compound operations.\n *\n * The LLM compiles natural language into this IR. The validator checks it. The scheduler\n * persists it. The executor runs it. Every compound operation — from \"swap ETH to USDC\"\n * to \"if ETH > $4000 at 5pm, sell half then bridge to Arbitrum\" — compiles to a Plan.\n *\n * ─── Design Principles ─────────────────────────────────────────────────\n *\n * 1. Plans are data, not code. A Plan is JSON-serializable, inspectable, diffable.\n *    The LLM produces them. Humans can read them. The validator can reason about them.\n *\n * 2. Small set of orthogonal primitives. Six node types compose into any compound\n *    operation: Action, Sequence, Conditional, Loop, Wait, and Gate. No special-casing.\n *\n * 3. Separation of trigger from execution. A Plan says WHAT to do. A Trigger says\n *    WHEN to start. The scheduler owns triggers; the executor owns plans.\n *\n * 4. Explicit failure modes. Every node can declare `onFailure`: abort the plan,\n *    skip and continue, or retry N times. No implicit behavior.\n *\n * 5. Observable state. Every plan execution produces a PlanExecution record with\n *    per-step status, timestamps, results, and errors. Fully auditable.\n */\n\n// ─── Primitive Value Types ──────────────────────────────────────────────\n\n/** A reference to a value that may come from a previous step's output. */\nexport type ValueRef =\n  | { type: 'literal'; value: string | number | boolean }\n  | { type: 'step_output'; stepId: string; path: string }   // e.g., step \"swap1\" output field \"amountOut\"\n  | { type: 'env'; key: string }                              // e.g., \"ETH_PRICE\", \"WALLET_BALANCE\"\n  | { type: 'runtime'; fn: RuntimeFn; args: ValueRef[] };     // e.g., runtime.price(\"ETH\"), runtime.balance(\"USDC\")\n\n/** Runtime functions the executor can evaluate. */\nexport type RuntimeFn =\n  | 'price'           // price(\"ETH\") → current USD price\n  | 'balance'         // balance(\"USDC\", chainId?) → wallet balance\n  | 'gas_price'       // gas_price(chainId?) → current gas in gwei\n  | 'timestamp'       // timestamp() → current unix seconds\n  | 'block_number';   // block_number(chainId?) → current block\n\n/** Comparison operators for conditions. */\nexport type CompareOp = 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'neq';\n\n/** Logical operators for combining conditions. */\nexport type LogicOp = 'and' | 'or' | 'not';\n\n// ─── Conditions ─────────────────────────────────────────────────────────\n\n/** A single comparison: left <op> right. */\nexport interface CompareCondition {\n  type: 'compare';\n  left: ValueRef;\n  op: CompareOp;\n  right: ValueRef;\n  /** Human-readable description for display. */\n  label?: string;\n}\n\n/** Logical combination of conditions. */\nexport interface LogicCondition {\n  type: 'logic';\n  op: LogicOp;\n  conditions: Condition[];\n}\n\nexport type Condition = CompareCondition | LogicCondition;\n\n// ─── Plan Nodes ─────────────────────────────────────────────────────────\n//\n// Six node types that compose into any compound operation:\n//\n//   Action    — call a tool (defi_swap, transfer, manage_orders, etc.)\n//   Sequence  — run nodes in order, passing outputs forward\n//   Parallel  — run nodes concurrently (for independent read operations)\n//   If        — conditional branching\n//   Wait      — pause until a condition or duration\n//   Loop      — repeat with a condition or count\n\n/** What to do when a node fails. */\nexport type FailurePolicy =\n  | { strategy: 'abort' }                                    // stop the entire plan\n  | { strategy: 'skip'; reason?: string }                    // skip this step, continue\n  | { strategy: 'retry'; maxAttempts: number; delayMs: number; backoffMultiplier?: number }; // retry with exponential backoff\n\n/** Base fields shared by all plan nodes. */\ninterface PlanNodeBase {\n  /** Unique identifier within this plan. Used by ValueRef to reference outputs. */\n  id: string;\n  /** Human-readable label shown to user. */\n  label: string;\n  /** What to do if this node fails. Default: abort. */\n  onFailure?: FailurePolicy;\n  /** Optional timeout for this node in ms. */\n  timeoutMs?: number;\n}\n\n/** Call a tool with parameters. The atomic unit of work. */\nexport interface ActionNode extends PlanNodeBase {\n  type: 'action';\n  /** Tool name (e.g., 'defi_swap', 'transfer', 'manage_orders'). */\n  tool: string;\n  /** Tool parameters. Values can be literals or references to prior step outputs. */\n  params: Record<string, ValueRef | string | number | boolean>;\n  /**\n   * If true, executor must confirm with user before executing (regardless of autosign).\n   * Use for high-value or irreversible operations.\n   */\n  requireConfirmation?: boolean;\n  /**\n   * Fallback branch executed when this action fails after all retries are exhausted.\n   * If present, the executor runs this sub-tree instead of propagating the failure up.\n   * Use for graceful degradation (e.g., swap fails → skip bridge, notify user).\n   */\n  onError?: PlanNode;\n}\n\n/** Run child nodes in order. The output of each node is available to the next. */\nexport interface SequenceNode extends PlanNodeBase {\n  type: 'sequence';\n  steps: PlanNode[];\n}\n\n/** Run child nodes concurrently. Only for independent operations (reads, price checks). */\nexport interface ParallelNode extends PlanNodeBase {\n  type: 'parallel';\n  steps: PlanNode[];\n  /** If true, continue even if some parallel steps fail. Default: false. */\n  allowPartialFailure?: boolean;\n}\n\n/** Conditional branching. */\nexport interface IfNode extends PlanNodeBase {\n  type: 'if';\n  condition: Condition;\n  then: PlanNode;\n  else?: PlanNode;\n}\n\n/**\n * Pause execution until a condition is met or a duration passes.\n * The scheduler polls the condition at `pollIntervalMs`.\n */\nexport interface WaitNode extends PlanNodeBase {\n  type: 'wait';\n  /** Wait until this condition is true. */\n  until?: Condition;\n  /** Wait for a fixed duration (ms). Mutually exclusive with `until`. */\n  durationMs?: number;\n  /** Wait until a specific time (ISO 8601). Mutually exclusive with `until` and `durationMs`. */\n  untilTime?: string;\n  /** How often to check the `until` condition (ms). Default: 60_000 (1 min). */\n  pollIntervalMs?: number;\n  /** Maximum time to wait before giving up (ms). Default: 86_400_000 (24h). */\n  maxWaitMs?: number;\n}\n\n/** Repeat a node. */\nexport interface LoopNode extends PlanNodeBase {\n  type: 'loop';\n  body: PlanNode;\n  /** Stop when this condition is true. Checked after each iteration. */\n  exitWhen?: Condition;\n  /** Maximum iterations. Required as a safety bound. */\n  maxIterations: number;\n  /** Delay between iterations (ms). Default: 0. */\n  delayMs?: number;\n}\n\nexport type PlanNode = ActionNode | SequenceNode | ParallelNode | IfNode | WaitNode | LoopNode;\n\n// ─── Triggers ───────────────────────────────────────────────────────────\n//\n// Triggers determine WHEN a plan starts. A plan without a trigger executes immediately.\n\n/** Execute at a specific time. */\nexport interface TimeTrigger {\n  type: 'time';\n  /** ISO 8601 datetime string (e.g., \"2026-03-06T17:00:00Z\"). */\n  at: string;\n}\n\n/** Execute on a recurring schedule. */\nexport interface IntervalTrigger {\n  type: 'interval';\n  /** Interval in ms between executions. */\n  everyMs: number;\n  /** First execution time (ISO 8601). If omitted, starts immediately. */\n  startAt?: string;\n  /** Stop recurring after this time. */\n  endAt?: string;\n  /** Max total executions. */\n  maxRuns?: number;\n}\n\n/** Execute when a condition becomes true (polled). */\nexport interface ConditionTrigger {\n  type: 'condition';\n  /** The condition to watch. */\n  when: Condition;\n  /** How often to poll (ms). Default: 60_000. */\n  pollIntervalMs?: number;\n  /** Stop watching after this time (ms from creation). */\n  expiresAfterMs?: number;\n  /** If true, trigger fires every time condition is met (not just first). Default: false. */\n  recurring?: boolean;\n}\n\n/** Execute immediately. */\nexport interface ImmediateTrigger {\n  type: 'immediate';\n}\n\n/** Execute on a cron schedule (e.g., \"0 9 * * 1\" for every Monday at 9am). */\nexport interface CronTrigger {\n  type: 'cron';\n  /** Standard 5-field cron expression: minute hour day-of-month month day-of-week. */\n  expression: string;\n  /** IANA timezone (e.g., \"America/New_York\"). Default: UTC. */\n  timezone?: string;\n  /** Max total executions. */\n  maxRuns?: number;\n}\n\n/** Execute when a price threshold is breached. */\nexport interface PriceTrigger {\n  type: 'price';\n  /** Token symbol to watch (e.g., \"ETH\", \"BTC\"). */\n  token: string;\n  /** Trigger when price is above, below, or crosses the threshold. */\n  condition: 'above' | 'below' | 'crosses';\n  /** Price threshold in USD. */\n  threshold: number;\n  /** Hysteresis: price must move this % past threshold before re-triggering. Default: 1. */\n  hysteresisPercent?: number;\n  /** Minimum cooldown between triggers in ms. Default: 300_000 (5 min). */\n  cooldownMs?: number;\n  /** If true, trigger fires every time condition is met (not just first). Default: false. */\n  recurring?: boolean;\n}\n\n/** Execute when an on-chain event is detected (contract log). */\nexport interface OnChainEventTrigger {\n  type: 'onchain_event';\n  /** Chain ID to monitor. */\n  chainId: number;\n  /** Contract address to watch. */\n  contractAddress: string;\n  /** Event signature to filter (e.g. 'Transfer(address,address,uint256)'). */\n  eventSignature: string;\n  /** Optional topic filters (indexed params). null = any value. */\n  topicFilters?: (string | null)[];\n  /** If true, trigger fires on every matching event. Default: false (one-shot). */\n  recurring?: boolean;\n}\n\n/** Execute when a token balance crosses a threshold. */\nexport interface BalanceTrigger {\n  type: 'balance';\n  /** Token symbol or contract address. */\n  token: string;\n  /** Chain ID. Default: 8453 (Base). */\n  chainId?: number;\n  /** Condition: balance drops below or rises above threshold. */\n  condition: 'above' | 'below';\n  /** Balance threshold (in token units, not wei). */\n  threshold: number;\n  /** If true, trigger fires repeatedly. Default: false. */\n  recurring?: boolean;\n}\n\nexport type Trigger = TimeTrigger | IntervalTrigger | ConditionTrigger | ImmediateTrigger | CronTrigger | PriceTrigger | OnChainEventTrigger | BalanceTrigger;\n\n// ─── Plan ───────────────────────────────────────────────────────────────\n\nexport type PlanStatus = 'draft' | 'validated' | 'scheduled' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';\n\nexport interface Plan {\n  /** Unique plan ID. Generated on creation. */\n  id: string;\n  /** Human-readable name (e.g., \"Sell ETH at $4000 and bridge to Arbitrum\"). */\n  name: string;\n  /** The user who created this plan. */\n  userId: string;\n  /** When was this plan created. */\n  createdAt: number;\n  /** Current status. */\n  status: PlanStatus;\n\n  /** When to start execution. If omitted, executes immediately. */\n  trigger?: Trigger;\n  /** The operation tree. */\n  root: PlanNode;\n\n  /** Validation results (populated by PlanValidator). */\n  validation?: ValidationResult;\n\n  /** Tags for organization (e.g., ['swap', 'eth', 'scheduled']). */\n  tags?: string[];\n  /** The original natural language request that produced this plan. */\n  naturalLanguage?: string;\n}\n\n// ─── Validation ─────────────────────────────────────────────────────────\n\nexport type IssueSeverity = 'error' | 'warning' | 'info';\n\nexport interface ValidationIssue {\n  severity: IssueSeverity;\n  /** Which node caused this issue (by id). Null for plan-level issues. */\n  nodeId: string | null;\n  /** Issue code for programmatic handling. */\n  code: string;\n  /** Human-readable description. */\n  message: string;\n}\n\nexport interface ValidationResult {\n  valid: boolean;\n  issues: ValidationIssue[];\n  /** Estimated total gas cost in ETH (rough). */\n  estimatedGasEth?: number;\n  /** Estimated total time to complete (ms). */\n  estimatedDurationMs?: number;\n  /** Tools this plan will use. */\n  toolsUsed: string[];\n  /** Chains this plan will touch. */\n  chainsUsed: number[];\n}\n\n// ─── Execution Tracking ─────────────────────────────────────────────────\n\nexport type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'waiting';\n\nexport interface StepExecution {\n  nodeId: string;\n  status: StepStatus;\n  startedAt?: number;\n  completedAt?: number;\n  /** The result returned by the tool (for action nodes). */\n  result?: unknown;\n  /** Error message if failed. */\n  error?: string;\n  /** Number of retry attempts made. */\n  retryCount?: number;\n}\n\nexport interface PlanExecution {\n  planId: string;\n  /** Unique execution ID (a plan can execute multiple times via interval triggers). */\n  executionId: string;\n  status: 'running' | 'completed' | 'failed' | 'cancelled';\n  startedAt: number;\n  completedAt?: number;\n  steps: StepExecution[];\n  /** Cumulative gas spent in ETH. */\n  gasSpentEth?: number;\n}\n\n// ─── Templates ──────────────────────────────────────────────────────────\n\n/** A reusable plan template with parameterizable fields. */\nexport interface PlanTemplate {\n  /** Unique template ID. */\n  id: string;\n  /** Human-readable name (e.g., \"DCA ETH weekly\"). */\n  name: string;\n  /** Description of what this template does. */\n  description?: string;\n  /** The user who created this template. */\n  createdBy: string;\n  /** When this template was saved. */\n  createdAt: number;\n  /** Tags for organization. */\n  tags?: string[];\n  /**\n   * The intent that created this template. Stored so it can be\n   * re-compiled with different parameters.\n   */\n  intent: {\n    naturalLanguage: string;\n    steps: Record<string, unknown>[];\n    trigger?: Record<string, unknown>;\n    tags?: string[];\n  };\n  /**\n   * Parameter placeholders. Each key is a placeholder name (e.g., \"amount\"),\n   * and the value describes where it appears and its default.\n   */\n  params?: Record<string, {\n    description?: string;\n    default?: string | number;\n    required?: boolean;\n  }>;\n}\n\n// ─── Plan Store Interface ───────────────────────────────────────────────\n\nexport interface PlanStore {\n  save(plan: Plan): void;\n  load(planId: string): Plan | null;\n  loadAll(userId?: string): Plan[];\n  delete(planId: string): boolean;\n  saveExecution(exec: PlanExecution): void;\n  loadExecutions(planId: string): PlanExecution[];\n}\n\n// ─── Dead-Letter Log ────────────────────────────────────────────────────\n// Records terminal failures after all retries + fallbacks are exhausted.\n\nexport interface DeadLetterEntry {\n  /** The plan that failed. */\n  planId: string;\n  /** The node that failed. */\n  nodeId: string;\n  /** The execution ID. */\n  executionId: string;\n  /** The user who owns the plan. */\n  userId: string;\n  /** Error message from the final attempt. */\n  error: string;\n  /** Number of retry attempts made. */\n  retryCount: number;\n  /** Tool name (for action nodes). */\n  tool?: string;\n  /** Resolved params at time of failure. */\n  params?: Record<string, unknown>;\n  /** When the failure occurred. */\n  timestamp: number;\n}\n\n// ─── Execution Checkpoints ──────────────────────────────────────────────\n// Persisted state for durable execution — survives process restarts.\n\nexport interface ExecutionCheckpoint {\n  /** The execution ID. */\n  executionId: string;\n  /** The plan being executed. */\n  planId: string;\n  /** The user who owns the plan. */\n  userId: string;\n  /** ID of the node currently being executed (or next to execute). */\n  currentNodeId: string;\n  /** Results from completed steps, serialized as [nodeId, result] tuples. */\n  stepResults: Array<[string, unknown]>;\n  /** Step execution records so far. */\n  steps: StepExecution[];\n  /** Execution status. */\n  status: 'running' | 'paused';\n  /** When execution started. */\n  startedAt: number;\n  /** When this checkpoint was written. */\n  updatedAt: number;\n}\n\n// ─── Contradiction Codes ────────────────────────────────────────────────\n// Used by the validator to identify specific contradiction types.\n\nexport const CONTRADICTION_CODES = {\n  BUY_AND_SELL_SAME_TOKEN: 'CONTRA_BUY_SELL',\n  SPEND_MORE_THAN_BALANCE: 'CONTRA_OVERSPEND',\n  OPPOSITE_CONDITIONS: 'CONTRA_OPPOSITE_COND',\n  IMPOSSIBLE_TIMING: 'CONTRA_TIMING',\n  CIRCULAR_DEPENDENCY: 'CONTRA_CIRCULAR',\n  DUPLICATE_ACTION: 'CONTRA_DUPLICATE',\n  CONFLICTING_SLIPPAGE: 'CONTRA_SLIPPAGE',\n  UNSUPPORTED_CHAIN: 'CONTRA_CHAIN',\n  MISSING_TOOL: 'CONTRA_NO_TOOL',\n  INFINITE_LOOP: 'CONTRA_INFINITE',\n} as const;\n\nexport type ContradictionCode = typeof CONTRADICTION_CODES[keyof typeof CONTRADICTION_CODES];\n"],"mappings":";AA2cA,MAAa,sBAAsB;CACjC,yBAAyB;CACzB,yBAAyB;CACzB,qBAAqB;CACrB,mBAAmB;CACnB,qBAAqB;CACrB,kBAAkB;CAClB,sBAAsB;CACtB,mBAAmB;CACnB,cAAc;CACd,eAAe;CAChB"}