import { defineHandler, useAnalytics, useCache, useConfig, usePlatform } from '@kb-labs/sdk';
import { bootstrapAgentSDK, SessionManager, createSessionMemoryBridge, createCoreToolPack, PlanDocumentService } from '@kb-labs/agent-core';
import fs, { promises } from 'fs';
import { createDefaultResponseRequirementsSelector } from '@kb-labs/agent-runtime';
import { IncrementalTraceWriter } from '@kb-labs/agent-tracing';
import { AgentSDK } from '@kb-labs/agent-sdk';
import { createToolRegistry } from '@kb-labs/agent-tools';
import path from 'path';
import { AGENT_ANALYTICS_EVENTS, AGENTS_WS_CHANNELS, AGENTS_WS_BASE_PATH } from '@kb-labs/agent-contracts';

// src/rest/handlers/execute-session-plan-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/run-handler.ts
bootstrapAgentSDK();
var FOLLOW_UP_SCOPE_RE = /\b(глубже|подробнее|детал|слишком поверхност|deeper|more depth|details?)\b/i;
function isLikelyFollowUpScopeTask(task) {
  return FOLLOW_UP_SCOPE_RE.test(task);
}
function pathExists(dir) {
  try {
    return fs.existsSync(dir);
  } catch {
    return false;
  }
}
function scoreRepoFromToolPath(rawPath, scores) {
  const p = rawPath.replace(/\\/g, "/");
  const explicit = p.match(/(^|\/)(kb-labs-[^/]+)\//);
  if (explicit?.[2]) {
    scores.set(explicit[2], (scores.get(explicit[2]) ?? 0) + 5);
  }
  if (p.startsWith("packages/")) {
    if (/packages\/agent[-/]/.test(p) || /agent-core|agent-tools|agent-cli|agent-task-runner/.test(p)) {
      scores.set("kb-labs-agents", (scores.get("kb-labs-agents") ?? 0) + 4);
    }
    if (/packages\/mind[-/]/.test(p) || /mind-engine|mind-core/.test(p)) {
      scores.set("kb-labs-mind", (scores.get("kb-labs-mind") ?? 0) + 4);
    }
  }
}
async function inferFollowUpWorkingDir(sessionManager, sessionId, baseWorkingDir) {
  const events = await sessionManager.getSessionEvents(sessionId);
  if (!events.length) {
    return null;
  }
  const completedRuns = /* @__PURE__ */ new Set();
  for (const event of events) {
    if (event.type === "agent:end" && event.runId) {
      completedRuns.add(event.runId);
    }
  }
  const lastRunId = Array.from(completedRuns).at(-1);
  if (!lastRunId) {
    return null;
  }
  const repoScores = /* @__PURE__ */ new Map();
  for (const event of events) {
    if (event.runId !== lastRunId) {
      continue;
    }
    if (event.type === "tool:start") {
      const input = event.data?.input ?? {};
      const p1 = input.path;
      const p2 = input.directory;
      if (typeof p1 === "string") {
        scoreRepoFromToolPath(p1, repoScores);
      }
      if (typeof p2 === "string") {
        scoreRepoFromToolPath(p2, repoScores);
      }
    }
    if (event.type === "tool:end") {
      const metadata = event.data?.metadata ?? {};
      const p = metadata.path;
      if (typeof p === "string") {
        scoreRepoFromToolPath(p, repoScores);
      }
    }
  }
  const ranked = Array.from(repoScores.entries()).sort((a, b) => b[1] - a[1]);
  const top = ranked[0];
  if (!top || top[1] < 4) {
    return null;
  }
  const inferredDir = path.join(baseWorkingDir, top[0]);
  return pathExists(inferredDir) ? inferredDir : null;
}
var run_handler_default = defineHandler({
  async execute(ctx, input) {
    const body = input.body;
    if (!body?.task) {
      throw new Error("Task is required");
    }
    const analytics = useAnalytics();
    const runId = RunManager.generateRunId();
    const startTime = Date.now();
    let sessionId = body.sessionId;
    let workingDir = body.workingDir || ctx.cwd;
    let sessionManager = new SessionManager(workingDir);
    if (!sessionId) {
      const session = await sessionManager.createSession({
        mode: "execute",
        task: body.task,
        agentId: body.agentId ?? "orchestrator"
      });
      sessionId = session.id;
      ctx.platform.logger.info(`[run-handler] Created new session ${sessionId}`);
    } else {
      const existingSession = await sessionManager.loadSession(sessionId);
      let resolvedSession = existingSession;
      if (!resolvedSession && pathExists(ctx.cwd)) {
        const fallbackManager = new SessionManager(ctx.cwd);
        const fallbackSession = await fallbackManager.loadSession(sessionId);
        if (fallbackSession) {
          sessionManager = fallbackManager;
          resolvedSession = fallbackSession;
        }
      }
      if (!resolvedSession) {
        throw new Error(`Session not found: ${sessionId} (cwd=${ctx.cwd})`);
      }
      workingDir = body.workingDir || resolvedSession.workingDir || workingDir;
      if (!body.workingDir && isLikelyFollowUpScopeTask(body.task)) {
        const inferred = await inferFollowUpWorkingDir(sessionManager, sessionId, resolvedSession.workingDir || ctx.cwd);
        if (inferred && inferred !== workingDir) {
          ctx.platform.logger.info(`[run-handler] Follow-up scope inferred: ${workingDir} -> ${inferred}`);
          workingDir = inferred;
        }
      }
      sessionManager = new SessionManager(workingDir);
      ctx.platform.logger.info(`[run-handler] Continuing session ${sessionId} (workingDir=${workingDir})`);
    }
    ctx.platform.logger.info(`[run-handler] Starting run ${runId} for task: ${body.task}`);
    await sessionManager.createUserTurn(sessionId, body.task, runId);
    await analytics?.track(AGENT_ANALYTICS_EVENTS.RUN_STARTED, {
      runId,
      sessionId,
      taskLength: body.task.length,
      tier: body.tier ?? "medium",
      enableEscalation: body.enableEscalation ?? true,
      responseMode: body.responseMode ?? "auto",
      verbose: body.verbose ?? false
    });
    const sessionMemory = createSessionMemoryBridge(workingDir, sessionId);
    const responseRequirementsSelector = createDefaultResponseRequirementsSelector();
    const toolRegistry = createToolRegistry({
      workingDir,
      currentTask: body.task,
      sessionId,
      verbose: body.verbose,
      cache: useCache(),
      sessionMemory,
      responseRequirementsResolver: async ({ task, kernel }) => responseRequirementsSelector.select({
        state: kernel,
        messages: [],
        task: task ?? body.task
      })
    });
    const agentsConfig = await useConfig();
    const finalSessionId = sessionId;
    const traceDir = path.join(workingDir, ".kb", "traces", "incremental");
    const traceWriter = new IncrementalTraceWriter(runId, {}, traceDir);
    const agent = new AgentSDK().register(createCoreToolPack(toolRegistry)).createRunner({
      sessionId: finalSessionId,
      workingDir,
      maxIterations: 50,
      temperature: 0.1,
      tier: body.tier ?? "medium",
      tokenBudget: agentsConfig?.tokenBudget,
      onEvent: (event) => {
        const seqEvent = RunManager.broadcast(runId, event);
        void sessionManager.addEvent(finalSessionId, {
          ...seqEvent,
          sessionId: finalSessionId,
          runId,
          metadata: {
            ...seqEvent.metadata,
            sessionId: finalSessionId,
            runId,
            workingDir
          }
        });
      }
    });
    const run = await RunManager.register(runId, body.task, agent, sessionManager, finalSessionId);
    await RunManager.updateStatus(runId, "running");
    void (async () => {
      try {
        const result = await agent.execute(body.task);
        const durationMs = Date.now() - startTime;
        const detailedTrace = traceWriter.getEntries();
        await traceWriter.finalize?.();
        if (detailedTrace.length > 0) {
          await sessionManager.storeTraceArtifacts(finalSessionId, runId, detailedTrace);
        }
        if (result.fileChanges && result.fileChanges.length > 0) {
          await sessionManager.attachFileChangesToTurn(finalSessionId, runId, result.fileChanges);
        }
        await RunManager.updateStatus(runId, result.success ? "completed" : "failed", {
          completedAt: (/* @__PURE__ */ new Date()).toISOString(),
          durationMs,
          summary: result.summary,
          error: result.error
        });
        await analytics?.track(
          result.success ? AGENT_ANALYTICS_EVENTS.RUN_COMPLETED : AGENT_ANALYTICS_EVENTS.RUN_FAILED,
          {
            runId,
            durationMs,
            success: result.success,
            summary: result.summary?.slice(0, 200)
          }
        );
        ctx.platform.logger.info(`[run-handler] Run ${runId} completed: ${result.success}`);
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        const durationMs = Date.now() - startTime;
        const detailedTrace = traceWriter.getEntries();
        await traceWriter.finalize?.();
        if (detailedTrace.length > 0) {
          await sessionManager.storeTraceArtifacts(finalSessionId, runId, detailedTrace);
        }
        await RunManager.updateStatus(runId, "failed", {
          completedAt: (/* @__PURE__ */ new Date()).toISOString(),
          durationMs,
          error: errorMsg
        });
        await analytics?.track(AGENT_ANALYTICS_EVENTS.RUN_FAILED, {
          runId,
          durationMs,
          error: errorMsg.slice(0, 200)
        });
        ctx.platform.logger.error(`[run-handler] Run ${runId} failed: ${errorMsg}`);
      }
    })();
    const wsPath = AGENTS_WS_CHANNELS.SESSION_STREAM.replace(":sessionId", finalSessionId);
    const eventsPath = `${AGENTS_WS_BASE_PATH}${wsPath}`;
    return {
      runId,
      sessionId: finalSessionId,
      eventsPath,
      status: "started",
      startedAt: run.startedAt
    };
  }
});

// src/rest/handlers/execute-session-plan-handler.ts
async function loadPlan(planPath) {
  const content = await promises.readFile(planPath, "utf-8");
  return JSON.parse(content);
}
function buildExecutionTask(plan) {
  const phaseLines = plan.phases.map((phase, idx) => {
    const steps = phase.steps.map((step, stepIdx) => {
      const tool = step.tool || "n/a";
      return `    ${stepIdx + 1}. ${step.action} [tool: ${tool}]`;
    }).join("\n");
    return `${idx + 1}. ${phase.name}
${steps || "    (no steps)"}`;
  }).join("\n");
  return [
    `Execute the approved implementation plan for task: ${plan.task}`,
    "",
    "Follow phase order and dependencies. Update progress with concrete results.",
    "",
    `Plan ID: ${plan.id}`,
    `Complexity: ${plan.complexity}`,
    `Estimated Duration: ${plan.estimatedDuration || "Unknown"}`,
    "",
    "Phases:",
    phaseLines,
    "",
    "Requirements:",
    "- Execute plan steps pragmatically; adapt only when blocked.",
    "- Keep outputs concrete: changed files, commands run, verification results."
  ].join("\n");
}
function buildExecutionTaskWithSpec(plan, spec) {
  return [
    `Execute the approved implementation plan for task: ${plan.task}`,
    "",
    `Plan ID: ${plan.id}`,
    `Complexity: ${plan.complexity}`,
    "",
    "## DETAILED SPECIFICATION (exact changes)",
    "",
    "A detailed spec was generated and verified. Apply these exact changes:",
    "",
    spec.markdown || "(no spec markdown available)",
    "",
    "Requirements:",
    "- Apply the before/after diffs from the spec as precisely as possible.",
    "- Verify each change after applying it.",
    "- If the spec code doesn't match the current file (file was modified since spec), adapt minimally.",
    "- Keep outputs concrete: changed files, commands run, verification results."
  ].join("\n");
}
var execute_session_plan_handler_default = defineHandler({
  async execute(ctx, input) {
    const params = input.params;
    const sessionId = params?.sessionId;
    const body = input.body ?? {};
    if (!sessionId) {
      throw new Error("Session ID is required");
    }
    const baseManager = new SessionManager(ctx.cwd);
    const session = await baseManager.loadSession(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    const workingDir = session.workingDir || ctx.cwd;
    const sessionManager = new SessionManager(workingDir);
    const planPath = sessionManager.getSessionPlanPath(sessionId);
    let plan;
    try {
      plan = await loadPlan(planPath);
    } catch {
      throw new Error(`Plan not found for session ${sessionId}`);
    }
    if (plan.status !== "approved" && plan.status !== "spec_ready") {
      throw new Error(`Plan must be approved before execution (current status: ${plan.status})`);
    }
    let spec = null;
    try {
      const specPath = sessionManager.getSessionSpecPath(sessionId);
      const specContent = await promises.readFile(specPath, "utf-8");
      spec = JSON.parse(specContent);
    } catch {
    }
    const executionTask = spec?.markdown ? buildExecutionTaskWithSpec(plan, spec) : buildExecutionTask(plan);
    const runInput = {
      body: {
        task: executionTask,
        sessionId,
        workingDir,
        tier: body.tier,
        responseMode: body.responseMode,
        verbose: body.verbose,
        enableEscalation: body.enableEscalation
      }
    };
    const runResponse = await run_handler_default.execute(ctx, runInput);
    const inProgressPlan = {
      ...plan,
      status: "in_progress",
      updatedAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    await promises.writeFile(planPath, JSON.stringify(inProgressPlan, null, 2), "utf-8");
    const documentService = new PlanDocumentService(workingDir);
    const markdownPath = documentService.getPlanPath(plan);
    await documentService.appendExecutionLog(
      markdownPath,
      `- ${(/* @__PURE__ */ new Date()).toISOString()}: Execution started (runId: ${runResponse.runId}).`
    );
    return {
      sessionId,
      planId: plan.id,
      runId: runResponse.runId,
      eventsPath: runResponse.eventsPath,
      status: runResponse.status,
      startedAt: runResponse.startedAt
    };
  }
});

export { execute_session_plan_handler_default as default };
//# sourceMappingURL=execute-session-plan-handler.js.map
//# sourceMappingURL=execute-session-plan-handler.js.map