#!/usr/bin/env node
import {
  ControlRequestEventSchema,
  SeqCounter,
  createApprovalRequestIdFactory
} from "./chunk-NXELIU46.js";
import {
  CLAUDE_PROVIDER,
  CODEX_PROVIDER
} from "./chunk-ZRDKF4BU.js";
import {
  LineBuffer,
  createWorkerReader,
  serializeWorkerMsg
} from "./chunk-OU3TG45P.js";
import "./chunk-NQDJ6QAM.js";
import "./chunk-7RSBFN2G.js";

// src/session-worker.ts
import { createServer } from "net";
import { mkdirSync, unlinkSync, existsSync, chmodSync } from "fs";

// src/worker/json-session.ts
import { spawn } from "child_process";
var denyAllStrategy = async () => ({
  behavior: "deny",
  message: "Tool use denied by default policy. Remote approval not yet configured."
});
var ToolWhitelist = class {
  allowed = /* @__PURE__ */ new Set();
  has(toolName) {
    return this.allowed.has(toolName);
  }
  add(toolName) {
    this.allowed.add(toolName);
  }
  clear() {
    this.allowed.clear();
  }
};
function createRelayApprovalStrategy(whitelist2, forwardToRelay2) {
  return async (toolName, input) => {
    if (whitelist2.has(toolName)) {
      return { behavior: "allow", message: "Auto-approved by session whitelist" };
    }
    return forwardToRelay2(toolName, input);
  };
}
var editToolNames = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "Write", "NotebookEdit"]);
function createPermissionModeApprovalStrategy(permissionMode, fallback) {
  switch (permissionMode) {
    case "bypassPermissions":
    case "dontAsk":
      return async () => ({ behavior: "allow", message: "Auto-approved by permission mode" });
    case "acceptEdits":
      return async (toolName, input) => {
        if (editToolNames.has(toolName)) {
          return { behavior: "allow", message: "Auto-approved edit by permission mode" };
        }
        return fallback(toolName, input);
      };
    case "plan":
      return async () => ({
        behavior: "deny",
        message: "Tool use denied by plan mode."
      });
    default:
      return fallback;
  }
}
var JsonSession = class {
  child = null;
  interruptedChildren = /* @__PURE__ */ new WeakSet();
  interruptedExitResolvers = /* @__PURE__ */ new WeakMap();
  stderrChunks = [];
  writeQueue = Promise.resolve();
  claudeSessionId = null;
  workDir;
  claudeArgs;
  approvalStrategy;
  onEvent;
  onExitCb;
  resumeSessionId;
  permissionMode;
  includePartialMessages;
  hook;
  constructor(options = {}) {
    this.workDir = options.cwd ?? options.workDir ?? process.cwd();
    this.claudeArgs = options.claudeArgs ?? [];
    this.approvalStrategy = options.approvalStrategy ?? denyAllStrategy;
    this.onEvent = options.onEvent;
    this.onExitCb = options.onExit;
    this.resumeSessionId = options.resumeSessionId;
    this.permissionMode = options.permissionMode;
    this.includePartialMessages = options.includePartialMessages ?? false;
    this.hook = options.hook;
  }
  getClaudeSessionId() {
    return this.claudeSessionId;
  }
  start() {
    return this.startChild(this.resumeSessionId);
  }
  startChild(resumeSessionId) {
    const command = CLAUDE_PROVIDER.buildJsonCommand(
      {
        extraArgs: this.claudeArgs,
        permissionMode: this.permissionMode,
        resumeSessionId,
        includePartialMessages: this.includePartialMessages,
        hook: this.hook
      },
      process.env
    );
    this.child = spawn(command.command, command.args, {
      cwd: this.workDir,
      stdio: ["pipe", "pipe", "pipe"],
      env: command.env
    });
    this.setupStdoutParsing();
    this.setupStderrCollection();
    this.setupExitHandler();
    return this.child.pid;
  }
  sendMessage(content) {
    const message = {
      type: "user",
      message: { role: "user", content }
    };
    this.writeToStdin(JSON.stringify(message));
  }
  async stop(gracePeriodMs = 5e3) {
    if (!this.child || !this.isAlive()) return;
    this.child.kill("SIGTERM");
    const start = Date.now();
    while (Date.now() - start < gracePeriodMs) {
      if (!this.isAlive()) return;
      await new Promise((r) => setTimeout(r, 200));
    }
    if (this.isAlive()) {
      this.child.kill("SIGKILL");
    }
  }
  async interruptCurrentTurn(gracePeriodMs = 5e3) {
    const child = this.child;
    if (!child || !this.isChildAlive(child)) return false;
    this.interruptedChildren.add(child);
    const drained = new Promise((resolve) => {
      this.interruptedExitResolvers.set(child, resolve);
    });
    const signaled = child.kill("SIGINT");
    if (!signaled) {
      this.interruptedChildren.delete(child);
      this.interruptedExitResolvers.delete(child);
      return false;
    }
    const start = Date.now();
    while (Date.now() - start < gracePeriodMs) {
      if (!this.isChildAlive(child)) break;
      await new Promise((r) => setTimeout(r, 100));
    }
    if (this.isChildAlive(child)) {
      child.kill("SIGKILL");
    }
    await Promise.race([drained, new Promise((resolve) => setTimeout(resolve, 1500))]);
    if (this.child === child) this.child = null;
    this.startChild(this.claudeSessionId ?? this.resumeSessionId ?? void 0);
    return true;
  }
  isAlive() {
    return this.child ? this.isChildAlive(this.child) : false;
  }
  isChildAlive(child) {
    if (!child.pid) return false;
    try {
      process.kill(child.pid, 0);
      return true;
    } catch {
      return false;
    }
  }
  getStderr() {
    return this.stderrChunks.join("");
  }
  setupStdoutParsing() {
    const child = this.child;
    if (!child?.stdout) return;
    const lineBuffer = new LineBuffer();
    child.stdout.pipe(lineBuffer);
    lineBuffer.on("data", (line) => {
      const str = typeof line === "string" ? line : line.toString();
      let event;
      try {
        event = JSON.parse(str);
      } catch {
        return;
      }
      if (event.type === "system" && typeof event.session_id === "string") {
        this.claudeSessionId = event.session_id;
      }
      if (event.type === "control_request") {
        const parsed = ControlRequestEventSchema.safeParse(event);
        if (!parsed.success) {
          console.error(
            "[json-session] control_request shape mismatch; skipping approval",
            parsed.error.issues.slice(0, 3)
          );
          return;
        }
        this.handleControlRequest(parsed.data);
        return;
      }
      this.onEvent?.(event);
    });
  }
  setupStderrCollection() {
    const child = this.child;
    if (!child?.stderr) return;
    child.stderr.on("data", (chunk) => {
      this.stderrChunks.push(typeof chunk === "string" ? chunk : chunk.toString());
    });
  }
  setupExitHandler() {
    const child = this.child;
    if (!child) return;
    let stdoutEnded = false;
    let exitCode = null;
    let exited = false;
    let fired = false;
    const fireOnce = () => {
      if (fired || !exited || !stdoutEnded) return;
      fired = true;
      if (this.interruptedChildren.has(child)) {
        this.interruptedChildren.delete(child);
        this.interruptedExitResolvers.get(child)?.();
        this.interruptedExitResolvers.delete(child);
        return;
      }
      this.onExitCb?.(exitCode ?? 1);
    };
    child.stdout?.on("end", () => {
      stdoutEnded = true;
      fireOnce();
    });
    child.on("exit", (code) => {
      exitCode = code;
      exited = true;
      setTimeout(() => {
        stdoutEnded = true;
        fireOnce();
      }, 1e3).unref();
      fireOnce();
    });
  }
  handleControlRequest(event) {
    const requestId = event.request_id;
    const request = event.request;
    this.approvalStrategy(request.tool_name, request.input).then((decision) => {
      const response = decision.behavior === "deny" ? {
        type: "control_response",
        response: {
          subtype: "success",
          request_id: requestId,
          response: {
            behavior: "deny",
            message: decision.message ?? "Tool use denied by default policy."
          }
        }
      } : {
        type: "control_response",
        response: {
          subtype: "success",
          request_id: requestId,
          response: {
            behavior: "allow",
            updatedInput: {}
          }
        }
      };
      this.writeToStdin(JSON.stringify(response));
    }).catch((err) => {
      console.error(
        "[json-session] approval strategy rejected, fallback to deny",
        requestId,
        err instanceof Error ? err.message : err
      );
      this.writeToStdin(
        JSON.stringify({
          type: "control_response",
          response: {
            subtype: "success",
            request_id: requestId,
            response: {
              behavior: "deny",
              message: "Approval strategy failed; denied as fallback."
            }
          }
        })
      );
    });
  }
  writeToStdin(data) {
    this.writeQueue = this.writeQueue.then(
      () => new Promise((resolve, reject) => {
        if (!this.child?.stdin?.writable) {
          reject(new Error("stdin not writable"));
          return;
        }
        this.child.stdin.write(data + "\n", (err) => {
          if (err) reject(err);
          else resolve();
        });
      })
    ).catch((err) => {
      console.error(
        "[json-session] writeToStdin failed",
        err instanceof Error ? err.message : err
      );
    });
  }
};

