'use strict';

// src/agent-harness.ts
function asRecord(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return value;
}
function asArray(value) {
  return Array.isArray(value) ? value : [];
}
function toSortedRecords(value) {
  return Object.values(asRecord(value) || {}).map((entry) => asRecord(entry)).filter((entry) => Boolean(entry));
}
function uniqueStrings(values, maxCount) {
  const seen = /* @__PURE__ */ new Set();
  const output = [];
  for (const value of values) {
    if (typeof value !== "string") continue;
    const normalized = value.trim();
    if (!normalized || seen.has(normalized)) continue;
    seen.add(normalized);
    output.push(normalized);
    if (typeof maxCount === "number" && output.length >= maxCount) break;
  }
  return output;
}
function formatScalar(value) {
  if (typeof value === "string") return JSON.stringify(value);
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  if (value === null) return "null";
  return "unknown";
}
function describeHeapEntry(entry, previewFieldLimit = 3) {
  const headline = entry.label || entry.id || entry.path || "Unknown";
  const pathLabel = entry.path && entry.path !== headline ? ` <${entry.path}>` : "";
  const classLabel = entry.className || "unknown";
  const preview = asArray(entry.fields).filter((field) => field?.name && field.name !== "_realId" && field.name !== "real_id").slice(0, previewFieldLimit).map((field) => `${field.name}=${formatScalar(field.value)}`).join(", ");
  return preview ? `${headline}${pathLabel} [${classLabel}] ${preview}` : `${headline}${pathLabel} [${classLabel}]`;
}
function hashString(value) {
  if (!value) return null;
  let hash = 2166136261;
  for (let index = 0; index < value.length; index += 1) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return (hash >>> 0).toString(16).padStart(8, "0");
}
function hasSubstantiveAwaitAfterPrompt(code, marker) {
  const startIndex = code.indexOf(marker);
  if (startIndex === -1) return true;
  const segment = code.slice(startIndex + marker.length);
  const callMatches = segment.matchAll(/await\s+([A-Za-z0-9_$.]+)\.([A-Za-z0-9_]+)\s*\(/g);
  for (const match of callMatches) {
    const receiver = match[1] || "";
    const method = match[2] || "";
    if (receiver === "loop" || receiver === "heap") continue;
    if (method.startsWith("get_") || method.startsWith("get")) continue;
    return true;
  }
  return false;
}
function reviewGeneratedJobCode(code) {
  const normalized = typeof code === "string" ? code : "";
  if (!normalized.trim()) return [];
  const issues = [];
  if (/require\s*\(\s*['"]\.\/sandbox-tools['"]\s*\)/.test(normalized)) {
    issues.push({
      code: "commonjs_require",
      severity: "error",
      message: "Use ESM imports like `import { Customer, loop } from './sandbox-tools';` instead of require('./sandbox-tools'). Generated jobs must be plain runnable JavaScript for the sandbox runtime."
    });
  }
  const placeholderPatterns = [
    /ready to make the change next/i,
    /ready to .* next/i,
    /ready to .* now/i,
    /i can make the change now/i,
    /i can do that next/i,
    /i'?m ready to continue/i,
    /have your approval .* ready to make/i,
    /approved\./i
  ];
  if (normalized.includes("await loop.confirm(")) {
    const postConfirm = normalized.slice(normalized.indexOf("await loop.confirm("));
    const hasPlaceholder = placeholderPatterns.some((pattern) => pattern.test(postConfirm));
    const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(normalized, "await loop.confirm(");
    if (!hasSubstantiveAwait || hasPlaceholder) {
      issues.push({
        code: "placeholder_after_confirm",
        severity: "error",
        message: "After await loop.confirm(...) returns true, the job must perform the approved mutation in the same resumed run. Do not stop with placeholder text like 'Approved, I can make the change now.'"
      });
    }
  }
  if (normalized.includes("await loop.ask_user(")) {
    const postPrompt = normalized.slice(normalized.indexOf("await loop.ask_user("));
    const hasPlaceholder = placeholderPatterns.some((pattern) => pattern.test(postPrompt));
    const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(normalized, "await loop.ask_user(");
    if (hasPlaceholder && !hasSubstantiveAwait) {
      issues.push({
        code: "placeholder_after_ask_user",
        severity: "error",
        message: "After await loop.ask_user(...) returns a usable answer, continue the workflow in the same resumed run instead of stopping with placeholder text about doing the work later."
      });
    }
    const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
    for (const call of askUserCalls) {
      const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
      const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
      const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
      const includesShortlistOptions = /options\s*:\s*\[/.test(call);
      if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
        issues.push({
          code: "disambiguation_requires_choice",
          severity: "error",
          message: "When asking the user to choose between known concrete records such as invoices, orders, shipments, requests, cases, or work orders, use loop.ask_user({ type: 'choice', options: [...] }) with a short explicit shortlist instead of a free-text input."
        });
        break;
      }
    }
  }
  const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized);
  const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
  const closesLoop = /loop\.close_loop\s*\(/.test(normalized);
  if (!hasConversationalReturn && returnsObjectLiteral && !closesLoop) {
    issues.push({
      code: "missing_user_reply",
      severity: "error",
      message: "User-facing jobs must end with a natural-language answer. Return a short string, an object with a top-level `reply` string, or use agent_message({ reply, show }). Do not end with bare structured JSON."
    });
  }
  return issues;
}
function extractFocusHintsFromActionSummary(actionSummaryLines) {
  const variableNames = [];
  const listNames = [];
  const entryPaths = [];
  for (const line of actionSummaryLines) {
    const variableMatch = line.match(/\bname="([^"]+)"/);
    if (variableMatch?.[1]) {
      variableNames.push(variableMatch[1]);
    }
    const listMatch = line.match(/\bsaveAs="([^"]+)"/);
    if (listMatch?.[1]) {
      listNames.push(listMatch[1]);
    }
    const pathMatch = line.match(/\bpath="([^"]+)"/);
    if (pathMatch?.[1]) {
      entryPaths.push(pathMatch[1]);
    }
  }
  return {
    variableNames: uniqueStrings(variableNames, 6),
    listNames: uniqueStrings(listNames, 6),
    entryPaths: uniqueStrings(entryPaths, 8)
  };
}
function getCurrentClosureId(liveDoc) {
  const loop = asRecord(liveDoc?.loop);
  return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
}
function getLatestClosure(liveDoc) {
  const loop = asRecord(liveDoc?.loop);
  const currentClosureId = getCurrentClosureId(liveDoc);
  const closuresById = asRecord(loop?.closuresById) || {};
  const currentClosure = currentClosureId ? asRecord(closuresById[currentClosureId]) : null;
  if (currentClosure) {
    return {
      ...currentClosure,
      closureId: currentClosureId || void 0
    };
  }
  const closures = [];
  for (const [closureId, value] of Object.entries(closuresById)) {
    const record = asRecord(value);
    if (!record) continue;
    closures.push({ ...record, closureId });
  }
  closures.sort((left, right) => (Number(right.createdAt) || 0) - (Number(left.createdAt) || 0));
  return closures[0] || null;
}
function getWorkflowBoundary(liveDoc, options) {
  const latestClosure = getLatestClosure(liveDoc);
  const explicitBoundaryTimestamp = typeof options?.boundaryTimestamp === "number" ? options.boundaryTimestamp : null;
  const latestClosureTimestamp = Number(latestClosure?.createdAt) || 0;
  if (explicitBoundaryTimestamp !== null && explicitBoundaryTimestamp > latestClosureTimestamp) {
    return {
      timestamp: explicitBoundaryTimestamp,
      reason: "request_start",
      latestClosureId: null
    };
  }
  if (!latestClosure) {
    return {
      timestamp: explicitBoundaryTimestamp ?? 0,
      reason: explicitBoundaryTimestamp !== null && explicitBoundaryTimestamp > 0 ? "request_start" : "session_start",
      latestClosureId: null
    };
  }
  return {
    timestamp: Number(latestClosure.createdAt) || 0,
    reason: "last_closed_loop",
    latestClosureId: typeof latestClosure.closureId === "string" ? latestClosure.closureId : null
  };
}
function getJobTimestamp(job) {
  return Number(job.finishedAt) || Number(job.startedAt) || Number(job.submittedAt) || 0;
}
function getJobRecords(liveDoc) {
  const jobsById = asRecord(asRecord(liveDoc?.jobs)?.byId) || {};
  const jobs = [];
  for (const [jobId, value] of Object.entries(jobsById)) {
    const record = asRecord(value);
    if (!record) continue;
    jobs.push({ ...record, jobId });
  }
  jobs.sort((left, right) => getJobTimestamp(right) - getJobTimestamp(left));
  return jobs;
}
function getPromptRecordsFromJobs(liveDoc) {
  return getJobRecords(liveDoc).flatMap((job) => Object.values(asRecord(job.prompts) || {})).map((prompt) => asRecord(prompt)).filter((prompt) => Boolean(prompt));
}
function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
  const boundary = getWorkflowBoundary(liveDoc, options);
  const jobs = getJobRecords(liveDoc).filter((job) => getJobTimestamp(job) >= boundary.timestamp).slice(0, 6).reverse();
  const actionSummaryLines = [];
  const variableNames = [];
  const listNames = [];
  const entryPaths = [];
  const activeTaskIds = [];
  const openDecisionIds = [];
  const openPromptIds = [];
  for (const job of jobs) {
    for (const line of asArray(job.actionSummary)) {
      if (typeof line === "string" && line.trim()) {
        actionSummaryLines.push(line.trim());
      }
    }
    for (const rawEvent of asArray(job.actionTrace)) {
      const event = asRecord(rawEvent);
      const details = asRecord(event?.details);
      const outcome = asRecord(event?.outcome);
      if (typeof details?.name === "string") {
        variableNames.push(details.name);
      }
      if (typeof details?.saveAs === "string") {
        listNames.push(details.saveAs);
      }
      if (typeof details?.path === "string") {
        entryPaths.push(details.path);
      }
      if (typeof details?.taskId === "string") {
        activeTaskIds.push(details.taskId);
      }
      if (typeof details?.decisionId === "string") {
        openDecisionIds.push(details.decisionId);
      }
      if (typeof outcome?.taskId === "string") {
        activeTaskIds.push(outcome.taskId);
      }
      if (typeof outcome?.decisionId === "string") {
        openDecisionIds.push(outcome.decisionId);
      }
      if (typeof outcome?.promptId === "string") {
        openPromptIds.push(outcome.promptId);
      }
    }
  }
  const loop = asRecord(liveDoc?.loop);
  const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
    const updatedAt = Number(task.updatedAt) || Number(task.createdAt) || 0;
    const status = typeof task.status === "string" ? task.status : "pending";
    if (boundary.reason === "request_start") {
      return updatedAt >= boundary.timestamp;
    }
    return status !== "completed" && status !== "canceled" ? true : updatedAt >= boundary.timestamp;
  }).sort((left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0));
  for (const task of tasks.slice(0, 4)) {
    if (typeof task.taskId === "string") {
      activeTaskIds.push(task.taskId);
    }
  }
  const decisions = toSortedRecords(loop?.decisionsById).filter((decision) => {
    const updatedAt = Number(decision.updatedAt) || Number(decision.createdAt) || 0;
    if (boundary.reason === "request_start") {
      return updatedAt >= boundary.timestamp;
    }
    return decision.status === "open" || updatedAt >= boundary.timestamp;
  }).sort((left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0));
  for (const decision of decisions.slice(0, 3)) {
    if (typeof decision.decisionId === "string" && decision.status === "open") {
      openDecisionIds.push(decision.decisionId);
    }
  }
  const promptRecords = getPromptRecordsFromJobs(liveDoc).filter((prompt) => {
    const openedAt = Number(prompt.openedAt) || 0;
    if (boundary.reason === "request_start") {
      return openedAt >= boundary.timestamp;
    }
    return prompt.status === "open" || openedAt >= boundary.timestamp;
  });
  for (const prompt of promptRecords) {
    const promptId = typeof prompt.promptId === "string" ? prompt.promptId : typeof prompt.id === "string" ? prompt.id : null;
    if (promptId && prompt.status === "open") {
      openPromptIds.push(promptId);
    }
  }
  for (const prompt of pendingPrompts) {
    if (typeof prompt.id === "string") {
      openPromptIds.push(prompt.id);
    }
  }
  const heap = asRecord(liveDoc?.heap);
  const variablesByName = asRecord(heap?.variablesByName) || {};
  const listsByName = asRecord(heap?.listsByName) || {};
  const recentHints = extractFocusHintsFromActionSummary(actionSummaryLines);
  variableNames.push(...recentHints.variableNames);
  listNames.push(...recentHints.listNames);
  entryPaths.push(...recentHints.entryPaths);
  for (const variableName of uniqueStrings(variableNames)) {
    const variable = asRecord(variablesByName[variableName]);
    if (!variable) continue;
    if (typeof variable.listName === "string") {
      listNames.push(variable.listName);
    }
    if (typeof variable.entryPath === "string") {
      entryPaths.push(variable.entryPath);
    }
  }
  for (const listName of uniqueStrings(listNames)) {
    const list = asRecord(listsByName[listName]);
    for (const path of asArray(list?.paths).slice(0, 4)) {
      entryPaths.push(path);
    }
  }
  if (variableNames.length === 0 && boundary.reason !== "request_start") {
    const recentVariables = Object.values(variablesByName).map((value) => asRecord(value)).filter((value) => Boolean(value)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, 3);
    for (const variable of recentVariables) {
      if (typeof variable.name === "string") {
        variableNames.push(variable.name);
      }
      if (typeof variable.listName === "string") {
        listNames.push(variable.listName);
      }
      if (typeof variable.entryPath === "string") {
        entryPaths.push(variable.entryPath);
      }
    }
  }
  return {
    boundaryTimestamp: boundary.timestamp,
    boundaryReason: boundary.reason,
    latestClosureId: boundary.latestClosureId,
    activeTaskIds: uniqueStrings(activeTaskIds, 4),
    openDecisionIds: uniqueStrings(openDecisionIds, 3),
    openPromptIds: uniqueStrings(openPromptIds, 3),
    variableNames: uniqueStrings(variableNames, 4),
    listNames: uniqueStrings(listNames, 4),
    entryPaths: uniqueStrings(entryPaths, 6),
    recentActionSummary: uniqueStrings(actionSummaryLines, 8)
  };
}
function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
  const focus = projectWorkflowFocus(liveDoc, pendingPrompts, options);
  const lines = [];
  lines.push("Workflow Boundary:");
  if (focus.boundaryReason === "request_start") {
    lines.push("- Start from work recorded after the current user request began.");
  } else if (focus.boundaryReason === "last_closed_loop" && focus.latestClosureId) {
    lines.push(`- Start from work recorded after ${focus.latestClosureId}.`);
  } else {
    lines.push("- No prior closed loop recorded; use the latest user request as the boundary.");
  }
  lines.push("", "Recent Actions:");
  if (focus.recentActionSummary.length === 0) {
    lines.push("- none");
  } else {
    for (const line of focus.recentActionSummary) {
      lines.push(line.startsWith("- ") ? line : `- ${line}`);
    }
  }
  lines.push("", "Working Set Hints:");
  if (focus.variableNames.length === 0 && focus.listNames.length === 0 && focus.entryPaths.length === 0) {
    lines.push("- none");
  } else {
    if (focus.variableNames.length > 0) {
      lines.push(`- variables: ${focus.variableNames.join(", ")}`);
    }
    if (focus.listNames.length > 0) {
      lines.push(`- lists: ${focus.listNames.join(", ")}`);
    }
    if (focus.entryPaths.length > 0) {
      lines.push(`- entries: ${focus.entryPaths.join(", ")}`);
    }
  }
  lines.push("", "Open Workflow Handles:");
  if (focus.activeTaskIds.length === 0 && focus.openDecisionIds.length === 0 && focus.openPromptIds.length === 0) {
    lines.push("- none");
  } else {
    if (focus.activeTaskIds.length > 0) {
      lines.push(`- tasks: ${focus.activeTaskIds.join(", ")}`);
    }
    if (focus.openDecisionIds.length > 0) {
      lines.push(`- decisions: ${focus.openDecisionIds.join(", ")}`);
    }
    if (focus.openPromptIds.length > 0) {
      lines.push(`- prompts: ${focus.openPromptIds.join(", ")}`);
    }
  }
  return lines.join("\n");
}
function hasOpenPrompt(liveDoc, pendingPrompts) {
  if (pendingPrompts.length > 0) return true;
  const jobsById = asRecord(asRecord(liveDoc?.jobs)?.byId) || {};
  for (const job of Object.values(jobsById)) {
    const prompts = asRecord(asRecord(job)?.prompts);
    if (!prompts) continue;
    for (const prompt of Object.values(prompts)) {
      const record = asRecord(prompt);
      if (record?.status === "open") return true;
    }
  }
  return false;
}
function getExclusivePromptTarget(pendingPrompts) {
  if (pendingPrompts.length !== 1) return null;
  const [prompt] = pendingPrompts;
  return prompt?.type === "input" ? prompt : null;
}
function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
  const lines = [];
  const loop = asRecord(liveDoc?.loop);
  const boundary = getWorkflowBoundary(liveDoc, options);
  const tasks = toSortedRecords(loop?.tasksById).filter((task) => {
    const updatedAt = Number(task.updatedAt) || Number(task.createdAt) || 0;
    const status = typeof task.status === "string" ? task.status : "pending";
    if (boundary.reason === "request_start") {
      return updatedAt >= boundary.timestamp;
    }
    return status !== "completed" && status !== "canceled" ? true : updatedAt >= boundary.timestamp;
  }).sort((left, right) => (Number(right.updatedAt) || 0) - (Number(left.updatedAt) || 0));
  const activeTasks = tasks.filter((task) => {
    const status = typeof task.status === "string" ? task.status : "pending";
    return status !== "completed" && status !== "canceled";
  });
  const visibleTasks = (activeTasks.length > 0 ? activeTasks : tasks).slice(0, 5);
  const hiddenTaskCount = Math.max(0, activeTasks.length - visibleTasks.length);
  lines.push("Tasks:");
  if (visibleTasks.length === 0) {
    lines.push("- none");
  } else {
    lines.push("- Reuse existing taskId values exactly as written below.");
    for (const task of visibleTasks) {
      const title = typeof task.title === "string" ? task.title : "Untitled task";
      const taskId = typeof task.taskId === "string" ? task.taskId : "unknown";
      const status = typeof task.status === "string" ? task.status : "pending";
      const summary = typeof task.summary === "string" && task.summary.trim() ? ` \u2014 ${task.summary.trim()}` : "";
      lines.push(`- [${status}] ${title} (${taskId})${summary}`);
    }
    if (hiddenTaskCount > 0) {
      lines.push(`- ${hiddenTaskCount} more active task(s) omitted`);
    }
  }
  const decisions = toSortedRecords(loop?.decisionsById).filter((decision) => {
    const updatedAt = Number(decision.updatedAt) || Number(decision.createdAt) || 0;
    if (boundary.reason === "request_start") {
      return updatedAt >= boundary.timestamp;
    }
    return decision.status === "open" || updatedAt >= boundary.timestamp;
  }).sort((left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0));
  const openDecisions = decisions.filter((decision) => decision.status === "open");
  const visibleDecisions = (openDecisions.length > 0 ? openDecisions : decisions.slice(0, 1)).slice(0, 3);
  lines.push("", "Recent Decisions:");
  if (visibleDecisions.length === 0) {
    lines.push("- none");
  } else {
    lines.push("- Reuse existing decisionId values exactly as written below.");
    for (const decision of visibleDecisions) {
      const status = typeof decision.status === "string" ? decision.status : "resolved";
      const title = typeof decision.title === "string" && decision.title.trim() ? decision.title.trim() : "Decision";
      const decisionId = typeof decision.decisionId === "string" ? decision.decisionId : "unknown";
      if (status === "open") {
        const candidatePreview = asArray(decision.candidates).slice(0, 3).map((candidate) => {
          const record = asRecord(candidate);
          if (!record) return null;
          const candidateId = typeof record.id === "string" ? record.id : "unknown";
          const candidateLabel = typeof record.label === "string" && record.label.trim() ? record.label.trim() : candidateId;
          return candidateLabel === candidateId ? candidateId : `${candidateLabel} (${candidateId})`;
        }).filter((value) => Boolean(value)).join(", ");
        lines.push(`- [open] ${title} (${decisionId})${candidatePreview ? ` \u2014 candidates: ${candidatePreview}` : ""}`);
      } else {
        const selected = asRecord(decision.selected);
        const label = typeof selected?.label === "string" ? selected.label : typeof selected?.id === "string" ? selected.id : "unknown";
        lines.push(`- [resolved] ${title} (${decisionId}) -> ${label}`);
      }
    }
  }
  const openPrompts = [
    ...pendingPrompts.map((prompt) => ({
      id: prompt.id,
      type: prompt.type,
      title: prompt.title,
      message: prompt.message
    })),
    ...Object.values(asRecord(asRecord(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord(asRecord(job)?.prompts) || {})).map((prompt) => asRecord(prompt)).filter((prompt) => Boolean(prompt && prompt.status === "open"))
  ];
  const visiblePrompts = boundary.reason === "request_start" ? openPrompts.filter((prompt) => {
    const promptRecord = asRecord(prompt);
    const openedAt = Number(promptRecord?.openedAt) || 0;
    const promptId = typeof promptRecord?.id === "string" ? promptRecord.id : typeof prompt.id === "string" ? prompt.id : null;
    return openedAt >= boundary.timestamp || (promptId ? pendingPrompts.some((pendingPrompt) => pendingPrompt.id === promptId) : false);
  }) : openPrompts;
  lines.push("", "Open Prompts:");
  if (visiblePrompts.length === 0) {
    lines.push("- none");
  } else {
    for (const prompt of visiblePrompts.slice(0, 3)) {
      const title = typeof prompt.title === "string" && prompt.title.trim() ? prompt.title.trim() : "Input required";
      const type = typeof prompt.type === "string" ? prompt.type : "input";
      const message = typeof prompt.message === "string" && prompt.message.trim() ? ` \u2014 ${prompt.message.trim()}` : "";
      lines.push(`- [${type}] ${title}${message}`);
    }
  }
  const currentClosureId = getCurrentClosureId(liveDoc);
  const closureRecord = currentClosureId ? asRecord(asRecord(loop?.closuresById)?.[currentClosureId]) : null;
  const visibleClosure = closureRecord && (boundary.reason !== "request_start" || (Number(closureRecord.createdAt) || 0) >= boundary.timestamp) ? closureRecord : null;
  lines.push("", "Loop Closure:");
  if (visibleClosure) {
    const status = typeof visibleClosure.status === "string" ? visibleClosure.status : "completed";
    const summary = typeof visibleClosure.summary === "string" ? visibleClosure.summary : "No summary";
    lines.push(`- current: [${status}] ${summary} (${currentClosureId})`);
  } else {
    lines.push("- none");
  }
  return lines.join("\n");
}
function projectHeapSummary(heap, options) {
  const heapRecord = asRecord(heap) || {};
  const entriesByPath = asRecord(heapRecord.entriesByPath) || {};
  const listsByName = asRecord(heapRecord.listsByName) || {};
  const variablesByName = asRecord(heapRecord.variablesByName) || {};
  const focusedVariableNames = new Set(uniqueStrings(options?.focus?.variableNames || []));
  const focusedListNames = new Set(uniqueStrings(options?.focus?.listNames || []));
  const focusedEntryPaths = new Set(uniqueStrings(options?.focus?.entryPaths || []));
  const hasFocus = focusedVariableNames.size > 0 || focusedListNames.size > 0 || focusedEntryPaths.size > 0;
  const suppressRecentFallback = Boolean(options?.focus) && !hasFocus;
  const maxVariables = options?.maxVariables ?? (hasFocus ? 4 : 6);
  const maxLists = options?.maxLists ?? (hasFocus ? 3 : 4);
  const maxEntries = options?.maxEntries ?? (hasFocus ? 5 : 6);
  const variables = suppressRecentFallback ? [] : Object.values(variablesByName).map((value) => asRecord(value)).filter((value) => Boolean(value)).sort((left, right) => {
    const leftFocused = left.name && focusedVariableNames.has(left.name) ? 1 : 0;
    const rightFocused = right.name && focusedVariableNames.has(right.name) ? 1 : 0;
    return rightFocused - leftFocused || (right.updatedAt || 0) - (left.updatedAt || 0);
  }).filter((variable, index) => {
    if (index < maxVariables) return true;
    return Boolean(variable.name && focusedVariableNames.has(variable.name));
  }).slice(0, maxVariables);
  const referencedPaths = /* @__PURE__ */ new Set();
  for (const variable of variables) {
    if (variable.entryPath) referencedPaths.add(variable.entryPath);
    if (variable.listName) {
      const list = asRecord(listsByName[variable.listName]);
      for (const path of list?.paths || []) referencedPaths.add(path);
    }
  }
  for (const path of focusedEntryPaths) {
    referencedPaths.add(path);
  }
  const visibleLists = Object.values(listsByName).map((value) => asRecord(value)).filter((value) => Boolean(value)).filter((list) => variables.some((variable) => variable.listName === list.name) || Boolean(list.name && focusedListNames.has(list.name))).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxLists);
  const visibleEntries = Object.values(entriesByPath).map((value) => asRecord(value)).filter((value) => Boolean(value)).filter((entry) => entry.path && referencedPaths.has(entry.path)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxEntries);
  const lines = [];
  lines.push("Variables:");
  if (variables.length === 0) {
    lines.push("- none");
  } else {
    for (const variable of variables) {
      if (variable.kind === "scalar") {
        lines.push(`- ${variable.name}: scalar = ${formatScalar(variable.value)}`);
        continue;
      }
      if (variable.kind === "entry") {
        const entry = variable.entryPath ? asRecord(entriesByPath[variable.entryPath]) : null;
        lines.push(`- ${variable.name}: entry<${variable.className || entry?.className || "unknown"}> -> ${entry ? describeHeapEntry(entry) : variable.entryPath || "missing"}`);
        continue;
      }
      const list = variable.listName ? asRecord(listsByName[variable.listName]) : null;
      lines.push(`- ${variable.name}: list<${variable.className || list?.className || "unknown"}> -> ${(list?.paths || []).length} item(s)`);
    }
  }
  lines.push("", "Named Lists:");
  if (visibleLists.length === 0) {
    lines.push("- none");
  } else {
    for (const list of visibleLists) {
      lines.push(`- ${list.name}: ${list.className || "unknown"}[${(list.paths || []).length}]`);
    }
  }
  lines.push("", "Active Entries:");
  if (visibleEntries.length === 0) {
    lines.push("- none");
  } else {
    for (const entry of visibleEntries) {
      lines.push(`- ${describeHeapEntry(entry)}`);
    }
  }
  return lines.join("\n");
}
function createHarnessVerifierSnapshot(input) {
  const workflowFocus = projectWorkflowFocus(input.liveDoc, [], input.projectionOptions);
  const heapDigest = hashString(projectHeapSummary(asRecord(input.liveDoc?.heap), {
    focus: workflowFocus
  })) || "00000000";
  const loopDigest = hashString(projectWorkflowSummary(input.liveDoc, [], input.projectionOptions)) || "00000000";
  return {
    codeDigest: hashString(input.finalCode?.trim()),
    resultDigest: hashString(input.resultPreview?.trim()),
    heapDigest,
    loopDigest
  };
}
function evaluateContinuation(input) {
  const maxIterations = input.budgets?.maxIterations ?? 6;
  const maxNoProgressIterations = input.budgets?.maxNoProgressIterations ?? 2;
  if (input.currentClosureId && input.currentClosureId !== input.baselineClosureId) {
    return {
      shouldContinue: false,
      outcome: "done",
      reason: "workflow_closed",
      nextNoProgressCount: 0
    };
  }
  if (hasOpenPrompt(input.liveDoc, input.pendingPrompts)) {
    return {
      shouldContinue: false,
      outcome: "await_user",
      reason: "open_prompt",
      nextNoProgressCount: 0
    };
  }
  const workflowFocus = projectWorkflowFocus(
    input.liveDoc,
    input.pendingPrompts,
    input.projectionOptions
  );
  const hasOpenWorkflowHandles = workflowFocus.activeTaskIds.length > 0 || workflowFocus.openDecisionIds.length > 0 || workflowFocus.openPromptIds.length > 0;
  if (!hasOpenWorkflowHandles && input.latestResponseText?.trim()) {
    return {
      shouldContinue: false,
      outcome: "done",
      reason: "user_reply_complete",
      nextNoProgressCount: 0
    };
  }
  if (input.iteration + 1 >= maxIterations) {
    return {
      shouldContinue: false,
      outcome: "budget_exhausted",
      reason: "iteration_budget_exhausted",
      nextNoProgressCount: input.previousNoProgressCount ?? 0
    };
  }
  if (!input.previousSnapshot) {
    return {
      shouldContinue: true,
      outcome: "continue",
      reason: "no_prior_snapshot",
      nextNoProgressCount: 0
    };
  }
  const madeProgress = input.previousSnapshot.resultDigest !== input.currentSnapshot.resultDigest || input.previousSnapshot.heapDigest !== input.currentSnapshot.heapDigest || input.previousSnapshot.loopDigest !== input.currentSnapshot.loopDigest;
  const nextNoProgressCount = madeProgress ? 0 : (input.previousNoProgressCount ?? 0) + 1;
  if (nextNoProgressCount >= maxNoProgressIterations) {
    return {
      shouldContinue: false,
      outcome: "stalled",
      reason: "stalled_no_progress",
      nextNoProgressCount
    };
  }
  return {
    shouldContinue: true,
    outcome: "continue",
    reason: "progress_detected",
    nextNoProgressCount
  };
}
function buildContinuationInstruction(resultPreview) {
  return [
    "Continue the same user request using the latest structured session state.",
    "Take only the minimum next step that directly helps the user.",
    "Use the active tasks, decisions, prompts, and heap references as the source of truth instead of replaying old work.",
    "If the user names a concrete record that is not already in the heap, fetch it from the graph instead of replying that it is not in context.",
    "If this request clearly spans multiple steps and there are no active tasks yet, create 2-4 short user-visible tasks now.",
    "Reuse any existing taskId and decisionId values exactly as they appear in AGENT LOOP STATE.",
    "Use ask_user with type input for open-ended preferences or missing text. Use type choice only for a short explicit shortlist.",
    "Do not ask for confirmation in plain text. Use loop.confirm(...) when approval is needed.",
    "Await loop.ask_user(...) and loop.confirm(...). Those helpers pause the current job and resume it after the user answers.",
    "After a resumed ask_user or confirm call, continue the same job and perform the newly authorized action when the answer is sufficient. Do not stop with placeholder text like 'I'm ready to do it next.'",
    "If you ask the user a new question in this job, do not also close the loop in the same job.",
    "Write the smallest straightforward code for the current step. Avoid defensive fallback branches for hypothetical states that are not currently true.",
    "Do not repeat completed work, fetch optional extra details, or store extra heap data unless it is needed right now.",
    "If the workflow is now completed, canceled, or blocked, call loop.close_loop(...) before stopping.",
    resultPreview ? `Latest job result:
${resultPreview}` : null
  ].filter(Boolean).join("\n\n");
}
function buildGranularAgentDomainBlock(domainDocumentation) {
  return domainDocumentation?.trim() || "No domain types available. The graph may not be ready yet.";
}
function buildGranularAgentSessionBlock(sessionContext) {
  if (!sessionContext) return "No session metadata available.";
  const rows = [
    ["sandboxId", sessionContext.sandboxId],
    ["environmentId", sessionContext.environmentId],
    ["userId", sessionContext.userId],
    ["granularId", sessionContext.granularId],
    ["userName", sessionContext.userName],
    ["domainRevision", sessionContext.domainRevision]
  ];
  const activeRows = rows.filter(([, value]) => Boolean(value));
  if (activeRows.length === 0) return "No session metadata available.";
  return activeRows.map(([key, value]) => `${key}: ${value}`).join("\n");
}
function buildGranularAgentHeapBlock(heapSummary) {
  return heapSummary?.trim() || "Heap is empty for this session.";
}
function buildGranularAgentLoopBlock(loopSummary) {
  return loopSummary?.trim() || "No active loop state recorded for this session.";
}
function buildGranularAgentWorkflowBlock(workflowSummary) {
  return workflowSummary?.trim() || "No current workflow snapshot recorded for this request yet.";
}
function buildGranularAgentToolBlock(tools) {
  const normalizedTools = (tools || []).filter((tool) => tool?.name).slice().sort((left, right) => {
    const leftScope = `${left.className || "global"}:${left.static ? "static" : "instance"}`;
    const rightScope = `${right.className || "global"}:${right.static ? "static" : "instance"}`;
    return leftScope.localeCompare(rightScope) || left.name.localeCompare(right.name);
  });
  if (normalizedTools.length === 0) {
    return "No live effects are available in this session yet.";
  }
  const globalTools = normalizedTools.filter((tool) => !tool.className);
  const staticTools = normalizedTools.filter((tool) => Boolean(tool.className && tool.static));
  const instanceTools = normalizedTools.filter((tool) => Boolean(tool.className && !tool.static));
  const lines = [
    "Treat this block as the planning map. Use DOMAIN TYPES below for exact signatures."
  ];
  const appendGroup = (title, group) => {
    lines.push(`- ${title}:`);
    if (group.length === 0) {
      lines.push("  none");
      return;
    }
    for (const tool of group.slice(0, 10)) {
      const availability = tool.ready === false ? " [not ready]" : "";
      const description = tool.description?.trim() ? ` - ${tool.description.trim()}` : "";
      lines.push(`  ${tool.name}${availability}${description}`);
    }
    if (group.length > 10) {
      lines.push(`  +${group.length - 10} more`);
    }
  };
  appendGroup("Global effects", globalTools);
  appendGroup("Class-level effects", staticTools);
  appendGroup("Record-level effects", instanceTools);
  return lines.join("\n");
}
function buildGranularAgentCheckpointBlock(checkpoint) {
  if (!checkpoint) {
    return "No previous execution checkpoint recorded for this request yet.";
  }
  const lines = [];
  if (typeof checkpoint.iteration === "number") {
    lines.push(`iteration: ${checkpoint.iteration}`);
  }
  if (checkpoint.latestJobStatus) {
    lines.push(`latestJobStatus: ${checkpoint.latestJobStatus}`);
  }
  if (checkpoint.controllerOutcome) {
    lines.push(`controllerOutcome: ${checkpoint.controllerOutcome}`);
  }
  if (checkpoint.controllerReason) {
    lines.push(`controllerReason: ${checkpoint.controllerReason}`);
  }
  if (typeof checkpoint.noProgressCount === "number") {
    lines.push(`noProgressCount: ${checkpoint.noProgressCount}`);
  }
  if (checkpoint.latestJobError?.trim()) {
    lines.push(`latestJobError: ${checkpoint.latestJobError.trim()}`);
  }
  if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
    lines.push("latestActionSummary:");
    for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
      lines.push(line.startsWith("- ") ? line : `- ${line}`);
    }
  }
  if (checkpoint.latestJobResult?.trim()) {
    lines.push(`latestJobResult:
${checkpoint.latestJobResult.trim()}`);
  }
  return lines.length > 0 ? lines.join("\n") : "No previous execution checkpoint recorded for this request yet.";
}
function buildGranularAgentSystemPrompt(input) {
  const sessionBlock = buildGranularAgentSessionBlock(input.sessionContext);
  const toolBlock = buildGranularAgentToolBlock(input.tools);
  const domainBlock = buildGranularAgentDomainBlock(input.domainDocumentation);
  const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
  const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
  const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
  return `You are an AI assistant for a live Granular session.
You can help the user understand the domain, answer questions, or generate and execute TypeScript code.
Your tone must be natural and human-like.

Call the \`execute_code\` effect ONLY when the user's intent matches the domain's capabilities and requires executing code against the live session. If the user is just asking a general question or if their request doesn't match the available effects or domain types, respond with text to explain.
When you call \`execute_code\`, additional assistant text must be either:
- empty, or
- a brief summary of the actions the generated code will perform.
Do not include any other kind of commentary when calling \`execute_code\`.
- If the next step needs to create or update workflow state in the live session, you must call \`execute_code\`. This includes \`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`, \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
- Never simulate a live prompt, confirmation, decision, task change, or loop closure in plain text. Plain-text replies are only for conversational answers that do not need to mutate session state.

\u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
- While you are writing code, add short single-line comments with the prefix \`// \` before meaningful blocks.
- These comments should explain the intent in friendly product language, not in implementation jargon.
- Comments are shown live as a reasoning trace, so keep them brief, concrete, and useful.
- Do not mention method names, file paths, or internal identifiers in those comments.
- Use only single-line \`//\` comments for this purpose. Do not use block comments.
- If you are replying with text only, you may also include a few leading \`// \` comment lines before the final answer.
- End text-only replies with the plain user-facing answer on normal lines, without a comment prefix.

\u2500\u2500\u2500 RESPONSE STYLE RULES \u2500\u2500\u2500
- Use plain, friendly product language.
- Never mention internal implementation details in user-facing text:
  class names, effect names, method names, function names, file paths, parameter names, or code snippets.
- Never expose dotted identifiers such as \`Class.method\` in user-facing text.
- Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
- If you need clarification, ask in everyday language.
- If the missing information should pause the live workflow for later continuation, ask through \`loop.ask_user(...)\` in generated code rather than with a plain-text question.
- Keep replies concise and clear.
- This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.

\u2500\u2500\u2500 SESSION CONTEXT \u2500\u2500\u2500
${sessionBlock}

\u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
${toolBlock}

\u2500\u2500\u2500 DOMAIN TYPES (TypeScript declarations from ./sandbox-tools) \u2500\u2500\u2500
Import classes and effect functions from \`./sandbox-tools\` in generated code.
Published effects appear as instance or static methods on the classes below, or as top-level \`export declare function\` entries for global effects.

${domainBlock}

\u2500\u2500\u2500 EXECUTION CHECKPOINT \u2500\u2500\u2500
${checkpointBlock}

\u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
${workflowBlock}

\u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
${heapBlock}

\u2500\u2500\u2500 AGENT LOOP STATE \u2500\u2500\u2500
${loopBlock}

\u2500\u2500\u2500 LOOP PLAYBOOK \u2500\u2500\u2500
- A single user request may span several assistant turns and several jobs. Continue from the latest structured session state instead of restarting.
- Treat WORKFLOW SNAPSHOT, EXECUTION CHECKPOINT, SESSION HEAP, and AGENT LOOP STATE as the authoritative working memory for the current request.
- Use CAPABILITY SNAPSHOT to choose the next step quickly, then use DOMAIN TYPES to write exact valid code.
- Use WORKFLOW SNAPSHOT to understand the current boundary, recent actions, and working set before fetching more data.
- If the user names a concrete customer, case, order, shipment, or other record that is not already in the heap, fetch it from the graph. "Not in the current context" is not a sufficient reason to stop.
- Treat user-provided names as human references, not exact database keys. If the user says "Northwind", "the Alpine compressor case", or another shorthand, prefer sensible case-insensitive partial matching across likely records before concluding that nothing matches.
- If exactly one strong partial-name match exists, use it. If several plausible partial matches exist, ask the user to choose instead of failing on an exact-equality lookup.
- Take the minimum next step that directly advances the user's request. Do not do speculative cleanup, enrichment, or bookkeeping.
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from the AGENT LOOP STATE block. Never guess or slugify IDs.
- If the request is ambiguous or clearly multi-step, start by creating 2-4 meaningful user-visible tasks. Do not create a detailed internal checklist.
- Keep tasks updated as the workflow advances. Complete tasks as soon as they are actually done.
- Write the smallest straightforward code that fits the current step. Avoid defensive branches for hypothetical states that are not currently true.
- Before asking a new question, check whether the answer is already present in the current heap, open decisions, or checkpoint.
- If the previous step made no progress, prefer a different concrete action, a narrower fetch, or a user question instead of repeating equivalent code.
- Use \`loop.open_decision(...)\` in one job to store grounded candidates, then \`loop.close_decision(...)\` in a later job to pick one stored candidate with \`selectedId\`.
- Use \`loop.ask_user({ type: 'input', ... })\` only for open-ended preferences or missing free-form text that cannot be represented as a short explicit shortlist.
- If you already have a short concrete shortlist, usually 2-7 candidates, generate code and call \`loop.ask_user({ type: 'choice', ... })\`. Do not downgrade that to a text input.
- If multiple concrete records match a singular user reference such as "the invoice", "the order", "the shipment", or "the request", do not silently choose one by heuristic. Ask the user to choose unless the request already uniquely identifies the record.
- For disambiguation between concrete known records, prefer \`type: 'choice'\` over \`type: 'input'\`. This is especially important for invoices, orders, shipments, requests, work orders, and cases.
- For \`type: 'choice'\` prompts, make the options directly pickable by a human: use a stable value and a readable label that includes the identifier or title they are likely to recognize.
- If a shortlist already exists, do not ask the user to type an exact database key or identifier manually. Present the shortlist as clickable choices instead.
- When reasoning about free-form status strings, do not use brittle substring checks such as \`status.includes("paid")\` because values like \`"unpaid"\` would be misclassified. Prefer explicit positive matches such as \`unpaid\`, \`open\`, or \`overdue\`, or exact normalized comparisons.
- When multiple concrete records match and \`loop.ask_user(...)\` is available, do not stop with a plain-text question like "Which invoice do you mean?". Persist the live workflow and ask through \`await loop.ask_user(...)\` instead.
- If the user could reasonably answer with a partial identifier such as \`abcd\` for \`INV-abcd\`, that is another sign the question should be a \`type: 'choice'\` prompt with visible options rather than a free-text input.
- Never ask for approval in plain text when \`loop.confirm(...)\` is available. Use \`loop.confirm(...)\` for consequential approval.
- When the correct next step is a loop helper action, generate code and call that helper. Do not replace it with a conversational reply.
- If you ask the user a new question in the current job, do not also call \`loop.close_loop(...)\` in that same job.
- When you need user input or approval, await \`loop.ask_user(...)\` or \`loop.confirm(...)\`. The job will pause until the user answers, then resume from that awaited call.
- It is valid to branch on the value returned by \`await loop.ask_user(...)\` or \`await loop.confirm(...)\` after the job resumes.
- After \`await loop.ask_user(...)\` returns a concrete choice, continue the workflow in the same resumed job whenever that answer is enough to act. Do not stop with placeholder text such as "I can do that next" or "I'm ready to continue".
- After \`await loop.confirm(...)\` returns \`true\`, execute the approved mutation in that same resumed job before returning. Do not end with placeholder text like "Approved, ready to make the change next."
- Only stop immediately after a resumed prompt when the user declined, the workflow is now blocked, or you truly still need another missing piece of information.
- If the user says stop, enough, or no further action, close the loop and end cleanly without asking another question.
- If one clear item is already selected and the next step matters, prefer \`loop.confirm(...)\` over another exploratory question.
- If one clear item is already selected and the only missing input is approval to proceed, use \`loop.confirm(...)\` rather than \`loop.ask_user(...)\`.
- If the user already gave a usable scheduling window such as "Tuesday morning", treat that as enough to choose a reasonable concrete slot. Do not open another menu just to choose between nearby sub-slots unless a real conflict or hard business rule forces that follow-up.
- For schedule changes, prefer one grounded recommendation plus one approval prompt. Avoid a second prompt for optional time-window micro-choices when you can pick a sensible default that still satisfies the user request.
- If the user explicitly instructs you to perform a consequential action now, that instruction counts as approval. Do not add an extra confirmation step unless the user expressed hesitation, ambiguity, or asked you not to execute yet.
- Direct imperatives such as "cancel this order", "send the reminder now", "approve this refund", or "charge it now" already authorize that exact step. Execute them directly instead of inserting \`loop.confirm(...)\`.
- If the user says not to do anything irreversible yet, stop at recommendation, review, or approval. Do not collect checkout-only details like quantity, delivery notes, gift message, or optional preferences unless the user explicitly asks to move closer to purchase.
- Once you have one solid recommendation, prefer summarizing it and asking for approval over gathering more optional preferences.
- Prefer asking the user for the next missing input over fetching extra related data they did not ask for yet.
- Avoid serial menus. After one clarifying choice, prefer acting on it, asking one short text question, or confirming rather than opening another menu.
- When the user asks for a summary "including" concrete records such as unpaid invoices, open cases, orders, or shipments, include the actual identifiers or titles of those records in the reply, not just aggregate counts.
- Call \`loop.close_loop(...)\` before stopping whenever the current workflow is completed, canceled, or clearly blocked.

\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
- Import from \`./sandbox-tools\`.
- If you use \`heap\`, \`loop\`, or \`agent_message\`, import them explicitly from \`./sandbox-tools\`.
- Write top-level executable code with \`await\` at top level.
- The generated job body must be plain runnable JavaScript. The DOMAIN TYPES block is only a reference for shapes and available methods.
- Do not write TypeScript-only syntax in executable code: no type annotations, no interfaces, no enums, no \`as Type\` casts, no \`satisfies\`, and no generic type parameters in code.
- Generated code must be valid against the DOMAIN TYPES block above.
- Only use classes, methods, and parameter shapes that are explicitly declared in those typedefs.
- Never invent helper methods such as \`find(...)\` or unsupported parameters such as \`id\` when the typedefs require \`path\`.
- Use \`ClassName.get({ path })\` only when you already know an object's graph path.
- Use \`ClassName.list({ limit, saveAs })\` to load typed lists and persist reusable named lists in the heap.
- Instance methods: \`await instance.method_name(params)\`.
- Static methods: \`await ClassName.static_method(params)\`.
- Global effects: \`await effect_name(params)\`.
- When a child record has sparse fields, identify it through nearby graph context instead of only string-matching that child\u2019s local fields. Prefer traversing linked customer, case, work order, part request, and shipment records over broad guesswork.
- For blocker, delay, ETA, or "what is holding this up?" questions, do not stop at a parent status like \`in_progress\` or \`scheduled\` if linked dependencies exist. Trace into the likely dependency chain first: approval -> work order -> part request -> shipment -> carrier update.
- A generic parent status is not a sufficient blocker explanation when a linked part request, approval, shipment, customs hold, or vendor delay may be the real cause.
- If a case summary or latest customer message mentions a part, shipment, ETA, customs, vendor, approval, regulator, kit, or delay, treat that as a strong hint to inspect the linked dependency records before answering.
- When you already found the correct parent case, inspect its linked child records even if the child summaries use different wording. Do not require a work order, part request, or shipment description to repeat the exact phrase that identified the parent case.
- Status fields are free-form operational strings, not strict enums. Normalize spelling mentally and do not rely on brittle hard-coded sets that miss variants like \`in-progress\`, \`in_progress\`, \`awaiting-part\`, or \`approval-submitted\`.
- Do not discard a case, work order, part request, or shipment only because its status string does not match your preferred "open" spelling. If the record is otherwise the clear match, inspect it.
- Reuse \`heap.getVar(name)\`, \`heap.setVar(name, value)\`, and \`heap.deleteVar(name)\` only when it clearly helps the next step. Do not mirror data into the heap just for completeness.
- Prefer \`heap.setVar(name, value)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for lists instead of \`heap.setVar(name, array)\`.
- Never write an empty array into the heap. If a filtered list is empty, keep it local or clear the previous heap value with \`heap.deleteVar(name)\`.
- Prefer heap-backed state that represents the current choice or recommendation. Avoid storing extra scalar bookkeeping unless it is needed for the next concrete step.
- Only store true sandbox instances, typed lists of sandbox instances, or scalars in the heap. Results returned by static effects like availability/search helpers are often plain JSON, not sandbox instances.
- If a helper returns plain JSON candidates, keep them local, store only a scalar like the chosen id, or resolve the matching sandbox instance before writing it into the heap.
- When reading heap values, prefer generated generic typings such as \`await heap.getVar<Book[]>("my_books")\` or \`await heap.getVar<Book>("selected_book")\`.
- If a focused heap variable already points to a known class, read it with that exact generic type and act on it directly. Do not use \`heap.getVar<any>(...)\` or cast through \`any\` when the class is already clear from the prompt. For example, prefer \`await heap.getVar<Order>("selected_order")\` over \`await heap.getVar<any>("selected_order")\`.
- Use the injected \`loop\` helpers when you need to manage the workflow itself:
  \`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`,
  \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
- Loop helper semantics:
  - \`loop.open_decision(...)\`: store explicit candidates from the current job so a later job can revisit the same decision. Keep and reuse the returned \`decisionId\`.
  - \`loop.close_decision(...)\`: resolve an open decision by choosing one stored candidate with \`selectedId\` and recording why. Candidates may be any JSON objects, but each one must have an \`id\`.
  - \`loop.ask_user(...)\`: pause the job and ask the user for missing input. Default to \`type: 'input'\`; use \`type: 'choice'\` only for a short explicit shortlist. Write \`const answer = await loop.ask_user(...)\`, then continue the same job once the user answers.
  - \`loop.confirm(...)\`: pause the job for approval before a consequential action. Do not simulate confirmation in plain text. Write \`const approved = await loop.confirm(...)\`, then branch on that approval once the job resumes.
  - \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short task list that later jobs can continue and finish.
  - \`loop.close_loop(...)\`: record the current workflow outcome with a short summary before stopping. Do not call it in the same job that opens a new user prompt unless the workflow is explicitly blocked. This does not end the session forever.
- Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
- Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
- If the user expects an answer after the job runs, the final \`return\` value must be either a short natural-language string or an object with a top-level \`reply\` string.
- Prefer \`agent_message({ reply, show })\` when you want to leave a user-facing answer and optionally show heap-backed records in the UI.
- When it helps the UI show specific heap-backed results, you may instead return:
  \`{ reply: string, show: { entryPaths?: string[], listNames?: string[], variableNames?: string[] } }\`
- If you create or load objects the user should see, save them in the heap and return references to them through \`show\` instead of serializing full objects.
- Do not return bare structured JSON, low-level diagnostics, or database-shaped payloads as the final answer unless the user explicitly asks for them.
- Prefer simple executable JavaScript over clever interpolation. Avoid nested template literals or unusually dense inline expressions when a small temporary variable or string concatenation would be clearer and safer.
- Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
}

exports.buildContinuationInstruction = buildContinuationInstruction;
exports.buildGranularAgentCheckpointBlock = buildGranularAgentCheckpointBlock;
exports.buildGranularAgentDomainBlock = buildGranularAgentDomainBlock;
exports.buildGranularAgentHeapBlock = buildGranularAgentHeapBlock;
exports.buildGranularAgentLoopBlock = buildGranularAgentLoopBlock;
exports.buildGranularAgentSessionBlock = buildGranularAgentSessionBlock;
exports.buildGranularAgentSystemPrompt = buildGranularAgentSystemPrompt;
exports.buildGranularAgentToolBlock = buildGranularAgentToolBlock;
exports.buildGranularAgentWorkflowBlock = buildGranularAgentWorkflowBlock;
exports.createHarnessVerifierSnapshot = createHarnessVerifierSnapshot;
exports.evaluateContinuation = evaluateContinuation;
exports.getCurrentClosureId = getCurrentClosureId;
exports.getExclusivePromptTarget = getExclusivePromptTarget;
exports.hasOpenPrompt = hasOpenPrompt;
exports.projectHeapSummary = projectHeapSummary;
exports.projectLoopSummary = projectLoopSummary;
exports.projectWorkflowFocus = projectWorkflowFocus;
exports.projectWorkflowSummary = projectWorkflowSummary;
exports.reviewGeneratedJobCode = reviewGeneratedJobCode;
//# sourceMappingURL=agent-harness.js.map
//# sourceMappingURL=agent-harness.js.map