"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/approval/index.ts
var approval_exports = {};
__export(approval_exports, {
  MemoryProposalAuditLogger: () => MemoryProposalAuditLogger,
  MemoryProposalStorage: () => MemoryProposalStorage,
  ProposalStateError: () => ProposalStateError,
  approveProposal: () => approveProposal,
  buildApprovalMessage: () => buildApprovalMessage,
  completeProposal: () => completeProposal,
  createProposal: () => createProposal,
  dismissProposal: () => dismissProposal,
  editProposal: () => editProposal,
  parseApprovalCallback: () => parseApprovalCallback,
  snoozeProposal: () => snoozeProposal
});
module.exports = __toCommonJS(approval_exports);

// src/approval/handlers.ts
var HAIKU_MODEL_DEFAULT = "claude-haiku-4-5-20251001";
var ProposalStateError = class extends Error {
  constructor(message) {
    super(message);
    this.name = "ProposalStateError";
  }
};
async function recordAudit(opts) {
  try {
    await opts.audit.record({
      proposalId: opts.proposal.id,
      agentId: opts.proposal.agentId,
      ...opts.proposal.userId !== void 0 ? { userId: opts.proposal.userId } : {},
      type: opts.proposal.type,
      transition: opts.transition,
      status: opts.proposal.status,
      ...opts.note !== void 0 ? { note: opts.note } : {},
      ...opts.meta !== void 0 ? { meta: opts.meta } : {},
      occurredAt: /* @__PURE__ */ new Date()
    });
  } catch (err) {
    opts.ctx.log.error("approval: audit.record threw", {
      proposalId: opts.proposal.id,
      transition: opts.transition,
      error: err instanceof Error ? err.message : String(err)
    });
  }
}
async function createProposal(args) {
  const proposal = await args.storage.create(args.record);
  await recordAudit({
    audit: args.audit,
    proposal,
    transition: "CREATED",
    ctx: args.ctx
  });
  return proposal;
}
async function approveProposal(args) {
  const proposal = await args.storage.get(args.id);
  if (!proposal) {
    throw new ProposalStateError(`Proposal ${args.id} not found`);
  }
  if (proposal.status !== "PENDING" && proposal.status !== "SNOOZED") {
    throw new ProposalStateError(
      `Proposal ${args.id} is ${proposal.status}; only PENDING/SNOOZED can be approved`
    );
  }
  let result;
  try {
    result = await args.executor(proposal.payload, args.ctx);
  } catch (err) {
    const errorMessage = err instanceof Error ? err.message : String(err);
    await recordAudit({
      audit: args.audit,
      proposal,
      transition: "EXECUTOR_FAILED",
      note: errorMessage,
      ctx: args.ctx
    });
    return { executed: false, error: errorMessage };
  }
  const completedAt = /* @__PURE__ */ new Date();
  const updated = await args.storage.update(args.id, {
    status: "COMPLETED",
    completedAt
  });
  await recordAudit({
    audit: args.audit,
    proposal: updated,
    transition: "APPROVED",
    meta: { hasResult: result !== void 0 && result !== null },
    ctx: args.ctx
  });
  return { executed: true, result };
}
async function snoozeProposal(args) {
  const proposal = await args.storage.get(args.id);
  if (!proposal) {
    throw new ProposalStateError(`Proposal ${args.id} not found`);
  }
  if (proposal.status !== "PENDING" && proposal.status !== "SNOOZED") {
    throw new ProposalStateError(
      `Proposal ${args.id} is ${proposal.status}; only PENDING/SNOOZED can be snoozed`
    );
  }
  const updated = await args.storage.update(args.id, {
    status: "SNOOZED",
    snoozedUntil: args.until
  });
  await recordAudit({
    audit: args.audit,
    proposal: updated,
    transition: "SNOOZED",
    meta: { snoozedUntil: args.until.toISOString() },
    ctx: args.ctx
  });
  return updated;
}
async function completeProposal(args) {
  const proposal = await args.storage.get(args.id);
  if (!proposal) {
    throw new ProposalStateError(`Proposal ${args.id} not found`);
  }
  if (proposal.status === "DISMISSED" || proposal.status === "COMPLETED") {
    throw new ProposalStateError(
      `Proposal ${args.id} is already ${proposal.status}`
    );
  }
  const updated = await args.storage.update(args.id, {
    status: "COMPLETED",
    completedAt: /* @__PURE__ */ new Date()
  });
  await recordAudit({
    audit: args.audit,
    proposal: updated,
    transition: "COMPLETED",
    ...args.note !== void 0 ? { note: args.note } : {},
    ctx: args.ctx
  });
  return updated;
}
async function dismissProposal(args) {
  const proposal = await args.storage.get(args.id);
  if (!proposal) {
    throw new ProposalStateError(`Proposal ${args.id} not found`);
  }
  if (proposal.status === "DISMISSED" || proposal.status === "COMPLETED") {
    throw new ProposalStateError(
      `Proposal ${args.id} is already ${proposal.status}`
    );
  }
  const updated = await args.storage.update(args.id, {
    status: "DISMISSED",
    dismissedAt: /* @__PURE__ */ new Date()
  });
  await recordAudit({
    audit: args.audit,
    proposal: updated,
    transition: "DISMISSED",
    ...args.reason !== void 0 ? { note: args.reason } : {},
    ctx: args.ctx
  });
  return updated;
}
var DEFAULT_MERGE_SYSTEM_PROMPT = [
  "You merge a user's free-text correction into a structured JSON payload.",
  "Rules:",
  "1. Return a SINGLE JSON object \u2014 the new payload \u2014 and nothing else.",
  "2. Preserve every field from the ORIGINAL payload that the user did not mention.",
  "3. Apply the user's CORRECTION to the fields they did mention.",
  "4. Do not invent new fields that are not present in the original payload.",
  "5. Match field types exactly \u2014 strings stay strings, numbers stay numbers.",
  "Output: a JSON object, no prose, no code fences."
].join("\n");
function extractJsonObject(text) {
  const trimmed = text.trim();
  const fenceMatch = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
  const candidate = fenceMatch ? fenceMatch[1].trim() : trimmed;
  return JSON.parse(candidate);
}
async function editProposal(args) {
  const proposal = await args.storage.get(args.id);
  if (!proposal) {
    throw new ProposalStateError(`Proposal ${args.id} not found`);
  }
  if (proposal.status !== "PENDING" && proposal.status !== "SNOOZED") {
    throw new ProposalStateError(
      `Proposal ${args.id} is ${proposal.status}; only PENDING/SNOOZED can be edited`
    );
  }
  const system = args.systemPrompt ?? DEFAULT_MERGE_SYSTEM_PROMPT;
  const userPromptParts = [
    `ORIGINAL payload:
${JSON.stringify(proposal.payload, null, 2)}`,
    args.payloadShapeHint ? `Field shape: ${args.payloadShapeHint}` : null,
    `USER CORRECTION:
${args.userCorrection}`,
    "Return the merged JSON payload only."
  ].filter((part) => part !== null);
  const response = await args.haikuClient.messages.create({
    model: args.haikuModel ?? HAIKU_MODEL_DEFAULT,
    max_tokens: 1024,
    system,
    messages: [{ role: "user", content: userPromptParts.join("\n\n") }]
  });
  const textBlock = response.content.find(
    (block) => block.type === "text"
  );
  if (!textBlock) {
    throw new Error("editProposal: Haiku returned no text block");
  }
  let mergedPayload;
  try {
    mergedPayload = extractJsonObject(textBlock.text);
  } catch (err) {
    throw new Error(
      `editProposal: Haiku output was not valid JSON: ${err instanceof Error ? err.message : String(err)}. Raw: ${textBlock.text.slice(0, 200)}`
    );
  }
  const originalPayload = proposal.originalPayload !== void 0 ? proposal.originalPayload : proposal.payload;
  const updated = await args.storage.update(args.id, {
    payload: mergedPayload,
    originalPayload,
    status: "PENDING"
  });
  await recordAudit({
    audit: args.audit,
    proposal: updated,
    transition: "EDITED",
    note: args.userCorrection,
    ctx: args.ctx
  });
  return updated;
}

