{"version":3,"file":"plan-compiler.mjs","names":[],"sources":["../../../src/services/plan-compiler.ts"],"sourcesContent":["/**\n * Plan Compiler — transforms structured intent (from LLM) into Plan IR.\n *\n * The compiler does NOT do NLP. The LLM does the NLP (understanding \"sell half my ETH\n * at $4000 then bridge to Arbitrum\") and produces a structured intent object. The\n * compiler validates and transforms that intent into a fully-formed Plan IR.\n *\n * This exists because:\n * 1. The LLM shouldn't have to produce the full Plan IR with all its boilerplate.\n *    It should express intent, and the compiler fills in IDs, labels, defaults, etc.\n * 2. The compiler can catch common LLM mistakes (missing required fields, wrong tool\n *    names, impossible conditions) before the plan even reaches the validator.\n * 3. It provides a stable API surface — even if the Plan IR evolves, the intent format\n *    can stay backwards-compatible.\n *\n * ─── Intent Format ──────────────────────────────────────────────────────\n *\n * An Intent is a simplified representation of what the user wants. The LLM produces\n * this from natural language. Example:\n *\n *   User: \"When ETH hits $4000, sell half my ETH for USDC, then bridge 500 USDC to Arbitrum\"\n *\n *   Intent: {\n *     trigger: { type: 'condition', token: 'ETH', op: 'gte', value: 4000 },\n *     steps: [\n *       { action: 'swap', tokenIn: 'ETH', tokenOut: 'USDC', amountPct: 50 },\n *       { action: 'bridge', token: 'USDC', amount: '500', toChain: 42161 },\n *     ]\n *   }\n */\n\nimport type {\n  Plan,\n  PlanNode,\n  ActionNode,\n  SequenceNode,\n  ParallelNode,\n  IfNode,\n  WaitNode,\n  LoopNode,\n  Trigger,\n  Condition,\n  ValueRef,\n  CompareOp,\n} from './plan-types.js';\n\n// ─── Intent Types ───────────────────────────────────────────────────────\n\nexport interface IntentTrigger {\n  type: 'immediate' | 'at_time' | 'every' | 'when_condition';\n  /** ISO 8601 time for 'at_time'. */\n  time?: string;\n  /** Interval string for 'every' (e.g., '4h', '30m', '1d'). */\n  interval?: string;\n  /** Max runs for recurring triggers. */\n  maxRuns?: number;\n  /** Condition fields for 'when_condition'. */\n  token?: string;\n  op?: 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'neq';\n  value?: number;\n  /** Compound condition: AND/OR multiple conditions. */\n  logic?: 'and' | 'or';\n  conditions?: IntentTrigger[];\n  /** Expiry for condition watches (e.g., '24h', '7d'). */\n  expires?: string;\n}\n\n/** Step type discriminator — 'action' is the default for backward compatibility. */\nexport type IntentStepType = 'action' | 'parallel' | 'wait' | 'loop';\n\nexport interface IntentStep {\n  /**\n   * Step type. Defaults to 'action' if omitted (backward compatible).\n   *   - 'action' — call a tool (swap, transfer, bridge, etc.)\n   *   - 'parallel' — run nested steps concurrently\n   *   - 'wait' — pause until a condition or duration\n   *   - 'loop' — repeat nested steps with exit condition\n   */\n  type?: IntentStepType;\n\n  /** The action to perform (required when type is 'action' or omitted). */\n  action?: 'swap' | 'transfer' | 'bridge' | 'check_price' | 'check_balance'\n    | 'set_order' | 'approve' | 'launch' | 'claim' | 'custom';\n\n  // ── Swap params\n  tokenIn?: string;\n  tokenOut?: string;\n  amount?: string;\n  amountPct?: number;        // \"sell half\" → 50\n  slippageBps?: number;\n  chainId?: number;\n\n  // ── Transfer params\n  to?: string;\n  token?: string;\n\n  // ── Bridge params\n  toChain?: number;\n  fromChain?: number;\n\n  // ── Order params\n  orderType?: string;        // 'limit_buy', 'stop_loss', etc.\n  triggerPrice?: number;\n\n  // ── Custom tool call\n  tool?: string;\n  params?: Record<string, unknown>;\n\n  // ── Flow control\n  /** If provided, makes this step conditional. */\n  condition?: {\n    token?: string;\n    field?: 'price' | 'balance' | 'gas_price';\n    op: CompareOp;\n    value: number;\n  };\n\n  /** If true, require user confirmation before this step. */\n  confirm?: boolean;\n\n  /** Failure policy for this step. */\n  onFailure?: 'abort' | 'skip' | 'retry';\n  retryCount?: number;\n  /** Delay between retries in ms. Default: 5000. */\n  retryDelayMs?: number;\n  /** Backoff multiplier for exponential retry delay. Default: 2. */\n  backoffMultiplier?: number;\n\n  /** Fallback steps to execute if this step fails after all retries. */\n  onError?: IntentStep[];\n\n  /** Label override. */\n  label?: string;\n\n  // ── Step-output data flow (10.2)\n  /** Assign a ref name to this step's output so downstream steps can reference it. */\n  outputRef?: string;\n  /** Map param names to \"refName.path\" strings referencing prior step outputs. */\n  inputRefs?: Record<string, string>;\n\n  // ── Parallel step params\n  /** Nested steps for 'parallel' and 'loop' types. */\n  steps?: IntentStep[];\n  /** Whether to continue if some parallel steps fail (default false). */\n  allowPartialFailure?: boolean;\n\n  // ── Wait step params\n  /** Duration string for 'wait' type (e.g., \"30s\", \"5m\", \"1h\"). */\n  duration?: string;\n  /** ISO 8601 time to wait until. */\n  untilTime?: string;\n  /** Condition to wait for (same format as step condition). */\n  until?: {\n    token?: string;\n    field?: 'price' | 'balance' | 'gas_price';\n    op: CompareOp;\n    value: number;\n  };\n  /** Max wait time for condition waits (e.g., \"24h\"). Default: 24h. */\n  maxWait?: string;\n  /** Poll interval for condition waits (e.g., \"1m\"). Default: 1m. */\n  pollInterval?: string;\n\n  // ── Loop step params\n  /** Exit condition for 'loop' type. */\n  exitWhen?: {\n    token?: string;\n    field?: 'price' | 'balance' | 'gas_price';\n    op: CompareOp;\n    value: number;\n  };\n  /** Max iterations for 'loop' type (default 10). */\n  maxIterations?: number;\n  /** Delay between loop iterations (e.g., \"5s\"). */\n  delayBetween?: string;\n}\n\nexport interface Intent {\n  /** Human-readable name for the plan. */\n  name?: string;\n  /** The original natural language request. */\n  naturalLanguage: string;\n  /** When to start execution. */\n  trigger?: IntentTrigger;\n  /** Steps to execute (in order). */\n  steps: IntentStep[];\n  /** Tags for organization. */\n  tags?: string[];\n}\n\n// ─── Compiler ───────────────────────────────────────────────────────────\n\nexport class PlanCompiler {\n  private idCounter = 0;\n  /** Maps outputRef names to generated node IDs for step_output resolution. */\n  private outputRefToId = new Map<string, string>();\n\n  /**\n   * Compile an intent into a Plan IR.\n   * Throws CompilationError on invalid intents.\n   */\n  compile(intent: Intent, userId: string): Plan {\n    this.idCounter = 0;\n    this.outputRefToId = new Map();\n\n    if (!intent.steps || intent.steps.length === 0) {\n      throw new CompilationError('Intent has no steps.');\n    }\n\n    const trigger = intent.trigger ? this.compileTrigger(intent.trigger) : undefined;\n    const rootSteps = intent.steps.map(step => this.compileNode(step));\n\n    const root: PlanNode = rootSteps.length === 1\n      ? rootSteps[0]!\n      : {\n        id: this.nextId('seq'),\n        label: 'Main sequence',\n        type: 'sequence',\n        steps: rootSteps,\n      } as SequenceNode;\n\n    const plan: Plan = {\n      id: `plan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,\n      name: intent.name ?? this.inferName(intent),\n      userId,\n      createdAt: Date.now(),\n      status: trigger ? 'draft' : 'draft',\n      trigger,\n      root,\n      tags: intent.tags,\n      naturalLanguage: intent.naturalLanguage,\n    };\n\n    return plan;\n  }\n\n  // ─── Trigger Compilation ────────────────────────────────────────────\n\n  private compileTrigger(t: IntentTrigger): Trigger {\n    switch (t.type) {\n      case 'immediate':\n        return { type: 'immediate' };\n\n      case 'at_time': {\n        if (!t.time) throw new CompilationError('at_time trigger requires a \"time\" field (ISO 8601).');\n        const parsed = new Date(t.time);\n        if (isNaN(parsed.getTime())) throw new CompilationError(`Invalid time: \"${t.time}\". Use ISO 8601 format.`);\n        return { type: 'time', at: parsed.toISOString() };\n      }\n\n      case 'every': {\n        if (!t.interval) throw new CompilationError('every trigger requires an \"interval\" field (e.g., \"4h\", \"30m\").');\n        const ms = parseIntervalToMs(t.interval);\n        if (ms < 30_000) throw new CompilationError('Interval must be at least 30 seconds.');\n        return {\n          type: 'interval',\n          everyMs: ms,\n          maxRuns: t.maxRuns,\n        };\n      }\n\n      case 'when_condition': {\n        // Simple condition\n        if (t.token && t.op && t.value !== undefined) {\n          return {\n            type: 'condition',\n            when: this.buildPriceCondition(t.token, t.op, t.value),\n            pollIntervalMs: 60_000,\n            expiresAfterMs: t.expires ? parseIntervalToMs(t.expires) : 7 * 24 * 60 * 60 * 1000, // 7d default\n          };\n        }\n\n        // Compound condition\n        if (t.logic && t.conditions && t.conditions.length > 0) {\n          const subConditions = t.conditions.map(sub => {\n            if (!sub.token || !sub.op || sub.value === undefined) {\n              throw new CompilationError('Each sub-condition needs token, op, and value.');\n            }\n            return this.buildPriceCondition(sub.token, sub.op, sub.value);\n          });\n          return {\n            type: 'condition',\n            when: { type: 'logic', op: t.logic, conditions: subConditions },\n            pollIntervalMs: 60_000,\n            expiresAfterMs: t.expires ? parseIntervalToMs(t.expires) : 7 * 24 * 60 * 60 * 1000,\n          };\n        }\n\n        throw new CompilationError('when_condition trigger needs (token + op + value) or (logic + conditions[]).');\n      }\n    }\n  }\n\n  private buildPriceCondition(token: string, op: CompareOp, value: number): Condition {\n    return {\n      type: 'compare',\n      left: { type: 'runtime', fn: 'price', args: [{ type: 'literal', value: token }] },\n      op,\n      right: { type: 'literal', value },\n      label: `${token} price ${opSymbol(op)} $${value}`,\n    };\n  }\n\n  // ─── Node Compilation (recursive) ───────────────────────────────────\n  // Compiles an IntentStep into a PlanNode. Handles all step types:\n  // action (default), parallel, wait, loop.\n\n  private compileNode(step: IntentStep): PlanNode {\n    const stepType = step.type ?? 'action';\n\n    switch (stepType) {\n      case 'parallel':\n        return this.compileParallel(step);\n      case 'wait':\n        return this.compileWait(step);\n      case 'loop':\n        return this.compileLoop(step);\n      case 'action':\n      default:\n        return this.compileAction(step);\n    }\n  }\n\n  // ─── Action Step ─────────────────────────────────────────────────────\n\n  private compileAction(step: IntentStep): PlanNode {\n    const action = step.action ?? 'custom';\n    const id = step.outputRef ?? this.nextId(action);\n    const failurePolicy = this.compileFailurePolicy(step);\n\n    // Register outputRef → id mapping for step_output resolution\n    if (step.outputRef) {\n      this.outputRefToId.set(step.outputRef, id);\n    }\n\n    // Resolve inputRefs → step_output ValueRefs in params\n    const extraParams: Record<string, ValueRef> = {};\n    if (step.inputRefs) {\n      for (const [paramName, refStr] of Object.entries(step.inputRefs)) {\n        const dotIdx = refStr.indexOf('.');\n        if (dotIdx < 0) throw new CompilationError(`inputRef \"${refStr}\" must be \"refName.path\" (e.g., \"swap1.amountOut\").`);\n        const refName = refStr.slice(0, dotIdx);\n        const path = refStr.slice(dotIdx + 1);\n        const stepId = this.outputRefToId.get(refName);\n        if (!stepId) throw new CompilationError(`inputRef \"${refStr}\" references unknown outputRef \"${refName}\". Ensure the step with outputRef=\"${refName}\" appears earlier.`);\n        extraParams[paramName] = { type: 'step_output', stepId, path };\n      }\n    }\n\n    let actionNode: ActionNode;\n\n    switch (action) {\n      case 'swap':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Swap ${step.amountPct ? step.amountPct + '% ' : ''}${step.tokenIn ?? '?'} → ${step.tokenOut ?? '?'}`,\n          tool: 'defi_swap',\n          params: {\n            action: 'execute',\n            ...(step.tokenIn && { token_in: step.tokenIn }),\n            ...(step.tokenOut && { token_out: step.tokenOut }),\n            ...(step.amount && { amount: step.amount }),\n            ...(step.amountPct !== undefined && { amount_pct: step.amountPct }),\n            ...(step.slippageBps !== undefined && { slippage_bps: step.slippageBps }),\n            ...(step.chainId !== undefined && { chain_id: step.chainId }),\n            ...extraParams,\n          },\n          requireConfirmation: step.confirm ?? true,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'transfer':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Transfer ${step.amount ?? '?'} ${step.token ?? 'ETH'} to ${step.to ? truncateAddr(step.to) : '?'}`,\n          tool: 'transfer',\n          params: {\n            action: 'send',\n            ...(step.token && { token: step.token }),\n            ...(step.amount && { amount: step.amount }),\n            ...(step.to && { to: step.to }),\n            ...extraParams,\n          },\n          requireConfirmation: step.confirm ?? true,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'bridge':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Bridge ${step.amount ?? '?'} ${step.token ?? '?'} to chain ${step.toChain ?? '?'}`,\n          tool: 'bridge',\n          params: {\n            action: 'execute',\n            ...(step.token && { token: step.token }),\n            ...(step.amount && { amount: step.amount }),\n            ...(step.toChain !== undefined && { to_chain_id: step.toChain }),\n            ...(step.fromChain !== undefined && { from_chain_id: step.fromChain }),\n            ...extraParams,\n          },\n          requireConfirmation: step.confirm ?? true,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'check_price':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Check ${step.token ?? 'ETH'} price`,\n          tool: 'defi_price',\n          params: {\n            action: 'lookup',\n            ...(step.token && { token: step.token }),\n            ...extraParams,\n          },\n          requireConfirmation: false,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'check_balance':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Check ${step.token ?? 'all'} balance`,\n          tool: 'defi_balance',\n          params: {\n            ...(step.token && { token: step.token }),\n            ...extraParams,\n          },\n          requireConfirmation: false,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'set_order':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Set ${step.orderType ?? 'limit'} order for ${step.token ?? step.tokenOut ?? '?'}`,\n          tool: 'manage_orders',\n          params: {\n            action: 'create',\n            ...(step.orderType && { order_type: step.orderType }),\n            ...(step.token && { token: step.token }),\n            ...(step.tokenIn && { token_in: step.tokenIn }),\n            ...(step.tokenOut && { token_out: step.tokenOut }),\n            ...(step.amount && { amount: step.amount }),\n            ...(step.triggerPrice !== undefined && { trigger_price: step.triggerPrice }),\n            ...extraParams,\n          },\n          requireConfirmation: step.confirm ?? false,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'approve':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Approve ${step.token ?? '?'}`,\n          tool: 'permit2',\n          params: {\n            action: 'approve',\n            ...(step.token && { token: step.token }),\n            ...extraParams,\n          },\n          requireConfirmation: step.confirm ?? true,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'launch':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? 'Launch token',\n          tool: 'clawnch_launch',\n          params: { ...(step.params ?? {}), ...extraParams } as Record<string, string | number | boolean | ValueRef>,\n          requireConfirmation: step.confirm ?? true,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'claim':\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? 'Claim fees/rewards',\n          tool: 'clawnch_fees',\n          params: {\n            action: 'claim',\n            ...(step.token && { token: step.token }),\n            ...extraParams,\n          },\n          requireConfirmation: step.confirm ?? false,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      case 'custom':\n        if (!step.tool) throw new CompilationError('custom action requires a \"tool\" field.');\n        actionNode = {\n          id,\n          type: 'action',\n          label: step.label ?? `Run ${step.tool}`,\n          tool: step.tool,\n          params: { ...(step.params ?? {}), ...extraParams } as Record<string, string | number | boolean | ValueRef>,\n          requireConfirmation: step.confirm ?? false,\n          onFailure: failurePolicy,\n        };\n        break;\n\n      default:\n        throw new CompilationError(`Unknown action: \"${action}\".`);\n    }\n\n    // Compile onError fallback branch\n    const onErrorNode = this.compileOnError(step);\n    if (onErrorNode) {\n      actionNode.onError = onErrorNode;\n    }\n\n    // Wrap in IfNode if step has an inline condition\n    if (step.condition) {\n      const cond = this.buildStepCondition(step.condition);\n      return {\n        id: this.nextId('if'),\n        type: 'if',\n        label: `If ${step.condition.field ?? 'price'}(${step.condition.token ?? '?'}) ${opSymbol(step.condition.op)} ${step.condition.value}`,\n        condition: cond,\n        then: actionNode,\n      } as IfNode;\n    }\n\n    return actionNode;\n  }\n\n  // ─── Parallel Step ───────────────────────────────────────────────────\n\n  private compileParallel(step: IntentStep): PlanNode {\n    if (!step.steps || step.steps.length === 0) {\n      throw new CompilationError('parallel step requires a non-empty \"steps\" array.');\n    }\n    const children = step.steps.map(s => this.compileNode(s));\n    return {\n      id: this.nextId('par'),\n      type: 'parallel',\n      label: step.label ?? `Parallel (${children.length} steps)`,\n      steps: children,\n      allowPartialFailure: step.allowPartialFailure ?? false,\n      onFailure: this.compileFailurePolicy(step),\n    } as ParallelNode;\n  }\n\n  // ─── Wait Step ───────────────────────────────────────────────────────\n\n  private compileWait(step: IntentStep): PlanNode {\n    const id = this.nextId('wait');\n    const node: WaitNode = {\n      id,\n      type: 'wait',\n      label: step.label ?? 'Wait',\n      onFailure: this.compileFailurePolicy(step),\n    };\n\n    if (step.duration) {\n      node.durationMs = parseIntervalToMs(step.duration);\n      node.label = step.label ?? `Wait ${step.duration}`;\n    } else if (step.untilTime) {\n      const parsed = new Date(step.untilTime);\n      if (isNaN(parsed.getTime())) throw new CompilationError(`Invalid untilTime: \"${step.untilTime}\". Use ISO 8601.`);\n      node.untilTime = parsed.toISOString();\n      node.label = step.label ?? `Wait until ${step.untilTime}`;\n    } else if (step.until) {\n      node.until = this.buildStepCondition(step.until);\n      node.pollIntervalMs = step.pollInterval ? parseIntervalToMs(step.pollInterval) : 60_000;\n      node.maxWaitMs = step.maxWait ? parseIntervalToMs(step.maxWait) : 86_400_000;\n      const field = step.until.field ?? 'price';\n      node.label = step.label ?? `Wait until ${step.until.token ?? '?'} ${field} ${opSymbol(step.until.op)} ${step.until.value}`;\n    } else {\n      throw new CompilationError('wait step requires \"duration\", \"untilTime\", or \"until\" condition.');\n    }\n\n    return node;\n  }\n\n  // ─── Loop Step ───────────────────────────────────────────────────────\n\n  private compileLoop(step: IntentStep): PlanNode {\n    if (!step.steps || step.steps.length === 0) {\n      throw new CompilationError('loop step requires a non-empty \"steps\" array.');\n    }\n    const bodySteps = step.steps.map(s => this.compileNode(s));\n    const body: PlanNode = bodySteps.length === 1\n      ? bodySteps[0]!\n      : { id: this.nextId('seq'), type: 'sequence', label: 'Loop body', steps: bodySteps } as SequenceNode;\n\n    const node: LoopNode = {\n      id: this.nextId('loop'),\n      type: 'loop',\n      label: step.label ?? `Loop (max ${step.maxIterations ?? 10} iterations)`,\n      body,\n      maxIterations: step.maxIterations ?? 10,\n      onFailure: this.compileFailurePolicy(step),\n    };\n\n    if (step.exitWhen) {\n      node.exitWhen = this.buildStepCondition(step.exitWhen);\n    }\n    if (step.delayBetween) {\n      node.delayMs = parseIntervalToMs(step.delayBetween);\n    }\n\n    return node;\n  }\n\n  private buildStepCondition(c: NonNullable<IntentStep['condition']>): Condition {\n    const field = c.field ?? 'price';\n    const token = c.token ?? 'ETH';\n\n    let fn: 'price' | 'balance' | 'gas_price';\n    if (field === 'balance') fn = 'balance';\n    else if (field === 'gas_price') fn = 'gas_price';\n    else fn = 'price';\n\n    return {\n      type: 'compare',\n      left: { type: 'runtime', fn, args: [{ type: 'literal', value: token }] },\n      op: c.op,\n      right: { type: 'literal', value: c.value },\n      label: `${token} ${field} ${opSymbol(c.op)} ${c.value}`,\n    };\n  }\n\n  private compileFailurePolicy(step: IntentStep) {\n    if (!step.onFailure || step.onFailure === 'abort') return undefined; // default\n    if (step.onFailure === 'skip') return { strategy: 'skip' as const };\n    if (step.onFailure === 'retry') {\n      return {\n        strategy: 'retry' as const,\n        maxAttempts: step.retryCount ?? 3,\n        delayMs: step.retryDelayMs ?? 5_000,\n        backoffMultiplier: step.backoffMultiplier ?? 2,\n      };\n    }\n    return undefined;\n  }\n\n  /** Compile onError fallback steps into a PlanNode sub-tree. */\n  private compileOnError(step: IntentStep): PlanNode | undefined {\n    if (!step.onError || step.onError.length === 0) return undefined;\n    if (step.onError.length === 1) {\n      return this.compileNode(step.onError[0]!);\n    }\n    // Multiple fallback steps → wrap in sequence\n    return {\n      id: this.nextId('err_seq'),\n      type: 'sequence',\n      label: 'Error fallback',\n      steps: step.onError.map(s => this.compileNode(s)),\n    };\n  }\n\n  // ─── Helpers ────────────────────────────────────────────────────────\n\n  private nextId(prefix: string): string {\n    return `${prefix}_${++this.idCounter}`;\n  }\n\n  private inferName(intent: Intent): string {\n    if (intent.steps.length === 1) {\n      const step = intent.steps[0]!;\n      return step.label ?? `${step.action} ${step.tokenIn ?? step.token ?? ''}`.trim();\n    }\n    const actions = intent.steps.map(s => s.action).join(' → ');\n    const triggerDesc = intent.trigger?.type === 'at_time' ? ` at ${intent.trigger.time}`\n      : intent.trigger?.type === 'when_condition' ? ` when ${intent.trigger.token ?? ''} ${opSymbol(intent.trigger.op ?? 'gte')} $${intent.trigger.value ?? '?'}`\n      : '';\n    return `${actions}${triggerDesc}`;\n  }\n}\n\n// ─── Errors ─────────────────────────────────────────────────────────────\n\nexport class CompilationError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'CompilationError';\n  }\n}\n\n// ─── Utilities ──────────────────────────────────────────────────────────\n\nfunction opSymbol(op: string): string {\n  switch (op) {\n    case 'gt': return '>';\n    case 'gte': return '>=';\n    case 'lt': return '<';\n    case 'lte': return '<=';\n    case 'eq': return '=';\n    case 'neq': return '!=';\n    default: return op;\n  }\n}\n\nfunction truncateAddr(addr: string): string {\n  if (addr.length <= 10) return addr;\n  return `${addr.slice(0, 6)}...${addr.slice(-4)}`;\n}\n\n/**\n * Parse human-readable intervals to milliseconds.\n * Supports: 30s, 5m, 4h, 1d, 2w\n */\nexport function parseIntervalToMs(input: string): number {\n  const match = input.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*(s|sec|m|min|h|hr|d|day|w|wk)s?$/i);\n  if (!match) throw new CompilationError(`Invalid interval format: \"${input}\". Use e.g., \"30s\", \"5m\", \"4h\", \"1d\", \"2w\".`);\n\n  const value = parseFloat(match[1]!);\n  const unit = match[2]!.toLowerCase();\n\n  const multipliers: Record<string, number> = {\n    s: 1000, sec: 1000,\n    m: 60_000, min: 60_000,\n    h: 3_600_000, hr: 3_600_000,\n    d: 86_400_000, day: 86_400_000,\n    w: 604_800_000, wk: 604_800_000,\n  };\n\n  const ms = value * (multipliers[unit] ?? 1000);\n  if (ms <= 0 || !isFinite(ms)) throw new CompilationError(`Invalid interval: \"${input}\".`);\n  return Math.round(ms);\n}\n"],"mappings":"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"}