{"version":3,"file":"interrupt-service.mjs","names":[],"sources":["../../../src/services/interrupt-service.ts"],"sourcesContent":["/**\n * Interrupt Service — shared interrupt flag per session.\n *\n * When `/interrupt` is called, the flag is set for the current session.\n * The `message_sending` hook checks the flag and cancels the LLM response.\n * The `after_tool_call` hook checks the flag to skip further tool calls.\n *\n * Flags auto-expire after TTL_MS to prevent stale interrupts from\n * silencing future responses.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\ninterface InterruptFlag {\n  /** When the interrupt was requested. */\n  timestamp: number;\n  /** Reason (optional, for logging). */\n  reason?: string;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────\n\n/** Interrupt flags expire after 30 seconds. */\nconst TTL_MS = 30_000;\n\n/** Sweep stale entries every 60 seconds. */\nconst SWEEP_INTERVAL_MS = 60_000;\n\n// ─── Service ─────────────────────────────────────────────────────────────\n\nclass InterruptService {\n  private flags = new Map<string, InterruptFlag>();\n  private sweepTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);\n    // Unref so the timer doesn't keep the process alive\n    if (this.sweepTimer && typeof this.sweepTimer.unref === 'function') {\n      this.sweepTimer.unref();\n    }\n  }\n\n  /**\n   * Set the interrupt flag for a session.\n   * Returns true if a flag was newly set (wasn't already active).\n   */\n  interrupt(sessionKey: string, reason?: string): boolean {\n    const existing = this.flags.get(sessionKey);\n    if (existing && Date.now() - existing.timestamp < TTL_MS) {\n      return false; // Already interrupted\n    }\n    this.flags.set(sessionKey, { timestamp: Date.now(), reason });\n    return true;\n  }\n\n  /**\n   * Check and consume the interrupt flag.\n   * Returns true if an interrupt was pending (and consumes it).\n   */\n  consume(sessionKey: string): boolean {\n    const flag = this.flags.get(sessionKey);\n    if (!flag) return false;\n\n    // Check expiry\n    if (Date.now() - flag.timestamp > TTL_MS) {\n      this.flags.delete(sessionKey);\n      return false;\n    }\n\n    this.flags.delete(sessionKey);\n    return true;\n  }\n\n  /**\n   * Check if an interrupt is pending without consuming it.\n   */\n  isPending(sessionKey: string): boolean {\n    const flag = this.flags.get(sessionKey);\n    if (!flag) return false;\n    if (Date.now() - flag.timestamp > TTL_MS) {\n      this.flags.delete(sessionKey);\n      return false;\n    }\n    return true;\n  }\n\n  /** Remove expired entries. */\n  private sweep(): void {\n    const now = Date.now();\n    for (const [key, flag] of this.flags) {\n      if (now - flag.timestamp > TTL_MS) {\n        this.flags.delete(key);\n      }\n    }\n  }\n\n  /** Stop the sweep timer (for shutdown). */\n  stop(): void {\n    if (this.sweepTimer) {\n      clearInterval(this.sweepTimer);\n      this.sweepTimer = null;\n    }\n  }\n\n  /** Number of active flags (for testing). */\n  get size(): number {\n    return this.flags.size;\n  }\n}\n\n// ─── Singleton ───────────────────────────────────────────────────────────\n\nlet instance: InterruptService | null = null;\n\nexport function getInterruptService(): InterruptService {\n  if (!instance) {\n    instance = new InterruptService();\n  }\n  return instance;\n}\n\nexport function resetInterruptService(): void {\n  instance?.stop();\n  instance = null;\n}\n"],"mappings":";;AAuBA,MAAM,SAAS;;AAGf,MAAM,oBAAoB;AAI1B,IAAM,mBAAN,MAAuB;CACrB,wBAAgB,IAAI,KAA4B;CAChD,aAA4D;CAE5D,cAAc;AACZ,OAAK,aAAa,kBAAkB,KAAK,OAAO,EAAE,kBAAkB;AAEpE,MAAI,KAAK,cAAc,OAAO,KAAK,WAAW,UAAU,WACtD,MAAK,WAAW,OAAO;;;;;;CAQ3B,UAAU,YAAoB,QAA0B;EACtD,MAAM,WAAW,KAAK,MAAM,IAAI,WAAW;AAC3C,MAAI,YAAY,KAAK,KAAK,GAAG,SAAS,YAAY,OAChD,QAAO;AAET,OAAK,MAAM,IAAI,YAAY;GAAE,WAAW,KAAK,KAAK;GAAE;GAAQ,CAAC;AAC7D,SAAO;;;;;;CAOT,QAAQ,YAA6B;EACnC,MAAM,OAAO,KAAK,MAAM,IAAI,WAAW;AACvC,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,KAAK,KAAK,GAAG,KAAK,YAAY,QAAQ;AACxC,QAAK,MAAM,OAAO,WAAW;AAC7B,UAAO;;AAGT,OAAK,MAAM,OAAO,WAAW;AAC7B,SAAO;;;;;CAMT,UAAU,YAA6B;EACrC,MAAM,OAAO,KAAK,MAAM,IAAI,WAAW;AACvC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,KAAK,KAAK,GAAG,KAAK,YAAY,QAAQ;AACxC,QAAK,MAAM,OAAO,WAAW;AAC7B,UAAO;;AAET,SAAO;;;CAIT,QAAsB;EACpB,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,MAAM,CAAC,KAAK,SAAS,KAAK,MAC7B,KAAI,MAAM,KAAK,YAAY,OACzB,MAAK,MAAM,OAAO,IAAI;;;CAM5B,OAAa;AACX,MAAI,KAAK,YAAY;AACnB,iBAAc,KAAK,WAAW;AAC9B,QAAK,aAAa;;;;CAKtB,IAAI,OAAe;AACjB,SAAO,KAAK,MAAM;;;AAMtB,IAAI,WAAoC;AAExC,SAAgB,sBAAwC;AACtD,KAAI,CAAC,SACH,YAAW,IAAI,kBAAkB;AAEnC,QAAO;;AAGT,SAAgB,wBAA8B;AAC5C,WAAU,MAAM;AAChB,YAAW"}