// src/telegram/inline-keyboard.ts
function createInlineKeyboard(rows) {
  return { inline_keyboard: rows };
}
function actionButton(text, action, payload = "") {
  const data = payload ? `${action}|${payload}` : action;
  if (data.length > 64) {
    throw new Error(
      `actionButton: callback_data exceeds Telegram's 64-byte limit (got ${data.length})`
    );
  }
  return { text, callback_data: data };
}

// src/telegram/pending-proposal.ts
function createApprovalKeyboard(proposalId, opts = {}) {
  const yesAction = opts.yesAction ?? "approve";
  const editAction = opts.editAction ?? "edit";
  const discardAction = opts.discardAction ?? "discard";
  return createInlineKeyboard([
    [
      actionButton(opts.yesLabel ?? "\u2705 Yes", yesAction, proposalId),
      actionButton(opts.editLabel ?? "\u270F\uFE0F Edit", editAction, proposalId),
      actionButton(opts.discardLabel ?? "\u{1F5D1} Discard", discardAction, proposalId)
    ]
  ]);
}

// src/approval/telegram.ts
var APPROVAL_ACTIONS = [
  "approve",
  "edit",
  "discard"
];
function buildApprovalMessage(proposal, opts = {}) {
  const titlePrefix = opts.titlePrefix ?? "\u{1F4DD} ";
  const showRationale = opts.showRationale ?? true;
  const lines = [`${titlePrefix}${proposal.title}`];
  if (showRationale && proposal.rationale) {
    lines.push("", proposal.rationale);
  }
  const keyboardOpts = {};
  if (opts.yesLabel !== void 0) keyboardOpts.yesLabel = opts.yesLabel;
  if (opts.editLabel !== void 0) keyboardOpts.editLabel = opts.editLabel;
  if (opts.discardLabel !== void 0) {
    keyboardOpts.discardLabel = opts.discardLabel;
  }
  return {
    text: lines.join("\n"),
    inlineKeyboard: createApprovalKeyboard(proposal.id, keyboardOpts)
  };
}
function parseApprovalCallback(callbackData) {
  const separatorIdx = callbackData.indexOf("|");
  if (separatorIdx <= 0 || separatorIdx === callbackData.length - 1) {
    return null;
  }
  const action = callbackData.slice(0, separatorIdx);
  const proposalId = callbackData.slice(separatorIdx + 1);
  if (!APPROVAL_ACTIONS.includes(action)) {
    return null;
  }
  return { action, proposalId };
}