// src/worker/codex-app-server-session.ts
import { spawn as spawn2 } from "child_process";
var CLIENT_INFO = {
  name: "dev-anywhere",
  title: "Dev Anywhere",
  version: "0.0.0"
};
function approvalPolicy(permissionMode) {
  switch (permissionMode) {
    case "auto":
      return "on-request";
    case "bypassPermissions":
    case "dontAsk":
      return "never";
    default:
      return "untrusted";
  }
}
function sandboxMode(permissionMode) {
  return permissionMode === "bypassPermissions" || permissionMode === "dontAsk" ? "danger-full-access" : null;
}
function getThreadId(result) {
  if (!result || typeof result !== "object") return null;
  const thread = result.thread;
  if (!thread || typeof thread !== "object") return null;
  const id = thread.id;
  return typeof id === "string" ? id : null;
}
function isRecord(value) {
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function normalizeApprovalInput(params) {
  if (!isRecord(params)) return {};
  const input = { ...params };
  if (Array.isArray(input.command)) {
    input.command = input.command.map((part) => String(part)).join(" ");
  }
  return input;
}
var denyAllStrategy2 = async () => ({
  behavior: "deny",
  message: "Tool use denied by default policy. Remote approval not yet configured."
});
var CodexAppServerSession = class {
  child = null;
  stderrChunks = [];
  nextRequestId = 1;
  pendingRequests = /* @__PURE__ */ new Map();
  threadReady;
  resolveThreadReady = () => {
  };
  codexThreadId = null;
  activeTurnId = null;
  workDir;
  resumeSessionId;
  permissionMode;
  approvalStrategy;
  onEvent;
  onThreadId;
  onExitCb;
  constructor(options = {}) {
    this.workDir = options.cwd ?? options.workDir ?? process.cwd();
    this.resumeSessionId = options.resumeSessionId;
    this.permissionMode = options.permissionMode;
    this.approvalStrategy = options.approvalStrategy ?? denyAllStrategy2;
    this.onEvent = options.onEvent;
    this.onThreadId = options.onThreadId;
    this.onExitCb = options.onExit;
    this.threadReady = new Promise((resolve) => {
      this.resolveThreadReady = resolve;
    });
  }
  getCodexThreadId() {
    return this.codexThreadId;
  }
  start() {
    const command = CODEX_PROVIDER.buildJsonCommand({}, process.env);
    this.child = spawn2(command.command, command.args, {
      cwd: this.workDir,
      stdio: ["pipe", "pipe", "pipe"],
      env: command.env
    });
    this.setupStdoutParsing();
    this.setupStderrCollection();
    this.setupExitHandler();
    void this.initializeThread();
    return this.child.pid;
  }
  sendMessage(content) {
    void this.threadReady.then(
      (threadId) => this.request("turn/start", {
        threadId,
        input: [{ type: "text", text: content, text_elements: [] }],
        approvalPolicy: approvalPolicy(this.permissionMode)
      }).then((result) => {
        if (isRecord(result) && isRecord(result.turn) && typeof result.turn.id === "string") {
          this.activeTurnId = result.turn.id;
        }
      })
    );
  }
  async stop(gracePeriodMs = 5e3) {
    if (!this.child || !this.isAlive()) return;
    this.child.kill("SIGTERM");
    const start = Date.now();
    while (Date.now() - start < gracePeriodMs) {
      if (!this.isAlive()) return;
      await new Promise((resolve) => setTimeout(resolve, 200));
    }
    if (this.isAlive()) this.child.kill("SIGKILL");
  }
  async interruptCurrentTurn() {
    if (!this.codexThreadId || !this.activeTurnId) return false;
    await this.request("turn/interrupt", {
      threadId: this.codexThreadId,
      turnId: this.activeTurnId
    });
    return true;
  }
  isAlive() {
    if (!this.child?.pid) return false;
    try {
      process.kill(this.child.pid, 0);
      return true;
    } catch {
      return false;
    }
  }
  getStderr() {
    return this.stderrChunks.join("");
  }
  async initializeThread() {
    await this.request("initialize", {
      clientInfo: CLIENT_INFO,
      capabilities: {
        experimentalApi: true,
        requestAttestation: false
      }
    });
    const result = this.resumeSessionId ? await this.request("thread/resume", this.threadParams({ threadId: this.resumeSessionId })) : await this.request("thread/start", this.threadParams());
    const threadId = getThreadId(result);
    if (threadId) {
      this.codexThreadId = threadId;
      this.onThreadId?.(threadId);
      this.resolveThreadReady(threadId);
    }
  }
  threadParams(extra = {}) {
    const sandbox = sandboxMode(this.permissionMode);
    return {
      ...extra,
      cwd: this.workDir,
      approvalPolicy: approvalPolicy(this.permissionMode),
      ...sandbox ? { sandbox } : {},
      experimentalRawEvents: false,
      persistExtendedHistory: false
    };
  }
  request(method, params) {
    const id = this.nextRequestId++;
    const payload = { jsonrpc: "2.0", id, method, params };
    this.writeLine(payload);
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(id, { resolve, reject });
    });
  }
  writeLine(payload) {
    if (!this.child?.stdin?.writable) return;
    this.child.stdin.write(`${JSON.stringify(payload)}
`);
  }
  setupStdoutParsing() {
    const child = this.child;
    if (!child?.stdout) return;
    const lineBuffer = new LineBuffer();
    child.stdout.pipe(lineBuffer);
    lineBuffer.on("data", (line) => {
      const str = typeof line === "string" ? line : line.toString();
      let message;
      try {
        message = JSON.parse(str);
      } catch {
        return;
      }
      this.handleAppServerMessage(message);
    });
  }
  handleAppServerMessage(message) {
    if (!isRecord(message)) return;
    if ("id" in message && !("method" in message)) {
      this.handleResponse(message);
      return;
    }
    if ("id" in message && typeof message.method === "string") {
      this.handleServerRequest(message);
      return;
    }
    if (typeof message.method === "string") {
      this.onEvent?.({
        type: "codex_app_server",
        method: message.method,
        params: isRecord(message.params) ? message.params : {}
      });
    }
  }
  handleResponse(message) {
    const id = message.id;
    const pending = this.pendingRequests.get(id);
    if (!pending) return;
    this.pendingRequests.delete(id);
    if (isRecord(message.error)) {
      pending.reject(new Error(String(message.error.message ?? "Codex app-server request failed")));
      return;
    }
    pending.resolve(message.result);
  }
  handleServerRequest(request) {
    if (request.method === "item/commandExecution/requestApproval") {
      void this.handleApprovalRequest(request.id, "Bash", request.params, "item");
      return;
    }
    if (request.method === "item/fileChange/requestApproval") {
      void this.handleApprovalRequest(request.id, "Patch", request.params, "item");
      return;
    }
    if (request.method === "execCommandApproval") {
      void this.handleApprovalRequest(request.id, "Bash", request.params, "legacy");
      return;
    }
    if (request.method === "applyPatchApproval") {
      void this.handleApprovalRequest(request.id, "Patch", request.params, "legacy");
      return;
    }
    if (request.method === "item/permissions/requestApproval") {
      void this.handlePermissionsApprovalRequest(request.id, request.params);
      return;
    }
    this.writeLine({
      jsonrpc: "2.0",
      id: request.id,
      error: { code: -32601, message: `Unsupported Codex server request: ${request.method}` }
    });
  }
  async handleApprovalRequest(id, toolName, params, style) {
    const input = normalizeApprovalInput(params);
    let decision;
    try {
      decision = await this.approvalStrategy(toolName, input);
    } catch (err) {
      decision = { behavior: "deny", message: err instanceof Error ? err.message : String(err) };
    }
    const allowed = decision.behavior === "allow";
    this.writeLine({
      jsonrpc: "2.0",
      id,
      result: {
        decision: allowed ? style === "legacy" ? "approved" : "accept" : style === "legacy" ? "denied" : "decline"
      }
    });
  }
  async handlePermissionsApprovalRequest(id, params) {
    const input = normalizeApprovalInput(params);
    let decision;
    try {
      decision = await this.approvalStrategy("Permissions", input);
    } catch (err) {
      decision = { behavior: "deny", message: err instanceof Error ? err.message : String(err) };
    }
    const requestedPermissions = isRecord(input.permissions) ? input.permissions : {};
    const allowed = decision.behavior === "allow";
    this.writeLine({
      jsonrpc: "2.0",
      id,
      result: {
        permissions: allowed ? requestedPermissions : {},
        scope: "turn",
        ...allowed ? {} : { strictAutoReview: true }
      }
    });
  }
  setupStderrCollection() {
    const child = this.child;
    if (!child?.stderr) return;
    child.stderr.on("data", (chunk) => {
      this.stderrChunks.push(typeof chunk === "string" ? chunk : chunk.toString());
    });
  }
  setupExitHandler() {
    const child = this.child;
    if (!child) return;
    child.on("exit", (code) => {
      this.onExitCb?.(code ?? 1);
    });
  }
};

