import { defineHandler, useAnalytics, useCache, usePlatform } from '@kb-labs/sdk';
import { AGENT_ANALYTICS_EVENTS } from '@kb-labs/agent-contracts';

// src/rest/handlers/stop-handler.ts
var CACHE_PREFIX = "agent:run:";
var EVENT_TOPIC_PREFIX = "agent:events:";
var CACHE_TTL = 36e5;
var RunManagerImpl = class {
  /** Live agents and listeners (not cacheable) */
  activeRuns = /* @__PURE__ */ new Map();
  /** Session-level listeners: sessionId → Set<callback> — receive events from ALL runs in session */
  sessionListeners = /* @__PURE__ */ new Map();
  /** sessionId per runId — set when run is registered */
  runSessionMap = /* @__PURE__ */ new Map();
  /**
   * Generate unique run ID
   */
  generateRunId() {
    return `run-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
  }
  /**
   * Register a new run
   */
  async register(runId, task, agent, sessionManager, sessionId) {
    const now = (/* @__PURE__ */ new Date()).toISOString();
    const run = {
      runId,
      task,
      status: "pending",
      agent,
      sessionManager,
      startedAt: now,
      listeners: /* @__PURE__ */ new Set(),
      lastSeq: 0,
      eventBuffer: []
    };
    this.activeRuns.set(runId, run);
    if (sessionId) {
      this.runSessionMap.set(runId, sessionId);
    }
    await this.saveToCache(run);
    return run;
  }
  /**
   * Get run by ID (from memory first, then cache for state)
   */
  get(runId) {
    return this.activeRuns.get(runId);
  }
  /**
   * Check if run exists (in memory or cache)
   */
  async exists(runId) {
    if (this.activeRuns.has(runId)) {
      return true;
    }
    const state = await this.getState(runId);
    return state !== null;
  }
  /**
   * Get run state from cache (for completed runs or cross-process access)
   */
  async getState(runId) {
    const cache = useCache();
    if (!cache) {
      return null;
    }
    return cache.get(`${CACHE_PREFIX}${runId}`);
  }
  /**
   * Update run status
   */
  async updateStatus(runId, status, extra) {
    const run = this.activeRuns.get(runId);
    if (run) {
      run.status = status;
      if (extra) {
        Object.assign(run, extra);
      }
      await this.saveToCache(run);
    }
  }
  /**
   * Save run state to cache
   */
  async saveToCache(run) {
    const cache = useCache();
    if (!cache) {
      return;
    }
    const state = {
      runId: run.runId,
      task: run.task,
      status: run.status,
      startedAt: run.startedAt,
      completedAt: run.completedAt,
      durationMs: run.durationMs,
      summary: run.summary,
      error: run.error
    };
    await cache.set(`${CACHE_PREFIX}${run.runId}`, state, CACHE_TTL);
  }
  /** Track subscriptions for cleanup */
  subscriptions = /* @__PURE__ */ new Map();
  /**
   * Add event listener to run (uses eventBus for cross-process)
   */
  addListener(runId, callback) {
    const platform = usePlatform();
    if (!platform?.eventBus) {
      const run2 = this.activeRuns.get(runId);
      if (run2) {
        run2.listeners.add(callback);
        return true;
      }
      return false;
    }
    const topic = `${EVENT_TOPIC_PREFIX}${runId}`;
    const unsubscribe = platform.eventBus.subscribe(topic, async (event) => {
      try {
        callback(event);
      } catch {
      }
    });
    if (!this.subscriptions.has(runId)) {
      this.subscriptions.set(runId, /* @__PURE__ */ new Map());
    }
    this.subscriptions.get(runId).set(callback, unsubscribe);
    const run = this.activeRuns.get(runId);
    if (run) {
      run.listeners.add(callback);
    }
    return true;
  }
  /**
   * Remove event listener from run
   */
  removeListener(runId, callback) {
    const runSubs = this.subscriptions.get(runId);
    if (runSubs) {
      const unsubscribe = runSubs.get(callback);
      if (unsubscribe) {
        unsubscribe();
        runSubs.delete(callback);
      }
      if (runSubs.size === 0) {
        this.subscriptions.delete(runId);
      }
    }
    const run = this.activeRuns.get(runId);
    if (run) {
      run.listeners.delete(callback);
    }
  }
  /**
   * Broadcast event to all listeners of a run (uses eventBus for cross-process)
   * Assigns monotonic sequence number for reliable ordering
   * @returns The event with seq assigned (for persistence)
   */
  broadcast(runId, event) {
    const platform = usePlatform();
    const run = this.activeRuns.get(runId);
    let seqEvent = event;
    if (run) {
      run.lastSeq++;
      seqEvent = { ...event, seq: run.lastSeq, runId };
      run.eventBuffer.push(seqEvent);
    }
    if (platform?.eventBus) {
      const topic = `${EVENT_TOPIC_PREFIX}${runId}`;
      platform.eventBus.publish(topic, seqEvent).catch(() => {
      });
    }
    if (run) {
      for (const listener of run.listeners) {
        try {
          listener(seqEvent);
        } catch {
        }
      }
    }
    const sessionId = this.runSessionMap.get(runId);
    if (sessionId) {
      const sListeners = this.sessionListeners.get(sessionId);
      if (sListeners) {
        for (const listener of sListeners) {
          try {
            listener(seqEvent);
          } catch {
          }
        }
      }
    }
    return seqEvent;
  }
  /**
   * Add a session-level listener — receives events from ALL runs in this session.
   */
  addSessionListener(sessionId, callback) {
    if (!this.sessionListeners.has(sessionId)) {
      this.sessionListeners.set(sessionId, /* @__PURE__ */ new Set());
    }
    this.sessionListeners.get(sessionId).add(callback);
  }
  /**
   * Remove a session-level listener.
   */
  removeSessionListener(sessionId, callback) {
    const listeners = this.sessionListeners.get(sessionId);
    if (listeners) {
      listeners.delete(callback);
      if (listeners.size === 0) {
        this.sessionListeners.delete(sessionId);
      }
    }
  }
  /**
   * Get replay buffer for a run (all events emitted since start).
   * Used by WS handler to send missed events on late connection.
   * Optionally filter by afterSeq to only get events the client hasn't seen.
   */
  getEventBuffer(runId, afterSeq) {
    const run = this.activeRuns.get(runId);
    if (!run) {
      return [];
    }
    if (afterSeq != null) {
      return run.eventBuffer.filter((e) => e.seq != null && e.seq > afterSeq);
    }
    return [...run.eventBuffer];
  }
  /**
   * List all active runs
   */
  listActive() {
    return Array.from(this.activeRuns.values()).map((r) => ({
      runId: r.runId,
      task: r.task,
      status: r.status,
      startedAt: r.startedAt
    }));
  }
  /**
   * Request graceful stop of a running agent (and its child agents via propagated AbortSignal).
   * Agent finishes its current tool call then exits at the next iteration boundary.
   */
  requestStop(runId) {
    const run = this.activeRuns.get(runId);
    if (!run || run.status !== "running" || !run.agent) {
      return false;
    }
    run.agent.requestStop();
    return true;
  }
  /**
   * Clean up completed runs from memory (cache handles its own TTL)
   */
  cleanup() {
    for (const [runId, run] of this.activeRuns) {
      if (run.status === "completed" || run.status === "failed" || run.status === "stopped") {
        this.activeRuns.delete(runId);
      }
    }
  }
};
var GLOBAL_KEY = "__kb_agent_run_manager__";
if (!globalThis[GLOBAL_KEY]) {
  globalThis[GLOBAL_KEY] = new RunManagerImpl();
}
var RunManager = globalThis[GLOBAL_KEY];

// src/rest/handlers/stop-handler.ts
var stop_handler_default = defineHandler({
  async execute(ctx, input) {
    const { runId } = input.params;
    const body = input.body;
    const reason = body?.reason;
    ctx.platform.logger.info(`[stop-handler] Stop requested for run ${runId}${reason ? `: ${reason}` : ""}`);
    const run = RunManager.get(runId);
    if (!run) {
      return {
        stopped: false,
        runId,
        finalStatus: "not_found"
      };
    }
    if (run.status === "completed" || run.status === "failed" || run.status === "stopped") {
      return {
        stopped: false,
        runId,
        finalStatus: "already_completed"
      };
    }
    RunManager.requestStop(runId);
    await RunManager.updateStatus(runId, "stopped", {
      completedAt: (/* @__PURE__ */ new Date()).toISOString(),
      durationMs: Date.now() - new Date(run.startedAt).getTime(),
      summary: reason ? `Stopped by user: ${reason}` : "Stopped by user"
    });
    const analytics = useAnalytics();
    await analytics?.track(AGENT_ANALYTICS_EVENTS.RUN_STOPPED, {
      runId,
      durationMs: Date.now() - new Date(run.startedAt).getTime(),
      reason: reason?.slice(0, 200)
    });
    ctx.platform.logger.info(`[stop-handler] Run ${runId} stopped`);
    return {
      stopped: true,
      runId,
      finalStatus: "stopped"
    };
  }
});

export { stop_handler_default as default };
//# sourceMappingURL=stop-handler.js.map
//# sourceMappingURL=stop-handler.js.map