markform 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +27 -2
  2. package/dist/ai-sdk.d.mts +1 -2
  3. package/dist/ai-sdk.mjs +2 -2
  4. package/dist/ai-sdk.mjs.map +1 -1
  5. package/dist/{apply-DgDJBscb.mjs → apply-Dalpt-D6.mjs} +422 -31
  6. package/dist/apply-Dalpt-D6.mjs.map +1 -0
  7. package/dist/bin.mjs +20 -2
  8. package/dist/bin.mjs.map +1 -1
  9. package/dist/{cli-DAl8LQzI.mjs → cli-tpvFNqFY.mjs} +325 -73
  10. package/dist/cli-tpvFNqFY.mjs.map +1 -0
  11. package/dist/cli.mjs +1 -1
  12. package/dist/{coreTypes-DiCddBKu.mjs → coreTypes-CPKXf2dc.mjs} +9 -4
  13. package/dist/coreTypes-CPKXf2dc.mjs.map +1 -0
  14. package/dist/{coreTypes-CnEea7Kh.d.mts → coreTypes-CkxML8g2.d.mts} +128 -10
  15. package/dist/index.d.mts +642 -22
  16. package/dist/index.mjs +5 -5
  17. package/dist/{session-XDrocA3j.mjs → session-CK0x28RO.mjs} +2 -2
  18. package/dist/session-CK0x28RO.mjs.map +1 -0
  19. package/dist/{session-B7aR6hno.mjs → session-ZHBi3LVQ.mjs} +1 -1
  20. package/dist/{shared-fUKfJ1UA.mjs → shared-BTR35aMz.mjs} +1 -1
  21. package/dist/{shared-CCq4haEV.mjs → shared-DwdyWmvE.mjs} +1 -3
  22. package/dist/shared-DwdyWmvE.mjs.map +1 -0
  23. package/dist/{src-CHVJLGKt.mjs → src-BTyz-wS6.mjs} +2009 -584
  24. package/dist/src-BTyz-wS6.mjs.map +1 -0
  25. package/docs/markform-apis.md +112 -0
  26. package/docs/markform-reference.md +27 -0
  27. package/docs/markform-spec.md +115 -0
  28. package/examples/movie-research/movie-deep-research-mock-filled.form.md +1 -1
  29. package/examples/movie-research/movie-deep-research.form.md +1 -1
  30. package/examples/parallel/parallel-research.form.md +57 -0
  31. package/examples/startup-deep-research/startup-deep-research.form.md +1 -1
  32. package/package.json +16 -14
  33. package/dist/apply-DgDJBscb.mjs.map +0 -1
  34. package/dist/cli-DAl8LQzI.mjs.map +0 -1
  35. package/dist/coreTypes-DiCddBKu.mjs.map +0 -1
  36. package/dist/session-XDrocA3j.mjs.map +0 -1
  37. package/dist/shared-CCq4haEV.mjs.map +0 -1
  38. package/dist/src-CHVJLGKt.mjs.map +0 -1
@@ -2,7 +2,7 @@
2
2
  import YAML from "yaml";
3
3
 
4
4
  //#region src/errors.ts
5
- const VERSION = "0.1.17";
5
+ const VERSION = "0.1.19";
6
6
  /**
7
7
  * Base error class for all markform errors.
8
8
  * Consumers can catch this to handle any markform error.
@@ -373,6 +373,11 @@ const DEFAULT_MAX_PATCHES_PER_TURN = 20;
373
373
  */
374
374
  const DEFAULT_MAX_ISSUES_PER_TURN = 10;