// src/worker/serve-socket-takeover.ts
function takeoverServeSocket(prev, next) {
  if (prev && prev !== next) {
    try {
      prev.destroy();
    } catch {
    }
  }
  return next;
}

// src/session-worker.ts
var sessionId = process.argv[2];
var sockPath = process.argv[3];
var separatorIdx = process.argv.indexOf("--");
var providerArgs = separatorIdx >= 0 ? process.argv.slice(separatorIdx + 1) : [];
var preArgs = process.argv.slice(4, separatorIdx >= 0 ? separatorIdx : void 0);
function getArg(name) {
  const idx = preArgs.indexOf(name);
  return idx >= 0 && idx + 1 < preArgs.length ? preArgs[idx + 1] : void 0;
}
function hasFlag(name) {
  return preArgs.includes(name);
}
var workerCwd = getArg("--cwd");
var workerResume = getArg("--resume");
var workerPermissionMode = getArg("--permission-mode");
var workerStreamDelta = hasFlag("--stream-delta");
var workerHookUrl = getArg("--hook-url");
var workerHookMarker = getArg("--hook-marker");
var workerHookToken = process.env.DEV_ANYWHERE_HOOK_TOKEN;
var workerHookProvider = getArg("--hook-provider");
var provider = getArg("--provider") ?? "claude";
if (!sessionId || !sockPath) {
  console.error("Usage: session-worker <sessionId> <socketPath> [-- claudeArgs...]");
  process.exit(1);
}
if (provider !== "claude" && provider !== "codex") {
  console.error(`Unsupported JSON worker provider: ${provider}`);
  process.exit(1);
}
var workerHook = workerHookUrl && workerHookMarker && workerHookToken && workerHookProvider ? {
  provider: workerHookProvider,
  sessionId,
  hookUrl: workerHookUrl,
  marker: workerHookMarker,
  token: workerHookToken
} : void 0;
var serveSocket = null;
var seqCounter = new SeqCounter(sessionId);
var whitelist = new ToolWhitelist();
var nextApprovalRequestId = createApprovalRequestIdFactory(sessionId);
var pendingApprovals = /* @__PURE__ */ new Map();
function sendToServe(msg) {
  if (serveSocket?.writable) {
    serveSocket.write(serializeWorkerMsg(msg));
  }
}
var forwardToRelay = async (toolName, input) => {
  return new Promise((resolve) => {
    const requestId = nextApprovalRequestId();
    pendingApprovals.set(requestId, { resolve, toolName, input });
    sendToServe({
      type: "worker_approval_request",
      requestId,
      toolName,
      input
    });
  });
};
var approvalStrategy = createPermissionModeApprovalStrategy(
  workerPermissionMode,
  createRelayApprovalStrategy(whitelist, forwardToRelay)
);
function handleProviderEvent(event) {
  if (event.type === "system" && typeof event.session_id === "string") {
    sendToServe({
      type: "worker_claude_session_id",
      sessionId: event.session_id
    });
  }
  const seq = seqCounter.next();
  sendToServe({
    type: "worker_event",
    seq,
    event
  });
}
function handleProviderExit(code) {
  whitelist.clear();
  sendToServe({ type: "worker_exit", code });
  cleanup();
  process.exit(0);
}
var session = provider === "codex" ? new CodexAppServerSession({
  cwd: workerCwd,
  resumeSessionId: workerResume,
  permissionMode: workerPermissionMode,
  approvalStrategy,
  onEvent: handleProviderEvent,
  onThreadId: (threadId) => {
    sendToServe({
      type: "worker_native_session_id",
      provider: "codex",
      sessionId: threadId
    });
  },
  onExit: handleProviderExit
}) : new JsonSession({
  claudeArgs: providerArgs,
  cwd: workerCwd,
  resumeSessionId: workerResume,
  permissionMode: workerPermissionMode,
  includePartialMessages: workerStreamDelta,
  hook: workerHook,
  approvalStrategy,
  onEvent: handleProviderEvent,
  onExit: handleProviderExit
});
function handleServeConnection(socket) {
  serveSocket = takeoverServeSocket(serveSocket, socket);
  for (const [requestId, pending] of pendingApprovals) {
    sendToServe({
      type: "worker_approval_request",
      requestId,
      toolName: pending.toolName,
      input: pending.input
    });
  }
  createWorkerReader(
    socket,
    (msg) => {
      switch (msg.type) {
        case "worker_input":
          session.sendMessage(msg.content);
          break;
        case "worker_interrupt":
          rejectAllPendingApprovals("Turn interrupted");
          void session.interruptCurrentTurn().then((interrupted) => {
            if (interrupted) sendToServe({ type: "worker_interrupted" });
            else console.error("[worker] interrupt requested but Claude child was not running");
          });
          break;
        case "worker_stop":
          session.stop();
          break;
        case "worker_approval_response": {
          const pending = pendingApprovals.get(msg.requestId);
          if (pending) {
            pending.resolve({ behavior: msg.behavior, message: msg.message });
            pendingApprovals.delete(msg.requestId);
          }
          break;
        }
        case "worker_whitelist_add":
          whitelist.add(msg.toolName);
          break;
      }
    },
    (err) => {
      console.error(`[worker] serve IPC message dropped: ${err.message}`);
    }
  );
  socket.on("close", () => {
    serveSocket = null;
    rejectAllPendingApprovals("Serve connection closed");
  });
  socket.on("error", () => {
    serveSocket = null;
    rejectAllPendingApprovals("Serve connection error");
  });
}
function rejectAllPendingApprovals(reason) {
  if (pendingApprovals.size === 0) return;
  for (const [, pending] of pendingApprovals) {
    pending.resolve({ behavior: "deny", message: reason });
  }
  pendingApprovals.clear();
}
var sockDir = sockPath.substring(0, sockPath.lastIndexOf("/"));
mkdirSync(sockDir, { recursive: true });
if (existsSync(sockPath)) {
  unlinkSync(sockPath);
}
var server = createServer((socket) => {
  handleServeConnection(socket);
});
function cleanup() {
  server.close();
  try {
    unlinkSync(sockPath);
  } catch {
  }
}
process.on("SIGTERM", () => {
  session.stop();
});
server.listen(sockPath, () => {
  chmodSync(sockPath, 384);
  const pid = session.start();
  sendToServe({ type: "worker_ready", pid });
});
//# sourceMappingURL=session-worker.js.map