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.
- package/README.md +27 -2
- package/dist/ai-sdk.d.mts +1 -2
- package/dist/ai-sdk.mjs +2 -2
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/{apply-DgDJBscb.mjs → apply-Dalpt-D6.mjs} +422 -31
- package/dist/apply-Dalpt-D6.mjs.map +1 -0
- package/dist/bin.mjs +20 -2
- package/dist/bin.mjs.map +1 -1
- package/dist/{cli-DAl8LQzI.mjs → cli-tpvFNqFY.mjs} +325 -73
- package/dist/cli-tpvFNqFY.mjs.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-DiCddBKu.mjs → coreTypes-CPKXf2dc.mjs} +9 -4
- package/dist/coreTypes-CPKXf2dc.mjs.map +1 -0
- package/dist/{coreTypes-CnEea7Kh.d.mts → coreTypes-CkxML8g2.d.mts} +128 -10
- package/dist/index.d.mts +642 -22
- package/dist/index.mjs +5 -5
- package/dist/{session-XDrocA3j.mjs → session-CK0x28RO.mjs} +2 -2
- package/dist/session-CK0x28RO.mjs.map +1 -0
- package/dist/{session-B7aR6hno.mjs → session-ZHBi3LVQ.mjs} +1 -1
- package/dist/{shared-fUKfJ1UA.mjs → shared-BTR35aMz.mjs} +1 -1
- package/dist/{shared-CCq4haEV.mjs → shared-DwdyWmvE.mjs} +1 -3
- package/dist/shared-DwdyWmvE.mjs.map +1 -0
- package/dist/{src-CHVJLGKt.mjs → src-BTyz-wS6.mjs} +2009 -584
- package/dist/src-BTyz-wS6.mjs.map +1 -0
- package/docs/markform-apis.md +112 -0
- package/docs/markform-reference.md +27 -0
- package/docs/markform-spec.md +115 -0
- package/examples/movie-research/movie-deep-research-mock-filled.form.md +1 -1
- package/examples/movie-research/movie-deep-research.form.md +1 -1
- package/examples/parallel/parallel-research.form.md +57 -0
- package/examples/startup-deep-research/startup-deep-research.form.md +1 -1
- package/package.json +16 -14
- package/dist/apply-DgDJBscb.mjs.map +0 -1
- package/dist/cli-DAl8LQzI.mjs.map +0 -1
- package/dist/coreTypes-DiCddBKu.mjs.map +0 -1
- package/dist/session-XDrocA3j.mjs.map +0 -1
- package/dist/shared-CCq4haEV.mjs.map +0 -1
- 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.
|
|
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
|
|
507
|
-
State
|
|
508
|
-
State
|
|
509
|
-
return State
|
|
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(/&/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(/&/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
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
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
|
|
3837
|
-
const summaries
|
|
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
|
|
3841
|
-
progressSummary: summaries
|
|
3842
|
-
issues
|
|
3843
|
-
isComplete: summaries
|
|
3844
|
-
formState: summaries
|
|
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 {
|
|
3872
|
-
//# sourceMappingURL=apply-
|
|
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
|