'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 hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
  const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
  const returnsShowPayload = /return\s+\{[\s\S]*?\bshow\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 post text with agent_text_message(...). Do not end with bare structured JSON."
    });
  }
  if (returnsShowPayload) {
    issues.push({
      code: "return_show_not_for_ui",
      severity: "error",
      message: "Do not use the final return value to send UI record refs through `show`. Use agent_heap_objects(...) for heap-backed UI, then return plain text if you still want a final textual answer."
    });
  }
  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 normalizeActionSummaryForPrompt(line) {
  return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
}
function collectConversationReferents(liveDoc) {
  const conversation = asRecord(liveDoc?.conversation);
  const persistedReferents = asArray(conversation?.referents).map((value) => asRecord(value)).filter((value) => Boolean(value));
  if (persistedReferents.length > 0) {
    return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
  }
  const heap = asRecord(liveDoc?.heap);
  const entriesByPath = asRecord(heap?.entriesByPath) || {};
  const listsByName = asRecord(heap?.listsByName) || {};
  const variablesByName = asRecord(heap?.variablesByName) || {};
  const messages = asArray(conversation?.messages).map((value) => asRecord(value)).filter((value) => Boolean(value)).slice().sort((left, right) => (Number(right.ts) || 0) - (Number(left.ts) || 0));
  const referents = [];
  const seen = /* @__PURE__ */ new Set();
  const pushReferent = (referent) => {
    if (!referent?.kind || !referent.ref) return;
    const key = `${referent.kind}:${referent.ref}`;
    if (seen.has(key)) return;
    seen.add(key);
    referents.push(referent);
  };
  for (const message of messages) {
    if (message.role !== "assistant") continue;
    const show = asRecord(message.show);
    if (!show) continue;
    const ts = Number(message.ts) || 0;
    const messageId = typeof message.id === "string" ? message.id : void 0;
    const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
    for (const entryPath of uniqueStrings(asArray(show.entryPaths))) {
      const entry = asRecord(entriesByPath[entryPath]);
      pushReferent({
        id: `entry:${entryPath}`,
        kind: "entry",
        ref: entryPath,
        entryPath,
        className: typeof entry?.className === "string" ? entry.className : void 0,
        label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
        messageId,
        jobId,
        ts
      });
    }
    for (const listName of uniqueStrings(asArray(show.listNames))) {
      const list = asRecord(listsByName[listName]);
      pushReferent({
        id: `list:${listName}`,
        kind: "list",
        ref: listName,
        listName,
        className: typeof list?.className === "string" ? list.className : void 0,
        count: Array.isArray(list?.paths) ? list.paths.length : null,
        messageId,
        jobId,
        ts
      });
    }
    for (const variableName of uniqueStrings(
      asArray(show.variableNames)
    )) {
      const variable = asRecord(variablesByName[variableName]);
      const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
      const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
      const entry = entryPath ? asRecord(entriesByPath[entryPath]) : null;
      const list = listName ? asRecord(listsByName[listName]) : null;
      pushReferent({
        id: `variable:${variableName}`,
        kind: "variable",
        ref: variableName,
        variableName,
        variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
        entryPath,
        listName,
        className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
        label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
        count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
        scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
        messageId,
        jobId,
        ts
      });
    }
  }
  return referents;
}
function projectConversationReferentFocus(liveDoc) {
  const heap = asRecord(liveDoc?.heap);
  const listsByName = asRecord(heap?.listsByName) || {};
  const referents = collectConversationReferents(liveDoc);
  const entryPaths = [];
  const listNames = [];
  const variableNames = [];
  for (const referent of referents.slice(0, 8)) {
    if (referent.kind === "entry" && typeof referent.entryPath === "string") {
      entryPaths.push(referent.entryPath);
      continue;
    }
    if (referent.kind === "list" && typeof referent.listName === "string") {
      listNames.push(referent.listName);
      const list = asRecord(listsByName[referent.listName]);
      entryPaths.push(...asArray(list?.paths).slice(0, 4));
      continue;
    }
    if (referent.kind === "variable" && typeof referent.variableName === "string") {
      variableNames.push(referent.variableName);
      if (typeof referent.entryPath === "string") {
        entryPaths.push(referent.entryPath);
      }
      if (typeof referent.listName === "string") {
        listNames.push(referent.listName);
        const list = asRecord(listsByName[referent.listName]);
        entryPaths.push(...asArray(list?.paths).slice(0, 4));
      }
    }
  }
  return {
    entryPaths: uniqueStrings(entryPaths, 8),
    listNames: uniqueStrings(listNames, 4),
    variableNames: uniqueStrings(variableNames, 4)
  };
}
function projectConversationReferentSummary(liveDoc) {
  const referents = collectConversationReferents(liveDoc).slice(0, 8);
  if (referents.length === 0) {
    return "No recent referents recorded from prior assistant replies.";
  }
  const entryLines = [];
  const listLines = [];
  const variableLines = [];
  for (const referent of referents) {
    if (referent.kind === "entry" && referent.entryPath) {
      const label = referent.label || referent.entryPath;
      const classLabel = referent.className || "unknown";
      entryLines.push(`- ${label} <${referent.entryPath}> [${classLabel}]`);
      continue;
    }
    if (referent.kind === "list" && referent.listName) {
      const classLabel = referent.className || "unknown";
      const countLabel = typeof referent.count === "number" ? referent.count : "?";
      listLines.push(
        `- ${referent.listName}: list<${classLabel}> -> ${countLabel} item(s)`
      );
      continue;
    }
    if (referent.kind === "variable" && referent.variableName) {
      if (referent.variableKind === "entry" && referent.entryPath && referent.className) {
        const label = referent.label || referent.entryPath;
        variableLines.push(
          `- ${referent.variableName}: entry<${referent.className}> -> ${label} <${referent.entryPath}>`
        );
        continue;
      }
      if (referent.variableKind === "list" && referent.listName && referent.className) {
        const countLabel = typeof referent.count === "number" ? referent.count : "?";
        variableLines.push(
          `- ${referent.variableName}: list<${referent.className}> -> ${countLabel} item(s) via ${referent.listName}`
        );
        continue;
      }
      if (referent.variableKind === "scalar") {
        variableLines.push(
          `- ${referent.variableName}: scalar = ${formatScalar(referent.scalarValue)}`
        );
        continue;
      }
      variableLines.push(`- ${referent.variableName}`);
    }
  }
  const lines = [];
  lines.push("Entries:");
  lines.push(...entryLines.length > 0 ? entryLines : ["- none"]);
  lines.push("", "Lists:");
  lines.push(...listLines.length > 0 ? listLines : ["- none"]);
  lines.push("", "Variables:");
  lines.push(...variableLines.length > 0 ? variableLines : ["- none"]);
  return lines.join("\n");
}
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).map(
      normalizeActionSummaryForPrompt
    )
  };
}
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, resolve it from the graph before saying it is missing: try a broad search, then a small set of normalized/fuzzy variants or a paged scan when the domain supports it.",
    "If the request needs all matching records, use iterate(...) or page until hasMore is false. A single list(...) or page(...) call is only one page.",
    "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.",
    "When progress depends on the user's choice, missing detail, or approval, use loop.ask_user(...) or loop.confirm(...) so the job pauses and resumes through the live workflow.",
    "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 reference 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],
    ["userName", sessionContext.userName]
  ];
  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 buildGranularAgentReferentBlock(referentSummary) {
  return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
}
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 REFERENCE below for exact signatures and query examples."
  ];
  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)) {
      const normalizedLine = normalizeActionSummaryForPrompt(line);
      lines.push(
        normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
      );
    }
  }
  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 referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
  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 code against the live session.
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(...)\`.
- If the next step is an interactive clarification that should be resumable in the live workflow, you must call \`execute_code\`. A missing preference, rule, metric, target, or option selection is not a plain-text reply when the answer should drive the next live step.
- If you can offer a short grounded shortlist, that clarification should usually be \`loop.ask_user({ type: 'choice', ... })\` instead of a plain-text question with bullet options.
- 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.
- If you are asking the user to pick from explicit options, prefer a live \`loop.ask_user({ type: 'choice', ... })\` prompt over a direct reply that lists those options in text.
- 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 REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
Import classes and effect functions from \`./sandbox-tools\` in generated code.
Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.

${domainBlock}

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

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

\u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
${referentBlock}

\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
- Continue from the latest structured state. Treat WORKFLOW SNAPSHOT, EXECUTION CHECKPOINT, RECENT REFERENTS, SESSION HEAP, and AGENT LOOP STATE as the working memory for this request.
- Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
- Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
- Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
- Treat user-provided names, numbers, and labels as human references, not exact keys. Resolve them with code: check recent referents/heap first, then query the graph with the broadest supported \`search\` or \`filter\`, then retry with a few normalized/fuzzy/prefix variants when the first pass is empty or ambiguous. Only say a record does not exist after a reasonable lookup across the relevant class.
- If one strong match exists, use it. If several plausible matches remain, use \`loop.ask_user({ type: 'choice', ... })\` with the grounded candidates instead of guessing.
- If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
- For comparisons, rankings, selections, or summaries, first identify the rule you are using. If that rule is not clear from the user request and DOMAIN REFERENCE, ask the user before choosing anything.
- When the ranking, comparison, or selection rule is unclear, the minimum next step is the clarification itself. Do not run a placeholder query for a provisional winner before asking.
- If a user request matches both a domain type/effect and a loop helper, prioritize the domain type/effect. For example, if DOMAIN REFERENCE contains a \`Task\` class and the user asks to create a task, create the domain task record; do not call \`loop.create_task(...)\` unless you are only tracking your own workflow.
- Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
- If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
- Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
- For an unclear ranking, comparison, or selection rule, prefer \`type: 'choice'\` when you can offer a short grounded list of plausible interpretations from the domain or nearby context.
- When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
- Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
- Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
- Use \`loop.open_decision(...)\` to persist grounded candidates, \`loop.close_decision(...)\` to resolve one, and \`loop.close_loop(...)\` when the workflow is completed, canceled, or blocked.
- If you ask a new question in the current job, do not also close the loop in that same job.

\u2500\u2500\u2500 LOOP HELPER REFERENCE \u2500\u2500\u2500
- \`loop.ask_user(...)\`: pause the current job for missing input; use \`type: 'choice'\` only for a short grounded shortlist.
- \`loop.confirm(...)\`: pause for yes/no approval before a consequential action, then branch on the returned boolean.
- \`loop.open_decision(...)\`: save explicit candidates that later jobs can revisit; each candidate needs an \`id\`.
- \`loop.close_decision(...)\`: resolve an open decision with a stored \`selectedId\` and optional rationale.
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short resumable task list for the agent's workflow; these are not domain \`Task\` records.
- \`loop.close_loop(...)\`: record the workflow outcome when it is completed, canceled, or blocked.

\u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
- Import from \`./sandbox-tools\`.
- If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, 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. Do not use TypeScript-only syntax.
- Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
- Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
- Use \`ClassName.count()\` for totals, \`ClassName.page({ page, perPage, saveAs })\` when you need \`items\` plus \`totalCount\` or \`hasMore\`, \`ClassName.list({ page, perPage, saveAs })\` for one page of records, and \`ClassName.iterate({ perPage, maxItems })\` for large scans.
- \`perPage\` defaults to \`100\` and is capped at \`100\`.
- A single \`list(...)\` or \`page(...)\` call never proves there are no more records. For "all", "every", exports, broad scans, or exhaustive searches, use \`iterate(...)\` when available or loop \`page(...)\` until \`hasMore\` is false.
- Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
- A property appearing on a record does not make it valid in \`filter\` or \`sort\`; only use fields and operators that are explicitly exposed in DOMAIN REFERENCE.
- Choose \`sort.field\` verbatim from the sortable fields listed in DOMAIN REFERENCE. Do not sort by relationship names, related-record collections, counts, totals, or other derived metrics unless they are explicitly listed as sortable.
- If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
- Do not invent proxy metrics, fallback heuristics, or made-up tie-breakers to resolve ambiguity. If the rule is unclear, ask the user with \`loop.ask_user(...)\`.
- Do not fetch, sort, or show a provisional record just to have something to display while the real ranking or selection rule is still ambiguous.
- Call instance methods on instances, static methods on classes, and global effects by name.
- Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
- Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
- Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
- Only store sandbox instances, typed lists, or scalars in the heap. If a helper returns plain JSON, keep it local or store only the chosen scalar.
- Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
- Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
- \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
- After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
- Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
- Use \`agent_text_message(...)\` for user-visible text.
- Use \`agent_heap_objects(...)\` for user-visible records. You may pass sandbox instances directly, or heap-backed \`entryPaths\`, \`listNames\`, and \`variableNames\` when you already have them. Use \`saveAs\` or \`heap.setVar(...)\` when you need a reusable named selection.
- Do not rely on the final return value for UI output. Do not return ad-hoc \`reply\` / \`show\` payloads instead of explicit agent message calls.
- Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
- 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.buildGranularAgentReferentBlock = buildGranularAgentReferentBlock;
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.projectConversationReferentFocus = projectConversationReferentFocus;
exports.projectConversationReferentSummary = projectConversationReferentSummary;
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