markform 0.1.18 → 0.1.20
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-BYgtU64w.mjs → apply-DIvm1b1s.mjs} +430 -31
- package/dist/apply-DIvm1b1s.mjs.map +1 -0
- package/dist/bin.mjs +20 -2
- package/dist/bin.mjs.map +1 -1
- package/dist/{cli-D9w0Bp4J.mjs → cli-FFMoEhFS.mjs} +1039 -89
- package/dist/cli-FFMoEhFS.mjs.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-SDB3KRRJ.mjs → coreTypes-CPKXf2dc.mjs} +1 -1
- package/dist/{coreTypes-SDB3KRRJ.mjs.map → coreTypes-CPKXf2dc.mjs.map} +1 -1
- package/dist/{coreTypes-BMEs8h_2.d.mts → coreTypes-CkxML8g2.d.mts} +4 -9
- package/dist/index.d.mts +515 -22
- package/dist/index.mjs +5 -5
- package/dist/{session-Ci4B0Pna.mjs → session-CK0x28RO.mjs} +2 -2
- package/dist/session-CK0x28RO.mjs.map +1 -0
- package/dist/{session-CW9AQw6i.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-DDxi-2ne.mjs → src-wR7GoftB.mjs} +1707 -645
- package/dist/src-wR7GoftB.mjs.map +1 -0
- package/docs/markform-apis.md +81 -0
- package/docs/markform-reference.md +15 -1
- package/package.json +17 -15
- package/dist/apply-BYgtU64w.mjs.map +0 -1
- package/dist/cli-D9w0Bp4J.mjs.map +0 -1
- package/dist/session-Ci4B0Pna.mjs.map +0 -1
- package/dist/shared-CCq4haEV.mjs.map +0 -1
- package/dist/src-DDxi-2ne.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.20";
|
|
6
6
|
/**
|
|
7
7
|
* Base error class for all markform errors.
|
|
8
8
|
* Consumers can catch this to handle any markform error.
|
|
@@ -413,6 +413,24 @@ const REPORT_EXTENSION = ".report.md";
|
|
|
413
413
|
*/
|
|
414
414
|
const SCHEMA_EXTENSION = ".schema.json";
|
|
415
415
|
/**
|
|
416
|
+
* Fill record extension - sidecar file containing execution metadata.
|
|
417
|
+
* Generated by `markform fill --record-fill` and read by `markform serve`.
|
|
418
|
+
*
|
|
419
|
+
* **Recommended naming convention:**
|
|
420
|
+
* - Form file: `document.form.md` → Fill record: `document.fill.json`
|
|
421
|
+
* - Form file: `document.md` → Fill record: `document.fill.json`
|
|
422
|
+
*
|
|
423
|
+
* Using `.form.md` for forms and `.fill.json` for fill records helps tools
|
|
424
|
+
* discover related files automatically. The CLI enforces this convention.
|
|
425
|
+
*
|
|
426
|
+
* The fill record contains:
|
|
427
|
+
* - Session timing and duration
|
|
428
|
+
* - LLM token usage (input/output)
|
|
429
|
+
* - Tool call timeline with results
|
|
430
|
+
* - Form progress at completion
|
|
431
|
+
*/
|
|
432
|
+
const FILL_RECORD_EXTENSION = ".fill.json";
|
|
433
|
+
/**
|
|
416
434
|
* All recognized markform file extensions.
|
|
417
435
|
* Combines export formats with report and schema formats.
|
|
418
436
|
*/
|
|
@@ -471,6 +489,343 @@ function deriveSchemaPath(basePath) {
|
|
|
471
489
|
}
|
|
472
490
|
return base + SCHEMA_EXTENSION;
|
|
473
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Derive fill record sidecar path from a form file path.
|
|
494
|
+
*
|
|
495
|
+
* **Convention:**
|
|
496
|
+
* - `document.form.md` → `document.fill.json`
|
|
497
|
+
* - `document.md` → `document.fill.json`
|
|
498
|
+
*
|
|
499
|
+
* Priority: strips `.form.md` first, then falls back to `.md`.
|
|
500
|
+
* This ensures the recommended `.form.md` extension is handled correctly
|
|
501
|
+
* while also supporting plain `.md` files.
|
|
502
|
+
*/
|
|
503
|
+
function deriveFillRecordPath(formPath) {
|
|
504
|
+
if (formPath.endsWith(EXPORT_EXTENSIONS.form)) return formPath.slice(0, -EXPORT_EXTENSIONS.form.length) + FILL_RECORD_EXTENSION;
|
|
505
|
+
if (formPath.endsWith(".md")) return formPath.slice(0, -3) + FILL_RECORD_EXTENSION;
|
|
506
|
+
return formPath + FILL_RECORD_EXTENSION;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/engine/parseHelpers.ts
|
|
511
|
+
/** Map checkbox marker to state value */
|
|
512
|
+
const CHECKBOX_MARKERS = {
|
|
513
|
+
"[ ]": "todo",
|
|
514
|
+
"[x]": "done",
|
|
515
|
+
"[X]": "done",
|
|
516
|
+
"[/]": "incomplete",
|
|
517
|
+
"[*]": "active",
|
|
518
|
+
"[-]": "na",
|
|
519
|
+
"[y]": "yes",
|
|
520
|
+
"[Y]": "yes",
|
|
521
|
+
"[n]": "no",
|
|
522
|
+
"[N]": "no"
|
|
523
|
+
};
|
|
524
|
+
const OPTION_TEXT_PATTERN = /^(\[[^\]]\])\s*(.*?)\s*$/;
|
|
525
|
+
/**
|
|
526
|
+
* Parse option text to extract marker and label.
|
|
527
|
+
* Text is like "[ ] Label" or "[x] Label".
|
|
528
|
+
*/
|
|
529
|
+
function parseOptionText(text) {
|
|
530
|
+
const match = OPTION_TEXT_PATTERN.exec(text);
|
|
531
|
+
if (!match) return null;
|
|
532
|
+
return {
|
|
533
|
+
marker: match[1] ?? "",
|
|
534
|
+
label: (match[2] ?? "").trim()
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if a node is a tag node with specific name.
|
|
539
|
+
* Works with raw AST nodes (not transformed Tags).
|
|
540
|
+
*/
|
|
541
|
+
function isTagNode(node, name) {
|
|
542
|
+
if (typeof node !== "object" || node === null) return false;
|
|
543
|
+
if (node.type === "tag" && node.tag) return name === void 0 || node.tag === name;
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Get string attribute value or undefined.
|
|
548
|
+
*/
|
|
549
|
+
function getStringAttr(node, name) {
|
|
550
|
+
const value = node.attributes?.[name];
|
|
551
|
+
return typeof value === "string" ? value : void 0;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get number attribute value or undefined.
|
|
555
|
+
*/
|
|
556
|
+
function getNumberAttr(node, name) {
|
|
557
|
+
const value = node.attributes?.[name];
|
|
558
|
+
return typeof value === "number" ? value : void 0;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Get boolean attribute value or undefined.
|
|
562
|
+
*/
|
|
563
|
+
function getBooleanAttr(node, name) {
|
|
564
|
+
const value = node.attributes?.[name];
|
|
565
|
+
return typeof value === "boolean" ? value : void 0;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get validator references from validate attribute.
|
|
569
|
+
* Handles both single string and array formats.
|
|
570
|
+
*/
|
|
571
|
+
function getValidateAttr(node) {
|
|
572
|
+
const value = node.attributes?.validate;
|
|
573
|
+
if (value === void 0 || value === null) return;
|
|
574
|
+
if (Array.isArray(value)) return value;
|
|
575
|
+
if (typeof value === "string") return [value];
|
|
576
|
+
if (typeof value === "object") return [value];
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Get string array attribute value or undefined.
|
|
580
|
+
* Handles both single string (converts to array) and array formats.
|
|
581
|
+
*/
|
|
582
|
+
function getStringArrayAttr(node, name) {
|
|
583
|
+
const value = node.attributes?.[name];
|
|
584
|
+
if (value === void 0 || value === null) return;
|
|
585
|
+
if (Array.isArray(value)) {
|
|
586
|
+
const strings = value.filter((v) => typeof v === "string");
|
|
587
|
+
return strings.length > 0 ? strings : void 0;
|
|
588
|
+
}
|
|
589
|
+
if (typeof value === "string") return [value];
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Extract option items from node children (for option lists).
|
|
593
|
+
* Works with raw AST nodes. Collects text and ID from list items.
|
|
594
|
+
*/
|
|
595
|
+
function extractOptionItems(node) {
|
|
596
|
+
const items = [];
|
|
597
|
+
/**
|
|
598
|
+
* Collect all text content from a node tree into a single string.
|
|
599
|
+
*/
|
|
600
|
+
function collectText(n) {
|
|
601
|
+
let text = "";
|
|
602
|
+
if (n.type === "text" && typeof n.attributes?.content === "string") text += n.attributes.content;
|
|
603
|
+
if (n.type === "softbreak") text += "\n";
|
|
604
|
+
if (n.children && Array.isArray(n.children)) for (const c of n.children) text += collectText(c);
|
|
605
|
+
return text;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Traverse to find list items and extract their content.
|
|
609
|
+
*/
|
|
610
|
+
function traverse(child) {
|
|
611
|
+
if (!child || typeof child !== "object") return;
|
|
612
|
+
if (child.type === "item") {
|
|
613
|
+
const text = collectText(child);
|
|
614
|
+
let id = null;
|
|
615
|
+
if (typeof child.attributes?.id === "string") id = child.attributes.id;
|
|
616
|
+
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;
|
|
617
|
+
if (text.trim()) items.push({
|
|
618
|
+
id,
|
|
619
|
+
text: text.trim()
|
|
620
|
+
});
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (child.children && Array.isArray(child.children)) for (const c of child.children) traverse(c);
|
|
624
|
+
}
|
|
625
|
+
if (node.children && Array.isArray(node.children)) for (const child of node.children) traverse(child);
|
|
626
|
+
return items;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Extract fence value from node children.
|
|
630
|
+
* Looks for ```value code blocks.
|
|
631
|
+
*/
|
|
632
|
+
function extractFenceValue(node) {
|
|
633
|
+
function traverse(child) {
|
|
634
|
+
if (!child || typeof child !== "object") return null;
|
|
635
|
+
if (child.type === "fence") {
|
|
636
|
+
if (child.attributes?.language === "value") return typeof child.attributes?.content === "string" ? child.attributes.content : null;
|
|
637
|
+
}
|
|
638
|
+
if (child.children && Array.isArray(child.children)) for (const c of child.children) {
|
|
639
|
+
const result = traverse(c);
|
|
640
|
+
if (result !== null) return result;
|
|
641
|
+
}
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
if (node.children && Array.isArray(node.children)) for (const child of node.children) {
|
|
645
|
+
const result = traverse(child);
|
|
646
|
+
if (result !== null) return result;
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Extract table content from node children.
|
|
652
|
+
* Handles both raw text and Markdoc-parsed table nodes.
|
|
653
|
+
* Reconstructs markdown table format from the AST.
|
|
654
|
+
*/
|
|
655
|
+
function extractTableContent(node) {
|
|
656
|
+
const lines = [];
|
|
657
|
+
function extractTextFromNode(n) {
|
|
658
|
+
if (!n || typeof n !== "object") return "";
|
|
659
|
+
if (n.type === "text" && typeof n.attributes?.content === "string") return n.attributes.content;
|
|
660
|
+
if (n.children && Array.isArray(n.children)) return n.children.map(extractTextFromNode).join("");
|
|
661
|
+
return "";
|
|
662
|
+
}
|
|
663
|
+
function extractTableRow(trNode) {
|
|
664
|
+
if (!trNode.children || !Array.isArray(trNode.children)) return "";
|
|
665
|
+
return `| ${trNode.children.filter((c) => c.type === "th" || c.type === "td").map((c) => extractTextFromNode(c).trim()).join(" | ")} |`;
|
|
666
|
+
}
|
|
667
|
+
function processNode(child) {
|
|
668
|
+
if (!child || typeof child !== "object") return;
|
|
669
|
+
if (child.type === "paragraph" || child.type === "inline") {
|
|
670
|
+
const text = extractTextFromNode(child).trim();
|
|
671
|
+
if (text) lines.push(text);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (child.type === "text" && typeof child.attributes?.content === "string") {
|
|
675
|
+
const text = child.attributes.content.trim();
|
|
676
|
+
if (text) lines.push(text);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (child.type === "table") {
|
|
680
|
+
const thead = child.children?.find((c) => c.type === "thead");
|
|
681
|
+
if (thead?.children) for (const tr of thead.children.filter((c) => c.type === "tr")) lines.push(extractTableRow(tr));
|
|
682
|
+
if (thead?.children?.length) {
|
|
683
|
+
const firstTr = thead.children.find((c) => c.type === "tr");
|
|
684
|
+
if (firstTr?.children) {
|
|
685
|
+
const colCount = firstTr.children.filter((c) => c.type === "th" || c.type === "td").length;
|
|
686
|
+
const separatorCells = Array(colCount).fill("----");
|
|
687
|
+
lines.push(`| ${separatorCells.join(" | ")} |`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const tbody = child.children?.find((c) => c.type === "tbody");
|
|
691
|
+
if (tbody?.children) for (const tr of tbody.children.filter((c) => c.type === "tr")) lines.push(extractTableRow(tr));
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
if (child.children && Array.isArray(child.children)) for (const c of child.children) processNode(c);
|
|
695
|
+
}
|
|
696
|
+
if (node.children && Array.isArray(node.children)) for (const child of node.children) processNode(child);
|
|
697
|
+
return lines.join("\n").trim() || null;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/engine/parseSentinels.ts
|
|
702
|
+
/** Sentinel values for text fields */
|
|
703
|
+
const SENTINEL_SKIP = "%SKIP%";
|
|
704
|
+
const SENTINEL_ABORT = "%ABORT%";
|
|
705
|
+
/**
|
|
706
|
+
* Detect if a value contains a sentinel pattern (%SKIP% or %ABORT%).
|
|
707
|
+
*
|
|
708
|
+
* This is the shared low-level detection function used for:
|
|
709
|
+
* - Form parsing validation (strict format)
|
|
710
|
+
* - Patch value validation (reject embedded sentinels)
|
|
711
|
+
* - Table cell parsing (convert to skipped/aborted state)
|
|
712
|
+
*
|
|
713
|
+
* Supports multiple formats that LLMs might generate:
|
|
714
|
+
* - `%SKIP%` or `%skip%` (case-insensitive)
|
|
715
|
+
* - `%SKIP% (reason)` - canonical format
|
|
716
|
+
* - `%SKIP:reason%` or `%SKIP(reason)%` - compact formats
|
|
717
|
+
*
|
|
718
|
+
* @param value - The value to check (returns null for non-strings)
|
|
719
|
+
* @returns Detected sentinel type and optional reason, or null if no sentinel
|
|
720
|
+
*/
|
|
721
|
+
function detectSentinel(value) {
|
|
722
|
+
if (value == null || typeof value !== "string") return null;
|
|
723
|
+
const trimmed = value.trim();
|
|
724
|
+
const compactSkipMatch = /^%SKIP(?:[:(](.*?))?[)]?%$/i.exec(trimmed);
|
|
725
|
+
if (compactSkipMatch) {
|
|
726
|
+
const reason = compactSkipMatch[1]?.trim();
|
|
727
|
+
return {
|
|
728
|
+
type: "skip",
|
|
729
|
+
...reason && { reason }
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const compactAbortMatch = /^%ABORT(?:[:(](.*?))?[)]?%$/i.exec(trimmed);
|
|
733
|
+
if (compactAbortMatch) {
|
|
734
|
+
const reason = compactAbortMatch[1]?.trim();
|
|
735
|
+
return {
|
|
736
|
+
type: "abort",
|
|
737
|
+
...reason && { reason }
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const upper = trimmed.toUpperCase();
|
|
741
|
+
if (upper.startsWith("%SKIP%")) {
|
|
742
|
+
const rest = trimmed.slice(6).trim();
|
|
743
|
+
if (rest === "") return { type: "skip" };
|
|
744
|
+
const reasonMatch = /^\((.+)\)$/s.exec(rest);
|
|
745
|
+
if (reasonMatch?.[1]) return {
|
|
746
|
+
type: "skip",
|
|
747
|
+
reason: reasonMatch[1].trim()
|
|
748
|
+
};
|
|
749
|
+
return { type: "skip" };
|
|
750
|
+
}
|
|
751
|
+
if (upper.startsWith("%ABORT%")) {
|
|
752
|
+
const rest = trimmed.slice(7).trim();
|
|
753
|
+
if (rest === "") return { type: "abort" };
|
|
754
|
+
const reasonMatch = /^\((.+)\)$/s.exec(rest);
|
|
755
|
+
if (reasonMatch?.[1]) return {
|
|
756
|
+
type: "abort",
|
|
757
|
+
reason: reasonMatch[1].trim()
|
|
758
|
+
};
|
|
759
|
+
return { type: "abort" };
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Parse a sentinel value with optional parenthesized reason (strict format).
|
|
765
|
+
*
|
|
766
|
+
* This is the strict parser used during form parsing, where we want to
|
|
767
|
+
* validate the exact format. For patch validation, use detectSentinel().
|
|
768
|
+
*
|
|
769
|
+
* Formats: `%SKIP%`, `%SKIP% (reason text)`, `%ABORT%`, `%ABORT% (reason text)`
|
|
770
|
+
* Returns null if the content is not a valid sentinel format.
|
|
771
|
+
*/
|
|
772
|
+
function parseSentinel(content) {
|
|
773
|
+
if (!content) return null;
|
|
774
|
+
const trimmed = content.trim();
|
|
775
|
+
const reasonPattern = /^\((.+)\)$/s;
|
|
776
|
+
if (trimmed.startsWith(SENTINEL_SKIP)) {
|
|
777
|
+
const rest = trimmed.slice(6).trim();
|
|
778
|
+
if (rest === "") return { type: "skip" };
|
|
779
|
+
const match = reasonPattern.exec(rest);
|
|
780
|
+
if (match?.[1]) return {
|
|
781
|
+
type: "skip",
|
|
782
|
+
reason: match[1].trim()
|
|
783
|
+
};
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
if (trimmed.startsWith(SENTINEL_ABORT)) {
|
|
787
|
+
const rest = trimmed.slice(7).trim();
|
|
788
|
+
if (rest === "") return { type: "abort" };
|
|
789
|
+
const match = reasonPattern.exec(rest);
|
|
790
|
+
if (match?.[1]) return {
|
|
791
|
+
type: "abort",
|
|
792
|
+
reason: match[1].trim()
|
|
793
|
+
};
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Check for sentinel values in fence content and validate against state attribute.
|
|
800
|
+
* Handles the common pattern of checking for %SKIP% and %ABORT% sentinels in field values.
|
|
801
|
+
*
|
|
802
|
+
* @param node - The field node to check
|
|
803
|
+
* @param fieldId - The field ID for error messages
|
|
804
|
+
* @param required - Whether the field is required (skip not allowed on required fields)
|
|
805
|
+
* @returns A FieldResponse if a sentinel is found, null otherwise
|
|
806
|
+
*/
|
|
807
|
+
function tryParseSentinelResponse(node, fieldId, required) {
|
|
808
|
+
const fenceContent = extractFenceValue(node);
|
|
809
|
+
const stateAttr = getStringAttr(node, "state");
|
|
810
|
+
const sentinel = parseSentinel(fenceContent);
|
|
811
|
+
if (!sentinel) return null;
|
|
812
|
+
if (sentinel.type === "skip") {
|
|
813
|
+
if (stateAttr !== void 0 && stateAttr !== "skipped") throw new MarkformParseError(`Field '${fieldId}' has conflicting state='${stateAttr}' with %SKIP% sentinel`);
|
|
814
|
+
if (required) throw new MarkformParseError(`Field '${fieldId}' is required but has %SKIP% sentinel. Cannot skip required fields.`);
|
|
815
|
+
return {
|
|
816
|
+
state: "skipped",
|
|
817
|
+
...sentinel.reason && { reason: sentinel.reason }
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
if (sentinel.type === "abort") {
|
|
821
|
+
if (stateAttr !== void 0 && stateAttr !== "aborted") throw new MarkformParseError(`Field '${fieldId}' has conflicting state='${stateAttr}' with %ABORT% sentinel`);
|
|
822
|
+
return {
|
|
823
|
+
state: "aborted",
|
|
824
|
+
...sentinel.reason && { reason: sentinel.reason }
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
474
829
|
|
|
475
830
|
//#endregion
|
|
476
831
|
//#region src/engine/preprocess.ts
|
|
@@ -508,10 +863,10 @@ function isValidFormTag(content) {
|
|
|
508
863
|
return trimmed.includes("=") && /\bid\s*=/.test(trimmed);
|
|
509
864
|
}
|
|
510
865
|
/** Parser state for tracking code blocks */
|
|
511
|
-
var State = /* @__PURE__ */ function(State
|
|
512
|
-
State
|
|
513
|
-
State
|
|
514
|
-
return State
|
|
866
|
+
var State = /* @__PURE__ */ function(State) {
|
|
867
|
+
State[State["NORMAL"] = 0] = "NORMAL";
|
|
868
|
+
State[State["FENCED_CODE"] = 1] = "FENCED_CODE";
|
|
869
|
+
return State;
|
|
515
870
|
}(State || {});
|
|
516
871
|
/**
|
|
517
872
|
* Check if position is at the start of a line (or at position 0).
|
|
@@ -992,6 +1347,33 @@ function friendlyUrlAbbrev(url, maxPathChars = 12) {
|
|
|
992
1347
|
function formatUrlAsMarkdownLink(url) {
|
|
993
1348
|
return `[${friendlyUrlAbbrev(url)}](${url})`;
|
|
994
1349
|
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Format bare URLs in text as HTML links with abbreviated display text.
|
|
1352
|
+
* Also handles markdown-style links [text](url) for consistency.
|
|
1353
|
+
*
|
|
1354
|
+
* Processing order:
|
|
1355
|
+
* 1. Escape all HTML to prevent XSS
|
|
1356
|
+
* 2. Convert markdown links [text](url) to <a> tags
|
|
1357
|
+
* 3. Convert bare URLs (not already in links) to <a> tags with abbreviated display
|
|
1358
|
+
*
|
|
1359
|
+
* @param text - The raw text containing URLs (will be HTML-escaped)
|
|
1360
|
+
* @param escapeHtml - Function to escape HTML entities
|
|
1361
|
+
* @returns HTML-safe text with URLs converted to <a> tags
|
|
1362
|
+
*/
|
|
1363
|
+
function formatBareUrlsAsHtmlLinks(text, escapeHtml) {
|
|
1364
|
+
let result = escapeHtml(text);
|
|
1365
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, linkText, url) => {
|
|
1366
|
+
const cleanUrl = url.replace(/&/g, "&");
|
|
1367
|
+
return `<a href="${escapeHtml(cleanUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(cleanUrl)}">${linkText}</a>`;
|
|
1368
|
+
});
|
|
1369
|
+
result = result.replace(/(?<!href="|data-url="|">)(?:https?:\/\/|www\.)[^\s<>"]+(?<![.,;:!?'")])/g, (url) => {
|
|
1370
|
+
const cleanUrl = url.replace(/&/g, "&");
|
|
1371
|
+
const fullUrl = cleanUrl.startsWith("www.") ? `https://${cleanUrl}` : cleanUrl;
|
|
1372
|
+
const display = friendlyUrlAbbrev(fullUrl);
|
|
1373
|
+
return `<a href="${escapeHtml(fullUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(fullUrl)}">${escapeHtml(display)}</a>`;
|
|
1374
|
+
});
|
|
1375
|
+
return result;
|
|
1376
|
+
}
|
|
995
1377
|
|
|
996
1378
|
//#endregion
|
|
997
1379
|
//#region src/engine/serialize.ts
|
|
@@ -3549,6 +3931,16 @@ function typeMismatchError(index, op, field) {
|
|
|
3549
3931
|
};
|
|
3550
3932
|
}
|
|
3551
3933
|
/**
|
|
3934
|
+
* Create an error for embedded sentinel in patch value.
|
|
3935
|
+
*/
|
|
3936
|
+
function embeddedSentinelError(index, fieldId, sentinelType) {
|
|
3937
|
+
return {
|
|
3938
|
+
patchIndex: index,
|
|
3939
|
+
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.`,
|
|
3940
|
+
fieldId
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
/**
|
|
3552
3944
|
* Validate a single patch against the form schema.
|
|
3553
3945
|
*/
|
|
3554
3946
|
function validatePatch(form, patch, index) {
|
|
@@ -3573,6 +3965,10 @@ function validatePatch(form, patch, index) {
|
|
|
3573
3965
|
};
|
|
3574
3966
|
const expectedKind = PATCH_OP_TO_FIELD_KIND[patch.op];
|
|
3575
3967
|
if (expectedKind && field.kind !== expectedKind) return typeMismatchError(index, patch.op, field);
|
|
3968
|
+
if (patch.op === "set_string" || patch.op === "set_url" || patch.op === "set_date") {
|
|
3969
|
+
const sentinel = detectSentinel(patch.value);
|
|
3970
|
+
if (sentinel) return embeddedSentinelError(index, field.id, sentinel.type);
|
|
3971
|
+
}
|
|
3576
3972
|
if (patch.op === "set_string_list" && field.kind === "string_list") {
|
|
3577
3973
|
if (!Array.isArray(patch.value)) return {
|
|
3578
3974
|
patchIndex: index,
|
|
@@ -3580,6 +3976,10 @@ function validatePatch(form, patch, index) {
|
|
|
3580
3976
|
fieldId: field.id,
|
|
3581
3977
|
fieldKind: field.kind
|
|
3582
3978
|
};
|
|
3979
|
+
for (const item of patch.value) {
|
|
3980
|
+
const sentinel = detectSentinel(item);
|
|
3981
|
+
if (sentinel) return embeddedSentinelError(index, field.id, sentinel.type);
|
|
3982
|
+
}
|
|
3583
3983
|
} else if (patch.op === "set_single_select" && field.kind === "single_select") {
|
|
3584
3984
|
if (patch.value !== null) {
|
|
3585
3985
|
if (!new Set(field.options.map((o) => o.id)).has(patch.value)) return {
|
|
@@ -3618,6 +4018,10 @@ function validatePatch(form, patch, index) {
|
|
|
3618
4018
|
fieldId: field.id,
|
|
3619
4019
|
fieldKind: field.kind
|
|
3620
4020
|
};
|
|
4021
|
+
for (const item of patch.value) {
|
|
4022
|
+
const sentinel = detectSentinel(item);
|
|
4023
|
+
if (sentinel) return embeddedSentinelError(index, field.id, sentinel.type);
|
|
4024
|
+
}
|
|
3621
4025
|
} else if (patch.op === "set_table" && field.kind === "table") {
|
|
3622
4026
|
const columnIds = field.columns.map((c) => c.id);
|
|
3623
4027
|
if (!Array.isArray(patch.value)) return {
|
|
@@ -3702,23 +4106,18 @@ function setMultiSelectValue(responses, fieldId, selected) {
|
|
|
3702
4106
|
*/
|
|
3703
4107
|
function patchValueToCell(value) {
|
|
3704
4108
|
if (value === null || value === void 0) return { state: "skipped" };
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
return {
|
|
3718
|
-
state: "answered",
|
|
3719
|
-
value: trimmed
|
|
3720
|
-
};
|
|
3721
|
-
}
|
|
4109
|
+
const sentinel = detectSentinel(value);
|
|
4110
|
+
if (sentinel) return sentinel.type === "skip" ? {
|
|
4111
|
+
state: "skipped",
|
|
4112
|
+
...sentinel.reason && { reason: sentinel.reason }
|
|
4113
|
+
} : {
|
|
4114
|
+
state: "aborted",
|
|
4115
|
+
...sentinel.reason && { reason: sentinel.reason }
|
|
4116
|
+
};
|
|
4117
|
+
if (typeof value === "string") return {
|
|
4118
|
+
state: "answered",
|
|
4119
|
+
value: value.trim()
|
|
4120
|
+
};
|
|
3722
4121
|
return {
|
|
3723
4122
|
state: "answered",
|
|
3724
4123
|
value
|
|
@@ -3859,15 +4258,15 @@ function applyPatches(form, patches) {
|
|
|
3859
4258
|
}
|
|
3860
4259
|
}
|
|
3861
4260
|
if (validPatches.length === 0 && errors.length > 0) {
|
|
3862
|
-
const issues
|
|
3863
|
-
const summaries
|
|
4261
|
+
const issues = convertToInspectIssues(form);
|
|
4262
|
+
const summaries = computeAllSummaries(form.schema, form.responsesByFieldId, form.notes, issues);
|
|
3864
4263
|
return {
|
|
3865
4264
|
applyStatus: "rejected",
|
|
3866
|
-
structureSummary: summaries
|
|
3867
|
-
progressSummary: summaries
|
|
3868
|
-
issues
|
|
3869
|
-
isComplete: summaries
|
|
3870
|
-
formState: summaries
|
|
4265
|
+
structureSummary: summaries.structureSummary,
|
|
4266
|
+
progressSummary: summaries.progressSummary,
|
|
4267
|
+
issues,
|
|
4268
|
+
isComplete: summaries.isComplete,
|
|
4269
|
+
formState: summaries.formState,
|
|
3871
4270
|
appliedPatches: [],
|
|
3872
4271
|
rejectedPatches: errors,
|
|
3873
4272
|
warnings: []
|
|
@@ -3894,5 +4293,5 @@ function applyPatches(form, patches) {
|
|
|
3894
4293
|
}
|
|
3895
4294
|
|
|
3896
4295
|
//#endregion
|
|
3897
|
-
export {
|
|
3898
|
-
//# sourceMappingURL=apply-
|
|
4296
|
+
export { SUGGESTED_LLMS 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, deriveFillRecordPath 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, parseRolesFlag 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, deriveSchemaPath as X, deriveReportPath as Y, detectFileType as Z, preprocessCommentSyntax as _, isParseError as _t, validate as a, MarkformAbortError as at, CHECKBOX_MARKERS as b, isValidationError as bt, computeProgressSummary as c, MarkformLlmError as ct, serializeForm as d, MarkformValidationError as dt, WEB_SEARCH_CONFIG as et, serializeRawMarkdown as f, ParseError as ft, detectSyntaxStyle as g, isMarkformError as gt, friendlyUrlAbbrev as h, isLlmError as ht, inspect as i, parseModelIdForDisplay as it, AGENT_ROLE as j, isTagNode as k, computeStructureSummary as l, MarkformParseError as lt, formatBareUrlsAsHtmlLinks as m, isConfigError as mt, getAllFields as n, getWebSearchConfig as nt, computeAllSummaries as o, MarkformConfigError as ot, serializeReport as p, isAbortError as pt, deriveExportPath as q, getFieldsForRoles as r, hasWebSearchSupport as rt, computeFormState as s, MarkformError as st, applyPatches as t, formatSuggestedLlms as tt, isFormComplete as u, MarkformPatchError as ut, validateSyntaxConsistency as v, isPatchError as vt, getBooleanAttr as w, extractFenceValue as x, tryParseSentinelResponse as y, isRetryableError as yt, DEFAULT_PRIORITY as z };
|
|
4297
|
+
//# sourceMappingURL=apply-DIvm1b1s.mjs.map
|