// src/approval/mock-storage.ts
var import_node_crypto = require("crypto");
var MemoryProposalStorage = class {
  store = /* @__PURE__ */ new Map();
  /** Override the clock for deterministic tests. */
  now = () => /* @__PURE__ */ new Date();
  async create(record) {
    const id = (0, import_node_crypto.randomUUID)();
    const created = {
      ...record,
      id,
      status: "PENDING",
      createdAt: this.now()
    };
    this.store.set(id, created);
    return created;
  }
  async get(id) {
    return this.store.get(id) ?? null;
  }
  async update(id, patch) {
    const existing = this.store.get(id);
    if (!existing) {
      throw new Error(`MemoryProposalStorage: proposal ${id} not found`);
    }
    const updated = { ...existing, ...patch };
    this.store.set(id, updated);
    return updated;
  }
  async listPending(agentId, userId) {
    const now = this.now();
    return Array.from(this.store.values()).filter((p) => p.agentId === agentId).filter((p) => userId === void 0 ? true : p.userId === userId).filter((p) => {
      if (p.status === "PENDING") return true;
      if (p.status === "SNOOZED") {
        return !p.snoozedUntil || p.snoozedUntil <= now;
      }
      return false;
    }).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
  }
  async listByStatus(status, agentId, userId, limit) {
    const rows = Array.from(this.store.values()).filter((p) => p.status === status).filter((p) => p.agentId === agentId).filter((p) => userId === void 0 ? true : p.userId === userId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
    return limit !== void 0 ? rows.slice(0, limit) : rows;
  }
  size() {
    return this.store.size;
  }
  clear() {
    this.store.clear();
  }
};

// src/approval/mock-audit.ts
var MemoryProposalAuditLogger = class {
  events = [];
  record(event) {
    this.events.push(event);
  }
  clear() {
    this.events.length = 0;
  }
  byTransition(transition) {
    return this.events.filter((e) => e.transition === transition);
  }
  forProposal(proposalId) {
    return this.events.filter((e) => e.proposalId === proposalId);
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  MemoryProposalAuditLogger,
  MemoryProposalStorage,
  ProposalStateError,
  approveProposal,
  buildApprovalMessage,
  completeProposal,
  createProposal,
  dismissProposal,
  editProposal,
  parseApprovalCallback,
  snoozeProposal
});
//# sourceMappingURL=index.js.map