375
375
  /**
376
+ * Default maximum concurrent agents for parallel batches.
377
+ * When parallel batches are executed, this limits how many agents run simultaneously.
378
+ */
379
+ const DEFAULT_MAX_PARALLEL_AGENTS = 4;
380
+ /**
376
381
  * Default maximum AI SDK steps (tool call rounds) per harness turn.
377
382
  * Matches AI SDK's ToolLoopAgent default of 20.
378
383
  * @see https://ai-sdk.dev/docs/agents/loop-control
@@ -467,6 +472,327 @@ function deriveSchemaPath(basePath) {
467
472
  return base + SCHEMA_EXTENSION;
468
473
  }
469
474
 
475
+ //#endregion
476
+ //#region src/engine/parseHelpers.ts
477
+ /** Map checkbox marker to state value */
478
+ const CHECKBOX_MARKERS = {
479
+ "[ ]": "todo",
480
+ "[x]": "done",
481
+ "[X]": "done",
482
+ "[/]": "incomplete",
483
+ "[*]": "active",
484
+ "[-]": "na",
485
+ "[y]": "yes",
486
+ "[Y]": "yes",
487
+ "[n]": "no",
488
+ "[N]": "no"
489
+ };
490
+ const OPTION_TEXT_PATTERN = /^(\[[^\]]\])\s*(.*?)\s*$/;
491
+ /**
492
+ * Parse option text to extract marker and label.
493
+ * Text is like "[ ] Label" or "[x] Label".
494
+ */
495
+ function parseOptionText(text) {
496
+ const match = OPTION_TEXT_PATTERN.exec(text);
497
+ if (!match) return null;
498
+ return {
499
+ marker: match[1] ?? "",
500
+ label: (match[2] ?? "").trim()
501
+ };
502
+ }
503
+ /**
504
+ * Check if a node is a tag node with specific name.
505
+ * Works with raw AST nodes (not transformed Tags).
506
+ */
507
+ function isTagNode(node, name) {
508
+ if (typeof node !== "object" || node === null) return false;
509
+ if (node.type === "tag" && node.tag) return name === void 0 || node.tag === name;
510
+ return false;
511
+ }
512
+ /**
513
+ * Get string attribute value or undefined.
514
+ */
515
+ function getStringAttr(node, name) {
516
+ const value = node.attributes?.[name];
517
+ return typeof value === "string" ? value : void 0;
518
+ }
519
+ /**
520
+ * Get number attribute value or undefined.
521
+ */
522
+ function getNumberAttr(node, name) {
523
+ const value = node.attributes?.[name];
524
+ return typeof value === "number" ? value : void 0;
525
+ }
526
+ /**
527
+ * Get boolean attribute value or undefined.
528
+ */
529
+ function getBooleanAttr(node, name) {
530
+ const value = node.attributes?.[name];
531
+ return typeof value === "boolean" ? value : void 0;
532
+ }
533
+ /**
534
+ * Get validator references from validate attribute.
535
+ * Handles both single string and array formats.
536
+ */
537
+ function getValidateAttr(node) {
538
+ const value = node.attributes?.validate;
539
+ if (value === void 0 || value === null) return;
540
+ if (Array.isArray(value)) return value;
541
+ if (typeof value === "string") return [value];
542
+ if (typeof value === "object") return [value];
543
+ }
544
+ /**
545
+ * Get string array attribute value or undefined.
546
+ * Handles both single string (converts to array) and array formats.
547
+ */
548
+ function getStringArrayAttr(node, name) {
549
+ const value = node.attributes?.[name];
550
+ if (value === void 0 || value === null) return;
551
+ if (Array.isArray(value)) {
552
+ const strings = value.filter((v) => typeof v === "string");
553
+ return strings.length > 0 ? strings : void 0;
554
+ }
555
+ if (typeof value === "string") return [value];
556
+ }
557
+ /**
558
+ * Extract option items from node children (for option lists).
559
+ * Works with raw AST nodes. Collects text and ID from list items.
560
+ */
561
+ function extractOptionItems(node) {
562
+ const items = [];
563
+ /**
564
+ * Collect all text content from a node tree into a single string.
565
+ */
566
+ function collectText(n) {
567
+ let text = "";
568
+ if (n.type === "text" && typeof n.attributes?.content === "string") text += n.attributes.content;
569
+ if (n.type === "softbreak") text += "\n";
570
+ if (n.children && Array.isArray(n.children)) for (const c of n.children) text += collectText(c);
571
+ return text;
572
+ }
573
+ /**
574
+ * Traverse to find list items and extract their content.
575
+ */
576
+ function traverse(child) {
577
+ if (!child || typeof child !== "object") return;
578
+ if (child.type === "item") {
579
+ const text = collectText(child);
580
+ let id = null;
581
+ if (typeof child.attributes?.id === "string") id = child.attributes.id;
582
+ else if (child.children && Array.isArray(child.children) && child.children.length > 0 && typeof child.children[0]?.attributes?.id === "string") id = child.children[0].attributes.id;
583
+ if (text.trim()) items.push({
584
+ id,
585
+ text: text.trim()
586
+ });
587
+ return;
588
+ }
589
+ if (child.children && Array.isArray(child.children)) for (const c of child.children) traverse(c);
590
+ }
591
+ if (node.children && Array.isArray(node.children)) for (const child of node.children) traverse(child);
592
+ return items;
593
+ }
594
+ /**
595
+ * Extract fence value from node children.
596
+ * Looks for ```value code blocks.
597
+ */
598
+ function extractFenceValue(node) {
599
+ function traverse(child) {
600
+ if (!child || typeof child !== "object") return null;
601
+ if (child.type === "fence") {
602
+ if (child.attributes?.language === "value") return typeof child.attributes?.content === "string" ? child.attributes.content : null;
603
+ }
604
+ if (child.children && Array.isArray(child.children)) for (const c of child.children) {
605
+ const result = traverse(c);
606
+ if (result !== null) return result;
607
+ }
608
+ return null;
609
+ }
610
+ if (node.children && Array.isArray(node.children)) for (const child of node.children) {
611
+ const result = traverse(child);
612
+ if (result !== null) return result;
613
+ }
614
+ return null;
615
+ }
616
+ /**
617
+ * Extract table content from node children.
618
+ * Handles both raw text and Markdoc-parsed table nodes.
619
+ * Reconstructs markdown table format from the AST.
620
+ */
621
+ function extractTableContent(node) {
622
+ const lines = [];
623
+ function extractTextFromNode(n) {
624
+ if (!n || typeof n !== "object") return "";
625
+ if (n.type === "text" && typeof n.attributes?.content === "string") return n.attributes.content;
626
+ if (n.children && Array.isArray(n.children)) return n.children.map(extractTextFromNode).join("");
627
+ return "";
628
+ }
629
+ function extractTableRow(trNode) {
630
+ if (!trNode.children || !Array.isArray(trNode.children)) return "";
631
+ return `| ${trNode.children.filter((c) => c.type === "th" || c.type === "td").map((c) => extractTextFromNode(c).trim()).join(" | ")} |`;
632
+ }
633
+ function processNode(child) {
634
+ if (!child || typeof child !== "object") return;
635
+ if (child.type === "paragraph" || child.type === "inline") {
636
+ const text = extractTextFromNode(child).trim();
637
+ if (text) lines.push(text);
638
+ return;
639
+ }
640
+ if (child.type === "text" && typeof child.attributes?.content === "string") {
641
+ const text = child.attributes.content.trim();
642
+ if (text) lines.push(text);
643
+ return;
644
+ }
645
+ if (child.type === "table") {
646
+ const thead = child.children?.find((c) => c.type === "thead");
647
+ if (thead?.children) for (const tr of thead.children.filter((c) => c.type === "tr")) lines.push(extractTableRow(tr));
648
+ if (thead?.children?.length) {
649
+ const firstTr = thead.children.find((c) => c.type === "tr");
650
+ if (firstTr?.children) {
651
+ const colCount = firstTr.children.filter((c) => c.type === "th" || c.type === "td").length;
652
+ const separatorCells = Array(colCount).fill("----");
653
+ lines.push(`| ${separatorCells.join(" | ")} |`);
654
+ }
655
+ }
656
+ const tbody = child.children?.find((c) => c.type === "tbody");
657
+ if (tbody?.children) for (const tr of tbody.children.filter((c) => c.type === "tr")) lines.push(extractTableRow(tr));
658
+ return;
659
+ }
660
+ if (child.children && Array.isArray(child.children)) for (const c of child.children) processNode(c);
661
+ }
662
+ if (node.children && Array.isArray(node.children)) for (const child of node.children) processNode(child);
663
+ return lines.join("\n").trim() || null;
664
+ }
665
+
666
+ //#endregion
667
+ //#region src/engine/parseSentinels.ts
668
+ /** Sentinel values for text fields */
669
+ const SENTINEL_SKIP = "%SKIP%";
670
+ const SENTINEL_ABORT = "%ABORT%";
671
+ /**
672
+ * Detect if a value contains a sentinel pattern (%SKIP% or %ABORT%).
673
+ *
674
+ * This is the shared low-level detection function used for:
675
+ * - Form parsing validation (strict format)
676
+ * - Patch value validation (reject embedded sentinels)
677
+ * - Table cell parsing (convert to skipped/aborted state)
678
+ *
679
+ * Supports multiple formats that LLMs might generate:
680
+ * - `%SKIP%` or `%skip%` (case-insensitive)
681
+ * - `%SKIP% (reason)` - canonical format
682
+ * - `%SKIP:reason%` or `%SKIP(reason)%` - compact formats
683
+ *
684
+ * @param value - The value to check (returns null for non-strings)
685
+ * @returns Detected sentinel type and optional reason, or null if no sentinel
686
+ */
687
+ function detectSentinel(value) {
688
+ if (value == null || typeof value !== "string") return null;
689
+ const trimmed = value.trim();
690
+ const compactSkipMatch = /^%SKIP(?:[:(](.*?))?[)]?%$/i.exec(trimmed);
691
+ if (compactSkipMatch) {
692
+ const reason = compactSkipMatch[1]?.trim();
693
+ return {
694
+ type: "skip",
695
+ ...reason && { reason }
696
+ };
697
+ }
698
+ const compactAbortMatch = /^%ABORT(?:[:(](.*?))?[)]?%$/i.exec(trimmed);
699
+ if (compactAbortMatch) {
700
+ const reason = compactAbortMatch[1]?.trim();
701
+ return {
702
+ type: "abort",
703
+ ...reason && { reason }
704
+ };
705
+ }
706
+ const upper = trimmed.toUpperCase();
707
+ if (upper.startsWith("%SKIP%")) {
708
+ const rest = trimmed.slice(6).trim();
709
+ if (rest === "") return { type: "skip" };
710
+ const reasonMatch = /^\((.+)\)$/s.exec(rest);
711
+ if (reasonMatch?.[1]) return {
712
+ type: "skip",
713
+ reason: reasonMatch[1].trim()
714
+ };
715
+ return { type: "skip" };
716
+ }
717
+ if (upper.startsWith("%ABORT%")) {
718
+ const rest = trimmed.slice(7).trim();
719
+ if (rest === "") return { type: "abort" };
720
+ const reasonMatch = /^\((.+)\)$/s.exec(rest);
721
+ if (reasonMatch?.[1]) return {
722
+ type: "abort",
723
+ reason: reasonMatch[1].trim()
724
+ };
725
+ return { type: "abort" };
726
+ }
727
+ return null;
728
+ }
729
+ /**
730
+ * Parse a sentinel value with optional parenthesized reason (strict format).
731
+ *
732
+ * This is the strict parser used during form parsing, where we want to
733
+ * validate the exact format. For patch validation, use detectSentinel().
734
+ *
735
+ * Formats: `%SKIP%`, `%SKIP% (reason text)`, `%ABORT%`, `%ABORT% (reason text)`
736
+ * Returns null if the content is not a valid sentinel format.
737
+ */
738
+ function parseSentinel(content) {
739
+ if (!content) return null;
740
+ const trimmed = content.trim();
741
+ const reasonPattern = /^\((.+)\)$/s;
742
+ if (trimmed.startsWith(SENTINEL_SKIP)) {
743
+ const rest = trimmed.slice(6).trim();
744
+ if (rest === "") return { type: "skip" };
745
+ const match = reasonPattern.exec(rest);
746
+ if (match?.[1]) return {
747
+ type: "skip",
748
+ reason: match[1].trim()
749
+ };
750
+ return null;
751
+ }
752
+ if (trimmed.startsWith(SENTINEL_ABORT)) {
753
+ const rest = trimmed.slice(7).trim();
754
+ if (rest === "") return { type: "abort" };
755
+ const match = reasonPattern.exec(rest);
756
+ if (match?.[1]) return {
757
+ type: "abort",
758
+ reason: match[1].trim()
759
+ };
760
+ return null;
761
+ }
762
+ return null;
763
+ }
764
+ /**
765
+ * Check for sentinel values in fence content and validate against state attribute.
766
+ * Handles the common pattern of checking for %SKIP% and %ABORT% sentinels in field values.
767
+ *
768
+ * @param node - The field node to check
769
+ * @param fieldId - The field ID for error messages
770
+ * @param required - Whether the field is required (skip not allowed on required fields)
771
+ * @returns A FieldResponse if a sentinel is found, null otherwise
772
+ */
773
+ function tryParseSentinelResponse(node, fieldId, required) {
774
+ const fenceContent = extractFenceValue(node);
775
+ const stateAttr = getStringAttr(node, "state");
776
+ const sentinel = parseSentinel(fenceContent);
777
+ if (!sentinel) return null;
778
+ if (sentinel.type === "skip") {
779
+ if (stateAttr !== void 0 && stateAttr !== "skipped") throw new MarkformParseError(`Field '${fieldId}' has conflicting state='${stateAttr}' with %SKIP% sentinel`);
780
+ if (required) throw new MarkformParseError(`Field '${fieldId}' is required but has %SKIP% sentinel. Cannot skip required fields.`);
781
+ return {
782
+ state: "skipped",
783
+ ...sentinel.reason && { reason: sentinel.reason }
784
+ };
785
+ }
786
+ if (sentinel.type === "abort") {
787
+ if (stateAttr !== void 0 && stateAttr !== "aborted") throw new MarkformParseError(`Field '${fieldId}' has conflicting state='${stateAttr}' with %ABORT% sentinel`);
788
+ return {
789
+ state: "aborted",
790
+ ...sentinel.reason && { reason: sentinel.reason }
791
+ };
792
+ }
793
+ return null;
794
+ }
795
+
470
796
  //#endregion
471
797
  //#region src/engine/preprocess.ts
472
798
  const MARKFORM_TAGS = new Set([
@@ -503,10 +829,10 @@ function isValidFormTag(content) {
503
829
  return trimmed.includes("=") && /\bid\s*=/.test(trimmed);
504
830
  }
505
831
  /** Parser state for tracking code blocks */
506
- var State = /* @__PURE__ */ function(State$1) {
507
- State$1[State$1["NORMAL"] = 0] = "NORMAL";
508
- State$1[State$1["FENCED_CODE"] = 1] = "FENCED_CODE";
509
- return State$1;
832
+ var State = /* @__PURE__ */ function(State) {
833
+ State[State["NORMAL"] = 0] = "NORMAL";
834
+ State[State["FENCED_CODE"] = 1] = "FENCED_CODE";
835
+ return State;
510
836
  }(State || {});
511
837
  /**
512
838
  * Check if position is at the start of a line (or at position 0).
@@ -987,6 +1313,33 @@ function friendlyUrlAbbrev(url, maxPathChars = 12) {
987
1313
  function formatUrlAsMarkdownLink(url) {
988
1314
  return `[${friendlyUrlAbbrev(url)}](${url})`;
989
1315
  }
1316
+ /**
1317
+ * Format bare URLs in text as HTML links with abbreviated display text.
1318
+ * Also handles markdown-style links [text](url) for consistency.
1319
+ *
1320
+ * Processing order:
1321
+ * 1. Escape all HTML to prevent XSS
1322
+ * 2. Convert markdown links [text](url) to <a> tags
1323
+ * 3. Convert bare URLs (not already in links) to <a> tags with abbreviated display
1324
+ *
1325
+ * @param text - The raw text containing URLs (will be HTML-escaped)
1326
+ * @param escapeHtml - Function to escape HTML entities
1327
+ * @returns HTML-safe text with URLs converted to <a> tags
1328
+ */
1329
+ function formatBareUrlsAsHtmlLinks(text, escapeHtml) {
1330
+ let result = escapeHtml(text);
1331
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, linkText, url) => {
1332
+ const cleanUrl = url.replace(/&amp;/g, "&");
1333
+ return `<a href="${escapeHtml(cleanUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(cleanUrl)}">${linkText}</a>`;
1334
+ });
1335
+ result = result.replace(/(?<!href="|data-url="|">)(?:https?:\/\/|www\.)[^\s<>"]+(?<![.,;:!?'")])/g, (url) => {
1336
+ const cleanUrl = url.replace(/&amp;/g, "&");
1337
+ const fullUrl = cleanUrl.startsWith("www.") ? `https://${cleanUrl}` : cleanUrl;
1338
+ const display = friendlyUrlAbbrev(fullUrl);
1339
+ return `<a href="${escapeHtml(fullUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(fullUrl)}">${escapeHtml(display)}</a>`;
1340
+ });
1341
+ return result;
1342
+ }
990
1343
 
991
1344
  //#endregion
992
1345
  //#region src/engine/serialize.ts
@@ -1236,6 +1589,13 @@ function getMarker(state) {
1236
1589
  return STATE_TO_MARKER[state] ?? " ";
1237
1590
  }
1238
1591
  /**
1592
+ * Add parallel and order attributes to a field's attrs object if defined.
1593
+ */
1594
+ function addParallelOrderAttrs(attrs, field) {
1595
+ if (field.parallel !== void 0) attrs.parallel = field.parallel;
1596
+ if (field.order !== void 0) attrs.order = field.order;
1597
+ }
1598
+ /**
1239
1599
  * Serialize a string field.
1240
1600
  */
1241
1601
  function serializeStringField(field, response) {
@@ -1253,6 +1613,7 @@ function serializeStringField(field, response) {
1253
1613
  if (field.maxLength !== void 0) attrs.maxLength = field.maxLength;
1254
1614
  if (field.validate) attrs.validate = field.validate;
1255
1615
  if (field.report !== void 0) attrs.report = field.report;
1616
+ addParallelOrderAttrs(attrs, field);
1256
1617
  if (field.placeholder) attrs.placeholder = field.placeholder;
1257
1618
  if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
1258
1619
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
@@ -1283,6 +1644,7 @@ function serializeNumberField(field, response) {
1283
1644
  if (field.integer) attrs.integer = field.integer;
1284
1645
  if (field.validate) attrs.validate = field.validate;
1285
1646
  if (field.report !== void 0) attrs.report = field.report;
1647
+ addParallelOrderAttrs(attrs, field);
1286
1648
  if (field.placeholder) attrs.placeholder = field.placeholder;
1287
1649
  if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
1288
1650
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
@@ -1315,6 +1677,7 @@ function serializeStringListField(field, response) {
1315
1677
  if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
1316
1678
  if (field.validate) attrs.validate = field.validate;
1317
1679
  if (field.report !== void 0) attrs.report = field.report;
1680
+ addParallelOrderAttrs(attrs, field);
1318
1681
  if (field.placeholder) attrs.placeholder = field.placeholder;
1319
1682
  if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
1320
1683
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
@@ -1353,6 +1716,7 @@ function serializeSingleSelectField(field, response) {
1353
1716
  if (field.role !== AGENT_ROLE) attrs.role = field.role;
1354
1717
  if (field.validate) attrs.validate = field.validate;
1355
1718
  if (field.report !== void 0) attrs.report = field.report;
1719
+ addParallelOrderAttrs(attrs, field);
1356
1720
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
1357
1721
  const attrStr = serializeAttrs(attrs);
1358
1722
  let value;
@@ -1377,6 +1741,7 @@ function serializeMultiSelectField(field, response) {
1377
1741
  if (field.maxSelections !== void 0) attrs.maxSelections = field.maxSelections;
1378
1742
  if (field.validate) attrs.validate = field.validate;
1379
1743
  if (field.report !== void 0) attrs.report = field.report;
1744
+ addParallelOrderAttrs(attrs, field);
1380
1745
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
1381
1746
  const attrStr = serializeAttrs(attrs);
1382
1747
  let value;
@@ -1403,6 +1768,7 @@ function serializeCheckboxesField(field, response) {
1403
1768
  if (field.approvalMode !== "none") attrs.approvalMode = field.approvalMode;
1404
1769
  if (field.validate) attrs.validate = field.validate;
1405
1770
  if (field.report !== void 0) attrs.report = field.report;
1771
+ addParallelOrderAttrs(attrs, field);
1406
1772
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
1407
1773
  const attrStr = serializeAttrs(attrs);
1408
1774
  let value;
@@ -1423,6 +1789,7 @@ function serializeUrlField(field, response) {
1423
1789
  if (field.role !== AGENT_ROLE) attrs.role = field.role;
1424
1790
  if (field.validate) attrs.validate = field.validate;
1425
1791
  if (field.report !== void 0) attrs.report = field.report;
1792
+ addParallelOrderAttrs(attrs, field);
1426
1793
  if (field.placeholder) attrs.placeholder = field.placeholder;
1427
1794
  if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
1428
1795
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
@@ -1453,6 +1820,7 @@ function serializeUrlListField(field, response) {
1453
1820
  if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
1454
1821
  if (field.validate) attrs.validate = field.validate;
1455
1822
  if (field.report !== void 0) attrs.report = field.report;
1823
+ addParallelOrderAttrs(attrs, field);
1456
1824
  if (field.placeholder) attrs.placeholder = field.placeholder;
1457
1825
  if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
1458
1826
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
@@ -1482,6 +1850,7 @@ function serializeDateField(field, response) {
1482
1850
  if (field.max !== void 0) attrs.max = field.max;
1483
1851
  if (field.validate) attrs.validate = field.validate;
1484
1852
  if (field.report !== void 0) attrs.report = field.report;
1853
+ addParallelOrderAttrs(attrs, field);
1485
1854
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
1486
1855
  const attrStr = serializeAttrs(attrs);
1487
1856
  let content = "";
@@ -1509,6 +1878,7 @@ function serializeYearField(field, response) {
1509
1878
  if (field.max !== void 0) attrs.max = field.max;
1510
1879
  if (field.validate) attrs.validate = field.validate;
1511
1880
  if (field.report !== void 0) attrs.report = field.report;
1881
+ addParallelOrderAttrs(attrs, field);
1512
1882
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
1513
1883
  const attrStr = serializeAttrs(attrs);
1514
1884
  let content = "";
@@ -1578,6 +1948,7 @@ function serializeTableField(field, response) {
1578
1948
  if (field.maxRows !== void 0) attrs.maxRows = field.maxRows;
1579
1949
  if (field.validate) attrs.validate = field.validate;
1580
1950
  if (field.report !== void 0) attrs.report = field.report;
1951
+ addParallelOrderAttrs(attrs, field);
1581
1952
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
1582
1953
  const attrStr = serializeAttrs(attrs);
1583
1954
  let content = "";
@@ -1654,6 +2025,8 @@ function serializeFieldGroup(group, responses, docs) {
1654
2025
  if (group.title) attrs.title = group.title;
1655
2026
  if (group.validate) attrs.validate = group.validate;
1656
2027
  if (group.report !== void 0) attrs.report = group.report;
2028
+ if (group.parallel !== void 0) attrs.parallel = group.parallel;
2029
+ if (group.order !== void 0) attrs.order = group.order;
1657
2030
  const attrStr = serializeAttrs(attrs);
1658
2031
  lines.push(`{% group ${attrStr} %}`);
1659
2032
  }
@@ -1717,6 +2090,7 @@ function buildHarnessConfig(config) {
1717
2090
  if (config.maxTurns !== void 0) result.max_turns = config.maxTurns;
1718
2091
  if (config.maxPatchesPerTurn !== void 0) result.max_patches_per_turn = config.maxPatchesPerTurn;
1719
2092
  if (config.maxIssuesPerTurn !== void 0) result.max_issues_per_turn = config.maxIssuesPerTurn;
2093
+ if (config.maxParallelAgents !== void 0) result.max_parallel_agents = config.maxParallelAgents;
1720
2094
  return result;
1721
2095
  }
1722
2096
  /**
@@ -3523,6 +3897,16 @@ function typeMismatchError(index, op, field) {
3523
3897
  };
3524
3898
  }
3525
3899
  /**
3900
+ * Create an error for embedded sentinel in patch value.
3901
+ */
3902
+ function embeddedSentinelError(index, fieldId, sentinelType) {
3903
+ return {
3904
+ patchIndex: index,
3905
+ message: `Value contains ${sentinelType === "skip" ? "%SKIP%" : "%ABORT%"} sentinel for field "${fieldId}". Use ${sentinelType === "skip" ? "skip_field" : "abort_field"} operation instead of embedding sentinel in value.`,
3906
+ fieldId
3907
+ };
3908
+ }
3909
+ /**
3526
3910
  * Validate a single patch against the form schema.
3527
3911
  */
3528
3912
  function validatePatch(form, patch, index) {
@@ -3547,6 +3931,10 @@ function validatePatch(form, patch, index) {
3547
3931
  };
3548
3932
  const expectedKind = PATCH_OP_TO_FIELD_KIND[patch.op];
3549
3933
  if (expectedKind && field.kind !== expectedKind) return typeMismatchError(index, patch.op, field);
3934
+ if (patch.op === "set_string" || patch.op === "set_url" || patch.op === "set_date") {
3935
+ const sentinel = detectSentinel(patch.value);
3936
+ if (sentinel) return embeddedSentinelError(index, field.id, sentinel.type);
3937
+ }
3550
3938
  if (patch.op === "set_string_list" && field.kind === "string_list") {
3551
3939
  if (!Array.isArray(patch.value)) return {
3552
3940
  patchIndex: index,
@@ -3554,6 +3942,10 @@ function validatePatch(form, patch, index) {
3554
3942
  fieldId: field.id,
3555
3943
  fieldKind: field.kind
3556
3944
  };
3945
+ for (const item of patch.value) {
3946
+ const sentinel = detectSentinel(item);
3947
+ if (sentinel) return embeddedSentinelError(index, field.id, sentinel.type);
3948
+ }
3557
3949
  } else if (patch.op === "set_single_select" && field.kind === "single_select") {
3558
3950
  if (patch.value !== null) {
3559
3951
  if (!new Set(field.options.map((o) => o.id)).has(patch.value)) return {
@@ -3592,6 +3984,10 @@ function validatePatch(form, patch, index) {
3592
3984
  fieldId: field.id,
3593
3985
  fieldKind: field.kind
3594
3986
  };
3987
+ for (const item of patch.value) {
3988
+ const sentinel = detectSentinel(item);
3989
+ if (sentinel) return embeddedSentinelError(index, field.id, sentinel.type);
3990
+ }
3595
3991
  } else if (patch.op === "set_table" && field.kind === "table") {
3596
3992
  const columnIds = field.columns.map((c) => c.id);
3597
3993
  if (!Array.isArray(patch.value)) return {
@@ -3676,23 +4072,18 @@ function setMultiSelectValue(responses, fieldId, selected) {
3676
4072
  */
3677
4073
  function patchValueToCell(value) {
3678
4074
  if (value === null || value === void 0) return { state: "skipped" };
3679
- if (typeof value === "string") {
3680
- const trimmed = value.trim();
3681
- const skipMatch = /^%SKIP(?:[:(](.*))?[)]?%$/i.exec(trimmed);
3682
- if (skipMatch) return {
3683
- state: "skipped",
3684
- reason: skipMatch[1]
3685
- };
3686
- const abortMatch = /^%ABORT(?:[:(](.*))?[)]?%$/i.exec(trimmed);
3687
- if (abortMatch) return {
3688
- state: "aborted",
3689
- reason: abortMatch[1]
3690
- };
3691
- return {
3692
- state: "answered",
3693
- value: trimmed
3694
- };
3695
- }
4075
+ const sentinel = detectSentinel(value);
4076
+ if (sentinel) return sentinel.type === "skip" ? {
4077
+ state: "skipped",
4078
+ ...sentinel.reason && { reason: sentinel.reason }
4079
+ } : {
4080
+ state: "aborted",
4081
+ ...sentinel.reason && { reason: sentinel.reason }
4082
+ };
4083
+ if (typeof value === "string") return {
4084
+ state: "answered",
4085
+ value: value.trim()
4086
+ };
3696
4087
  return {
3697
4088
  state: "answered",
3698
4089
  value
@@ -3833,15 +4224,15 @@ function applyPatches(form, patches) {
3833
4224
  }
3834
4225
  }
3835
4226
  if (validPatches.length === 0 && errors.length > 0) {
3836
- const issues$1 = convertToInspectIssues(form);
3837
- const summaries$1 = computeAllSummaries(form.schema, form.responsesByFieldId, form.notes, issues$1);
4227
+ const issues = convertToInspectIssues(form);
4228
+ const summaries = computeAllSummaries(form.schema, form.responsesByFieldId, form.notes, issues);
3838
4229
  return {
3839
4230
  applyStatus: "rejected",
3840
- structureSummary: summaries$1.structureSummary,
3841
- progressSummary: summaries$1.progressSummary,
3842
- issues: issues$1,
3843
- isComplete: summaries$1.isComplete,
3844
- formState: summaries$1.formState,
4231
+ structureSummary: summaries.structureSummary,
4232
+ progressSummary: summaries.progressSummary,
4233
+ issues,
4234
+ isComplete: summaries.isComplete,
4235
+ formState: summaries.formState,
3845
4236
  appliedPatches: [],
3846
4237
  rejectedPatches: errors,
3847
4238
  warnings: []
@@ -3868,5 +4259,5 @@ function applyPatches(form, patches) {
3868
4259
  }
3869
4260
 
3870
4261
  //#endregion
3871
- export { isConfigError as $, MAX_FORMS_IN_MENU as A, formatSuggestedLlms as B, DEFAULT_MAX_TURNS as C, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as D, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as E, deriveSchemaPath as F, MarkformConfigError as G, hasWebSearchSupport as H, detectFileType as I, MarkformParseError as J, MarkformError as K, parseRolesFlag as L, USER_ROLE as M, deriveExportPath as N, DEFAULT_ROLES as O, deriveReportPath as P, isAbortError as Q, SUGGESTED_LLMS as R, DEFAULT_MAX_STEPS_PER_TURN as S, DEFAULT_PRIORITY as T, parseModelIdForDisplay as U, getWebSearchConfig as V, MarkformAbortError as W, MarkformValidationError as X, MarkformPatchError as Y, ParseError as Z, validateSyntaxConsistency as _, validate as a, isValidationError as at, DEFAULT_MAX_ISSUES_PER_TURN as b, computeProgressSummary as c, serializeForm as d, isLlmError as et, serializeRawMarkdown as f, preprocessCommentSyntax as g, detectSyntaxStyle as h, inspect as i, isRetryableError as it, REPORT_EXTENSION as j, DEFAULT_ROLE_INSTRUCTIONS as k, computeStructureSummary as l, friendlyUrlAbbrev as m, getAllFields as n, isParseError as nt, computeAllSummaries as o, serializeReport as p, MarkformLlmError as q, getFieldsForRoles as r, isPatchError as rt, computeFormState as s, applyPatches as t, isMarkformError as tt, isFormComplete as u, AGENT_ROLE as v, DEFAULT_PORT as w, DEFAULT_MAX_PATCHES_PER_TURN as x, DEFAULT_FORMS_DIR as y, WEB_SEARCH_CONFIG as z };
3872
- //# sourceMappingURL=apply-DgDJBscb.mjs.map
4262
+ export { WEB_SEARCH_CONFIG as $, parseOptionText as A, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as B, extractTableContent as C, getStringAttr as D, getStringArrayAttr as E, DEFAULT_MAX_PATCHES_PER_TURN as F, REPORT_EXTENSION as G, DEFAULT_ROLES as H, DEFAULT_MAX_STEPS_PER_TURN as I, deriveReportPath as J, USER_ROLE as K, DEFAULT_MAX_TURNS as L, DEFAULT_FORMS_DIR as M, DEFAULT_MAX_ISSUES_PER_TURN as N, getValidateAttr as O, DEFAULT_MAX_PARALLEL_AGENTS as P, SUGGESTED_LLMS as Q, DEFAULT_PORT as R, extractOptionItems as S, getNumberAttr as T, DEFAULT_ROLE_INSTRUCTIONS as U, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as V, MAX_FORMS_IN_MENU as W, detectFileType as X, deriveSchemaPath as Y, parseRolesFlag as Z, preprocessCommentSyntax as _, isPatchError as _t, validate as a, MarkformConfigError as at, CHECKBOX_MARKERS as b, computeProgressSummary as c, MarkformParseError as ct, serializeForm as d, ParseError as dt, formatSuggestedLlms as et, serializeRawMarkdown as f, isAbortError as ft, detectSyntaxStyle as g, isParseError as gt, friendlyUrlAbbrev as h, isMarkformError as ht, inspect as i, MarkformAbortError as it, AGENT_ROLE as j, isTagNode as k, computeStructureSummary as l, MarkformPatchError as lt, formatBareUrlsAsHtmlLinks as m, isLlmError as mt, getAllFields as n, hasWebSearchSupport as nt, computeAllSummaries as o, MarkformError as ot, serializeReport as p, isConfigError as pt, deriveExportPath as q, getFieldsForRoles as r, parseModelIdForDisplay as rt, computeFormState as s, MarkformLlmError as st, applyPatches as t, getWebSearchConfig as tt, isFormComplete as u, MarkformValidationError as ut, validateSyntaxConsistency as v, isRetryableError as vt, getBooleanAttr as w, extractFenceValue as x, tryParseSentinelResponse as y, isValidationError as yt, DEFAULT_PRIORITY as z };
4263
+ //# sourceMappingURL=apply-Dalpt-D6.mjs.map