markform 0.1.2 → 0.1.4

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 (35) hide show
  1. package/README.md +97 -42
  2. package/dist/ai-sdk.d.mts +2 -2
  3. package/dist/ai-sdk.mjs +5 -5
  4. package/dist/{apply-BfAGTHMh.mjs → apply-C54EMAJ1.mjs} +383 -26
  5. package/dist/bin.mjs +6 -6
  6. package/dist/{cli-B3NVm6zL.mjs → cli-BhWhn6L9.mjs} +456 -141
  7. package/dist/cli.mjs +6 -6
  8. package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-cbNTYAcb.d.mts} +1878 -325
  9. package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
  10. package/dist/index.d.mts +146 -9
  11. package/dist/index.mjs +5 -5
  12. package/dist/session-B_stoXQn.mjs +4 -0
  13. package/dist/{session-Bqnwi9wp.mjs → session-uF0e6m6k.mjs} +9 -5
  14. package/dist/{shared-N_s1M-_K.mjs → shared-BqPnYXrn.mjs} +82 -1
  15. package/dist/shared-CZsyShck.mjs +3 -0
  16. package/dist/{src-BXRkGFpG.mjs → src-BNh7Cx9P.mjs} +801 -121
  17. package/docs/markform-apis.md +194 -0
  18. package/{DOCS.md → docs/markform-reference.md} +111 -50
  19. package/{SPEC.md → docs/markform-spec.md} +342 -91
  20. package/examples/celebrity-deep-research/celebrity-deep-research.form.md +196 -141
  21. package/examples/earnings-analysis/earnings-analysis.form.md +236 -226
  22. package/examples/movie-research/movie-research-basic.form.md +25 -21
  23. package/examples/movie-research/movie-research-deep.form.md +74 -62
  24. package/examples/movie-research/movie-research-minimal.form.md +29 -34
  25. package/examples/simple/simple-mock-filled.form.md +93 -29
  26. package/examples/simple/simple-skipped-filled.form.md +91 -29
  27. package/examples/simple/simple-with-skips.session.yaml +93 -25
  28. package/examples/simple/simple.form.md +74 -20
  29. package/examples/simple/simple.session.yaml +98 -25
  30. package/examples/startup-deep-research/startup-deep-research.form.md +108 -81
  31. package/examples/startup-research/startup-research-mock-filled.form.md +43 -43
  32. package/examples/startup-research/startup-research.form.md +24 -24
  33. package/package.json +18 -27
  34. package/dist/session-DdAtY2Ni.mjs +0 -4
  35. package/dist/shared-D7gf27Tr.mjs +0 -3
@@ -2,11 +2,33 @@ import { resolve } from "node:path";
2
2
 
3
3
  //#region src/llms.ts
4
4
  /**
5
- * LLM-related settings and configuration.
5
+ * Parse a model ID string into provider and model components for display.
6
6
  *
7
- * This module centralizes LLM provider and model configuration,
8
- * including suggested models and web search support.
9
- */
7
+ * This is a non-throwing utility for extracting display-friendly components
8
+ * from a model ID. For validation and resolution, use `parseModelId` from
9
+ * `modelResolver.ts` instead.
10
+ *
11
+ * @param modelId - Model ID in format `provider/model-id`
12
+ * @returns Parsed components with 'unknown' provider if format is invalid
13
+ *
14
+ * @example
15
+ * parseModelIdForDisplay('anthropic/claude-sonnet-4')
16
+ * // => { provider: 'anthropic', model: 'claude-sonnet-4' }
17
+ *
18
+ * parseModelIdForDisplay('claude-sonnet-4')
19
+ * // => { provider: 'unknown', model: 'claude-sonnet-4' }
20
+ */
21
+ function parseModelIdForDisplay(modelId) {
22
+ const slashIndex = modelId.indexOf("/");
23
+ if (slashIndex === -1 || slashIndex === 0 || slashIndex === modelId.length - 1) return {
24
+ provider: "unknown",
25
+ model: modelId
26
+ };
27
+ return {
28
+ provider: modelId.slice(0, slashIndex),
29
+ model: modelId.slice(slashIndex + 1)
30
+ };
31
+ }
10
32
  /**
11
33
  * Suggested LLM models for the fill command, organized by provider.
12
34
  * These are shown in help/error messages and model selection prompts.
@@ -233,6 +255,47 @@ function deriveExportPath(basePath, format) {
233
255
  }
234
256
  return base + EXPORT_EXTENSIONS[format];
235
257
  }
258
+ /**
259
+ * Derive report path from any markform file path.
260
+ * Strips known extensions and appends .report.md.
261
+ */
262
+ function deriveReportPath(basePath) {
263
+ let base = basePath;
264
+ for (const ext of Object.values(ALL_EXTENSIONS)) if (base.endsWith(ext)) {
265
+ base = base.slice(0, -ext.length);
266
+ break;
267
+ }
268
+ return base + REPORT_EXTENSION;
269
+ }
270
+
271
+ //#endregion
272
+ //#region src/utils/keySort.ts
273
+ /**
274
+ * Key-based sorting utilities.
275
+ */
276
+ /** Create a comparator from a key function. */
277
+ function keyComparator(keyFn) {
278
+ return (a, b) => {
279
+ const ka = keyFn(a), kb = keyFn(b);
280
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
281
+ };
282
+ }
283
+ /**
284
+ * Comparator that sorts priority keys first (in order), then remaining keys alphabetically.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * const attrs = { label: 'Name', kind: 'string', id: 'name', required: true };
289
+ * const keys = Object.keys(attrs).sort(priorityKeyComparator(['kind', 'id']));
290
+ * // => ['kind', 'id', 'label', 'required']
291
+ * ```
292
+ */
293
+ function priorityKeyComparator(priorityKeys) {
294
+ return keyComparator((key) => {
295
+ const i = priorityKeys.indexOf(key);
296
+ return [i !== -1 ? i : Infinity, key];
297
+ });
298
+ }
236
299
 
237
300
  //#endregion
238
301
  //#region src/engine/serialize.ts
@@ -308,11 +371,18 @@ function serializeAttrValue(value) {
308
371
  if (typeof value === "undefined") return "null";
309
372
  throw new Error(`Cannot serialize value of type ${typeof value} to Markdoc`);
310
373
  }
374
+ /** Priority keys that appear first in serialized attributes, in this order. */
375
+ const ATTR_PRIORITY_KEYS = [
376
+ "kind",
377
+ "id",
378
+ "role"
379
+ ];
311
380
  /**
312
- * Serialize attributes to Markdoc format with alphabetical ordering.
381
+ * Serialize attributes to Markdoc format.
382
+ * Priority keys (kind, id, role) appear first in order, then remaining keys alphabetically.
313
383
  */
314
384
  function serializeAttrs(attrs) {
315
- const keys = Object.keys(attrs).sort();
385
+ const keys = Object.keys(attrs).sort(priorityKeyComparator(ATTR_PRIORITY_KEYS));
316
386
  const parts = [];
317
387
  for (const key of keys) {
318
388
  const value = attrs[key];
@@ -342,6 +412,7 @@ function getMarker(state) {
342
412
  */
343
413
  function serializeStringField(field, response) {
344
414
  const attrs = {
415
+ kind: "string",
345
416
  id: field.id,
346
417
  label: field.label
347
418
  };
@@ -354,6 +425,8 @@ function serializeStringField(field, response) {
354
425
  if (field.maxLength !== void 0) attrs.maxLength = field.maxLength;
355
426
  if (field.validate) attrs.validate = field.validate;
356
427
  if (field.report !== void 0) attrs.report = field.report;
428
+ if (field.placeholder) attrs.placeholder = field.placeholder;
429
+ if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
357
430
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
358
431
  const attrStr = serializeAttrs(attrs);
359
432
  let content = "";
@@ -363,13 +436,14 @@ function serializeStringField(field, response) {
363
436
  }
364
437
  const sentinelContent = getSentinelContent(response);
365
438
  if (sentinelContent) content = sentinelContent;
366
- return `{% string-field ${attrStr} %}${content}{% /string-field %}`;
439
+ return `{% field ${attrStr} %}${content}{% /field %}`;
367
440
  }
368
441
  /**
369
442
  * Serialize a number field.
370
443
  */
371
444
  function serializeNumberField(field, response) {
372
445
  const attrs = {
446
+ kind: "number",
373
447
  id: field.id,
374
448
  label: field.label
375
449
  };
@@ -381,6 +455,8 @@ function serializeNumberField(field, response) {
381
455
  if (field.integer) attrs.integer = field.integer;
382
456
  if (field.validate) attrs.validate = field.validate;
383
457
  if (field.report !== void 0) attrs.report = field.report;
458
+ if (field.placeholder) attrs.placeholder = field.placeholder;
459
+ if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
384
460
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
385
461
  const attrStr = serializeAttrs(attrs);
386
462
  let content = "";
@@ -390,13 +466,14 @@ function serializeNumberField(field, response) {
390
466
  }
391
467
  const sentinelContent = getSentinelContent(response);
392
468
  if (sentinelContent) content = sentinelContent;
393
- return `{% number-field ${attrStr} %}${content}{% /number-field %}`;
469
+ return `{% field ${attrStr} %}${content}{% /field %}`;
394
470
  }
395
471
  /**
396
472
  * Serialize a string-list field.
397
473
  */
398
474
  function serializeStringListField(field, response) {
399
475
  const attrs = {
476
+ kind: "string_list",
400
477
  id: field.id,
401
478
  label: field.label
402
479
  };
@@ -410,6 +487,8 @@ function serializeStringListField(field, response) {
410
487
  if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
411
488
  if (field.validate) attrs.validate = field.validate;
412
489
  if (field.report !== void 0) attrs.report = field.report;
490
+ if (field.placeholder) attrs.placeholder = field.placeholder;
491
+ if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
413
492
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
414
493
  const attrStr = serializeAttrs(attrs);
415
494
  let content = "";
@@ -419,7 +498,7 @@ function serializeStringListField(field, response) {
419
498
  }
420
499
  const sentinelContent = getSentinelContent(response);
421
500
  if (sentinelContent) content = sentinelContent;
422
- return `{% string-list ${attrStr} %}${content}{% /string-list %}`;
501
+ return `{% field ${attrStr} %}${content}{% /field %}`;
423
502
  }
424
503
  /**
425
504
  * Serialize options (for single-select, multi-select, checkboxes).
@@ -437,6 +516,7 @@ function serializeOptions(options, selected) {
437
516
  */
438
517
  function serializeSingleSelectField(field, response) {
439
518
  const attrs = {
519
+ kind: "single_select",
440
520
  id: field.id,
441
521
  label: field.label
442
522
  };
@@ -451,13 +531,14 @@ function serializeSingleSelectField(field, response) {
451
531
  if (response?.state === "answered" && response.value) value = response.value;
452
532
  const selected = {};
453
533
  for (const opt of field.options) selected[opt.id] = opt.id === value?.selected ? "done" : "todo";
454
- return `{% single-select ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /single-select %}`;
534
+ return `{% field ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /field %}`;
455
535
  }
456
536
  /**
457
537
  * Serialize a multi-select field.
458
538
  */
459
539
  function serializeMultiSelectField(field, response) {
460
540
  const attrs = {
541
+ kind: "multi_select",
461
542
  id: field.id,
462
543
  label: field.label
463
544
  };
@@ -475,13 +556,14 @@ function serializeMultiSelectField(field, response) {
475
556
  const selected = {};
476
557
  const selectedSet = new Set(value?.selected ?? []);
477
558
  for (const opt of field.options) selected[opt.id] = selectedSet.has(opt.id) ? "done" : "todo";
478
- return `{% multi-select ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /multi-select %}`;
559
+ return `{% field ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /field %}`;
479
560
  }
480
561
  /**
481
562
  * Serialize a checkboxes field.
482
563
  */
483
564
  function serializeCheckboxesField(field, response) {
484
565
  const attrs = {
566
+ kind: "checkboxes",
485
567
  id: field.id,
486
568
  label: field.label
487
569
  };
@@ -497,13 +579,14 @@ function serializeCheckboxesField(field, response) {
497
579
  const attrStr = serializeAttrs(attrs);
498
580
  let value;
499
581
  if (response?.state === "answered" && response.value) value = response.value;
500
- return `{% checkboxes ${attrStr} %}\n${serializeOptions(field.options, value?.values ?? {})}\n{% /checkboxes %}`;
582
+ return `{% field ${attrStr} %}\n${serializeOptions(field.options, value?.values ?? {})}\n{% /field %}`;
501
583
  }
502
584
  /**
503
585
  * Serialize a url-field.
504
586
  */
505
587
  function serializeUrlField(field, response) {
506
588
  const attrs = {
589
+ kind: "url",
507
590
  id: field.id,
508
591
  label: field.label
509
592
  };
@@ -512,6 +595,8 @@ function serializeUrlField(field, response) {
512
595
  if (field.role !== AGENT_ROLE) attrs.role = field.role;
513
596
  if (field.validate) attrs.validate = field.validate;
514
597
  if (field.report !== void 0) attrs.report = field.report;
598
+ if (field.placeholder) attrs.placeholder = field.placeholder;
599
+ if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
515
600
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
516
601
  const attrStr = serializeAttrs(attrs);
517
602
  let content = "";
@@ -521,13 +606,14 @@ function serializeUrlField(field, response) {
521
606
  }
522
607
  const sentinelContent = getSentinelContent(response);
523
608
  if (sentinelContent) content = sentinelContent;
524
- return `{% url-field ${attrStr} %}${content}{% /url-field %}`;
609
+ return `{% field ${attrStr} %}${content}{% /field %}`;
525
610
  }
526
611
  /**
527
612
  * Serialize a url-list field.
528
613
  */
529
614
  function serializeUrlListField(field, response) {
530
615
  const attrs = {
616
+ kind: "url_list",
531
617
  id: field.id,
532
618
  label: field.label
533
619
  };
@@ -539,6 +625,8 @@ function serializeUrlListField(field, response) {
539
625
  if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
540
626
  if (field.validate) attrs.validate = field.validate;
541
627
  if (field.report !== void 0) attrs.report = field.report;
628
+ if (field.placeholder) attrs.placeholder = field.placeholder;
629
+ if (field.examples && field.examples.length > 0) attrs.examples = field.examples;
542
630
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
543
631
  const attrStr = serializeAttrs(attrs);
544
632
  let content = "";
@@ -548,13 +636,14 @@ function serializeUrlListField(field, response) {
548
636
  }
549
637
  const sentinelContent = getSentinelContent(response);
550
638
  if (sentinelContent) content = sentinelContent;
551
- return `{% url-list ${attrStr} %}${content}{% /url-list %}`;
639
+ return `{% field ${attrStr} %}${content}{% /field %}`;
552
640
  }
553
641
  /**
554
642
  * Serialize a date-field.
555
643
  */
556
644
  function serializeDateField(field, response) {
557
645
  const attrs = {
646
+ kind: "date",
558
647
  id: field.id,
559
648
  label: field.label
560
649
  };
@@ -574,13 +663,14 @@ function serializeDateField(field, response) {
574
663
  }
575
664
  const sentinelContent = getSentinelContent(response);
576
665
  if (sentinelContent) content = sentinelContent;
577
- return `{% date-field ${attrStr} %}${content}{% /date-field %}`;
666
+ return `{% field ${attrStr} %}${content}{% /field %}`;
578
667
  }
579
668
  /**
580
669
  * Serialize a year-field.
581
670
  */
582
671
  function serializeYearField(field, response) {
583
672
  const attrs = {
673
+ kind: "year",
584
674
  id: field.id,
585
675
  label: field.label
586
676
  };
@@ -600,7 +690,74 @@ function serializeYearField(field, response) {
600
690
  }
601
691
  const sentinelContent = getSentinelContent(response);
602
692
  if (sentinelContent) content = sentinelContent;
603
- return `{% year-field ${attrStr} %}${content}{% /year-field %}`;
693
+ return `{% field ${attrStr} %}${content}{% /field %}`;
694
+ }
695
+ /**
696
+ * Serialize a cell value for table output.
697
+ */
698
+ function serializeCellValue(cell, _columnType) {
699
+ if (cell.state === "skipped") return cell.reason ? `%SKIP:${cell.reason}%` : "%SKIP%";
700
+ if (cell.state === "aborted") return cell.reason ? `%ABORT:${cell.reason}%` : "%ABORT%";
701
+ if (cell.value === void 0 || cell.value === null) return "";
702
+ if (typeof cell.value === "number") return String(cell.value);
703
+ return cell.value;
704
+ }
705
+ /**
706
+ * Serialize a table row to markdown table row format.
707
+ */
708
+ function serializeTableRow(row, columns) {
709
+ return `| ${columns.map((col) => {
710
+ return serializeCellValue(row[col.id] ?? { state: "skipped" }, col.type);
711
+ }).join(" | ")} |`;
712
+ }
713
+ /**
714
+ * Serialize a table value to markdown table format.
715
+ */
716
+ function serializeMarkdownTable(value, columns) {
717
+ if (columns.length === 0) return "";
718
+ const lines = [];
719
+ const headerCells = columns.map((col) => col.label);
720
+ lines.push(`| ${headerCells.join(" | ")} |`);
721
+ const separatorCells = columns.map(() => "---");
722
+ lines.push(`| ${separatorCells.join(" | ")} |`);
723
+ for (const row of value.rows) lines.push(serializeTableRow(row, columns));
724
+ return lines.join("\n");
725
+ }
726
+ /**
727
+ * Serialize a table-field.
728
+ */
729
+ function serializeTableField(field, response) {
730
+ const attrs = {
731
+ kind: "table",
732
+ id: field.id,
733
+ label: field.label
734
+ };
735
+ if (field.required) attrs.required = field.required;
736
+ if (field.priority !== DEFAULT_PRIORITY) attrs.priority = field.priority;
737
+ if (field.role !== AGENT_ROLE) attrs.role = field.role;
738
+ attrs.columnIds = field.columns.map((c) => c.id);
739
+ attrs.columnLabels = field.columns.map((c) => c.label);
740
+ attrs.columnTypes = field.columns.map((c) => {
741
+ if (c.required) return {
742
+ type: c.type,
743
+ required: true
744
+ };
745
+ return c.type;
746
+ });
747
+ if (field.minRows !== void 0) attrs.minRows = field.minRows;
748
+ if (field.maxRows !== void 0) attrs.maxRows = field.maxRows;
749
+ if (field.validate) attrs.validate = field.validate;
750
+ if (field.report !== void 0) attrs.report = field.report;
751
+ if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
752
+ const attrStr = serializeAttrs(attrs);
753
+ let content = "";
754
+ if (response?.state === "answered" && response.value) {
755
+ const value = response.value;
756
+ if (value.rows.length > 0) content = formatValueFence(serializeMarkdownTable(value, field.columns));
757
+ }
758
+ const sentinelContent = getSentinelContent(response);
759
+ if (sentinelContent) content = sentinelContent;
760
+ return `{% field ${attrStr} %}${content}{% /field %}`;
604
761
  }
605
762
  /**
606
763
  * Serialize a field to Markdoc format.
@@ -618,6 +775,7 @@ function serializeField(field, responses) {
618
775
  case "url_list": return serializeUrlListField(field, response);
619
776
  case "date": return serializeDateField(field, response);
620
777
  case "year": return serializeYearField(field, response);
778
+ case "table": return serializeTableField(field, response);
621
779
  }
622
780
  }
623
781
  /**
@@ -652,13 +810,19 @@ function serializeNotes(notes) {
652
810
  }
653
811
  /**
654
812
  * Serialize a field group.
813
+ * Implicit groups (fields placed directly under the form) are serialized
814
+ * without the field-group wrapper tags.
655
815
  */
656
816
  function serializeFieldGroup(group, responses, docs) {
657
- const attrs = { id: group.id };
658
- if (group.title) attrs.title = group.title;
659
- if (group.validate) attrs.validate = group.validate;
660
- if (group.report !== void 0) attrs.report = group.report;
661
- const lines = [`{% field-group ${serializeAttrs(attrs)} %}`];
817
+ const lines = [];
818
+ if (!group.implicit) {
819
+ const attrs = { id: group.id };
820
+ if (group.title) attrs.title = group.title;
821
+ if (group.validate) attrs.validate = group.validate;
822
+ if (group.report !== void 0) attrs.report = group.report;
823
+ const attrStr = serializeAttrs(attrs);
824
+ lines.push(`{% field-group ${attrStr} %}`);
825
+ }
662
826
  const docsByRef = /* @__PURE__ */ new Map();
663
827
  for (const doc of docs) {
664
828
  const list = docsByRef.get(doc.ref) ?? [];
@@ -674,8 +838,10 @@ function serializeFieldGroup(group, responses, docs) {
674
838
  lines.push(serializeDocBlock(doc));
675
839
  }
676
840
  }
677
- lines.push("");
678
- lines.push("{% /field-group %}");
841
+ if (!group.implicit) {
842
+ lines.push("");
843
+ lines.push("{% /field-group %}");
844
+ }
679
845
  return lines.join("\n");
680
846
  }
681
847
  /**
@@ -807,6 +973,12 @@ function serializeFieldRaw(field, responses) {
807
973
  else lines.push("_(empty)_");
808
974
  break;
809
975
  }
976
+ case "table": {
977
+ const tableValue = value;
978
+ if (tableValue?.rows && tableValue.rows.length > 0) lines.push(`_(${tableValue.rows.length} rows)_`);
979
+ else lines.push("_(empty)_");
980
+ break;
981
+ }
810
982
  }
811
983
  return lines.join("\n");
812
984
  }
@@ -941,14 +1113,17 @@ function computeStructureSummary(schema) {
941
1113
  url: 0,
942
1114
  url_list: 0,
943
1115
  date: 0,
944
- year: 0
1116
+ year: 0,
1117
+ table: 0
945
1118
  };
946
1119
  const groupsById = {};
947
1120
  const fieldsById = {};
948
1121
  const optionsById = {};
1122
+ const columnsById = {};
949
1123
  let groupCount = 0;
950
1124
  let fieldCount = 0;
951
1125
  let optionCount = 0;
1126
+ let columnCount = 0;
952
1127
  for (const group of schema.groups) {
953
1128
  groupCount++;
954
1129
  groupsById[group.id] = "field_group";
@@ -964,16 +1139,26 @@ function computeStructureSummary(schema) {
964
1139
  parentFieldKind: field.kind
965
1140
  };
966
1141
  }
1142
+ if (field.kind === "table") for (const column of field.columns) {
1143
+ columnCount++;
1144
+ const qualifiedRef = `${field.id}.${column.id}`;
1145
+ columnsById[qualifiedRef] = {
1146
+ parentFieldId: field.id,
1147
+ columnType: column.type
1148
+ };
1149
+ }
967
1150
  }
968
1151
  }
969
1152
  return {
970
1153
  groupCount,
971
1154
  fieldCount,
972
1155
  optionCount,
1156
+ columnCount,
973
1157
  fieldCountByKind,
974
1158
  groupsById,
975
1159
  fieldsById,
976
- optionsById
1160
+ optionsById,
1161
+ columnsById
977
1162
  };
978
1163
  }
979
1164
  /**
@@ -1012,6 +1197,7 @@ function isFieldSubmitted(field, value) {
1012
1197
  return v.value !== null && v.value.trim() !== "";
1013
1198
  }
1014
1199
  case "year": return value.value !== null;
1200
+ case "table": return value.rows.length > 0;
1015
1201
  }
1016
1202
  }
1017
1203
  /**
@@ -1617,6 +1803,116 @@ function validateYearField(field, value) {
1617
1803
  return issues;
1618
1804
  }
1619
1805
  /**
1806
+ * Validate a cell value according to its column type.
1807
+ */
1808
+ function validateCellValue(cell, column, fieldId, rowIndex) {
1809
+ const issues = [];
1810
+ const cellRef = `${fieldId}[${rowIndex}].${column.id}`;
1811
+ if (cell.state === "skipped" || cell.state === "aborted") {
1812
+ if (column.required) issues.push({
1813
+ severity: "error",
1814
+ message: `Required cell "${column.label}" in row ${rowIndex + 1} is ${cell.state}`,
1815
+ ref: cellRef,
1816
+ source: "builtin"
1817
+ });
1818
+ return issues;
1819
+ }
1820
+ if (column.required && (cell.value === void 0 || cell.value === null || cell.value === "")) {
1821
+ issues.push({
1822
+ severity: "error",
1823
+ message: `Required cell "${column.label}" in row ${rowIndex + 1} is empty`,
1824
+ ref: cellRef,
1825
+ source: "builtin"
1826
+ });
1827
+ return issues;
1828
+ }
1829
+ if (cell.value === void 0 || cell.value === null || cell.value === "") return issues;
1830
+ switch (column.type) {
1831
+ case "number":
1832
+ if (typeof cell.value !== "number") issues.push({
1833
+ severity: "error",
1834
+ message: `Cell "${column.label}" in row ${rowIndex + 1} must be a number (got "${cell.value}")`,
1835
+ ref: cellRef,
1836
+ source: "builtin"
1837
+ });
1838
+ break;
1839
+ case "year":
1840
+ if (typeof cell.value !== "number" || !Number.isInteger(cell.value)) issues.push({
1841
+ severity: "error",
1842
+ message: `Cell "${column.label}" in row ${rowIndex + 1} must be an integer year (got "${cell.value}")`,
1843
+ ref: cellRef,
1844
+ source: "builtin"
1845
+ });
1846
+ break;
1847
+ case "url":
1848
+ if (typeof cell.value === "string") try {
1849
+ new URL(cell.value);
1850
+ } catch {
1851
+ issues.push({
1852
+ severity: "error",
1853
+ message: `Cell "${column.label}" in row ${rowIndex + 1} must be a valid URL (got "${cell.value}")`,
1854
+ ref: cellRef,
1855
+ source: "builtin"
1856
+ });
1857
+ }
1858
+ break;
1859
+ case "date":
1860
+ if (typeof cell.value === "string") {
1861
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(cell.value)) issues.push({
1862
+ severity: "error",
1863
+ message: `Cell "${column.label}" in row ${rowIndex + 1} must be a valid date in YYYY-MM-DD format (got "${cell.value}")`,
1864
+ ref: cellRef,
1865
+ source: "builtin"
1866
+ });
1867
+ }
1868
+ break;
1869
+ case "string": break;
1870
+ }
1871
+ return issues;
1872
+ }
1873
+ /**
1874
+ * Validate a table row.
1875
+ */
1876
+ function validateTableRow(row, columns, fieldId, rowIndex) {
1877
+ const issues = [];
1878
+ for (const column of columns) {
1879
+ const cell = row[column.id] ?? { state: "skipped" };
1880
+ issues.push(...validateCellValue(cell, column, fieldId, rowIndex));
1881
+ }
1882
+ return issues;
1883
+ }
1884
+ /**
1885
+ * Validate a table field.
1886
+ */
1887
+ function validateTableField(field, value) {
1888
+ const issues = [];
1889
+ const isEmpty = !value || value.rows.length === 0;
1890
+ if (field.required && isEmpty) {
1891
+ issues.push({
1892
+ severity: "error",
1893
+ message: `Required field "${field.label}" is empty`,
1894
+ ref: field.id,
1895
+ source: "builtin"
1896
+ });
1897
+ return issues;
1898
+ }
1899
+ if (isEmpty) return issues;
1900
+ if (field.minRows !== void 0 && value.rows.length < field.minRows) issues.push({
1901
+ severity: "error",
1902
+ message: `"${field.label}" must have at least ${field.minRows} row(s) (has ${value.rows.length})`,
1903
+ ref: field.id,
1904
+ source: "builtin"
1905
+ });
1906
+ if (field.maxRows !== void 0 && value.rows.length > field.maxRows) issues.push({
1907
+ severity: "error",
1908
+ message: `"${field.label}" must have at most ${field.maxRows} row(s) (has ${value.rows.length})`,
1909
+ ref: field.id,
1910
+ source: "builtin"
1911
+ });
1912
+ for (let i = 0; i < value.rows.length; i++) issues.push(...validateTableRow(value.rows[i], field.columns, field.id, i));
1913
+ return issues;
1914
+ }
1915
+ /**
1620
1916
  * Validate a single field.
1621
1917
  */
1622
1918
  function validateField(field, responses) {
@@ -1633,6 +1929,7 @@ function validateField(field, responses) {
1633
1929
  case "url_list": return validateUrlListField(field, value);
1634
1930
  case "date": return validateDateField(field, value);
1635
1931
  case "year": return validateYearField(field, value);
1932
+ case "table": return validateTableField(field, value);
1636
1933
  }
1637
1934
  }
1638
1935
  /**
@@ -2152,6 +2449,19 @@ function validatePatch(form, patch, index) {
2152
2449
  message: `Cannot apply set_year to ${field.kind} field "${field.id}"`
2153
2450
  };
2154
2451
  break;
2452
+ case "set_table": {
2453
+ if (field.kind !== "table") return {
2454
+ patchIndex: index,
2455
+ message: `Cannot apply set_table to ${field.kind} field "${field.id}"`
2456
+ };
2457
+ const tableField = field;
2458
+ const validColumns = new Set(tableField.columns.map((c) => c.id));
2459
+ for (const row of patch.rows) for (const colId of Object.keys(row)) if (!validColumns.has(colId)) return {
2460
+ patchIndex: index,
2461
+ message: `Invalid column "${colId}" for table field "${field.id}"`
2462
+ };
2463
+ break;
2464
+ }
2155
2465
  case "clear_field": break;
2156
2466
  case "skip_field":
2157
2467
  if (field.required) return {
@@ -2311,6 +2621,50 @@ function applySetYear(responses, patch) {
2311
2621
  };
2312
2622
  }
2313
2623
  /**
2624
+ * Convert a patch row value to a cell response.
2625
+ */
2626
+ function patchValueToCell(value) {
2627
+ if (value === null || value === void 0) return { state: "skipped" };
2628
+ if (typeof value === "string") {
2629
+ const trimmed = value.trim();
2630
+ const skipMatch = /^%SKIP(?:[:(](.*))?[)]?%$/i.exec(trimmed);
2631
+ if (skipMatch) return {
2632
+ state: "skipped",
2633
+ reason: skipMatch[1]
2634
+ };
2635
+ const abortMatch = /^%ABORT(?:[:(](.*))?[)]?%$/i.exec(trimmed);
2636
+ if (abortMatch) return {
2637
+ state: "aborted",
2638
+ reason: abortMatch[1]
2639
+ };
2640
+ return {
2641
+ state: "answered",
2642
+ value: trimmed
2643
+ };
2644
+ }
2645
+ return {
2646
+ state: "answered",
2647
+ value
2648
+ };
2649
+ }
2650
+ /**
2651
+ * Apply a set_table patch.
2652
+ */
2653
+ function applySetTable(responses, patch) {
2654
+ const rows = patch.rows.map((patchRow) => {
2655
+ const row = {};
2656
+ for (const [colId, value] of Object.entries(patchRow)) row[colId] = patchValueToCell(value);
2657
+ return row;
2658
+ });
2659
+ responses[patch.fieldId] = {
2660
+ state: "answered",
2661
+ value: {
2662
+ kind: "table",
2663
+ rows
2664
+ }
2665
+ };
2666
+ }
2667
+ /**
2314
2668
  * Apply a clear_field patch.
2315
2669
  */
2316
2670
  function applyClearField(responses, patch) {
@@ -2392,6 +2746,9 @@ function applyPatch(form, responses, patch) {
2392
2746
  case "set_year":
2393
2747
  applySetYear(responses, patch);
2394
2748
  break;
2749
+ case "set_table":
2750
+ applySetTable(responses, patch);
2751
+ break;
2395
2752
  case "clear_field":
2396
2753
  applyClearField(responses, patch);
2397
2754
  break;
@@ -2467,4 +2824,4 @@ function applyPatches(form, patches) {
2467
2824
  }
2468
2825
 
2469
2826
  //#endregion
2470
- export { SUGGESTED_LLMS as A, DEFAULT_ROLE_INSTRUCTIONS as C, detectFileType as D, deriveExportPath as E, formatSuggestedLlms as M, getWebSearchConfig as N, getFormsDir as O, hasWebSearchSupport as P, DEFAULT_ROLES as S, USER_ROLE as T, DEFAULT_MAX_TURNS as _, computeAllSummaries as a, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as b, computeStructureSummary as c, serializeRawMarkdown as d, serializeReportMarkdown as f, DEFAULT_MAX_PATCHES_PER_TURN as g, DEFAULT_MAX_ISSUES_PER_TURN as h, validate as i, WEB_SEARCH_CONFIG as j, parseRolesFlag as k, isFormComplete as l, DEFAULT_FORMS_DIR as m, getFieldsForRoles as n, computeFormState as o, AGENT_ROLE as p, inspect as r, computeProgressSummary as s, applyPatches as t, serialize as u, DEFAULT_PORT as v, REPORT_EXTENSION as w, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as x, DEFAULT_PRIORITY as y };
2827
+ export { parseRolesFlag as A, DEFAULT_ROLE_INSTRUCTIONS as C, deriveReportPath as D, deriveExportPath as E, hasWebSearchSupport as F, parseModelIdForDisplay as I, WEB_SEARCH_CONFIG as M, formatSuggestedLlms as N, detectFileType as O, getWebSearchConfig as P, DEFAULT_ROLES as S, USER_ROLE as T, DEFAULT_MAX_TURNS as _, computeAllSummaries as a, DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN as b, computeStructureSummary as c, serializeRawMarkdown as d, serializeReportMarkdown as f, DEFAULT_MAX_PATCHES_PER_TURN as g, DEFAULT_MAX_ISSUES_PER_TURN as h, validate as i, SUGGESTED_LLMS as j, getFormsDir as k, isFormComplete as l, DEFAULT_FORMS_DIR as m, getFieldsForRoles as n, computeFormState as o, AGENT_ROLE as p, inspect as r, computeProgressSummary as s, applyPatches as t, serialize as u, DEFAULT_PORT as v, REPORT_EXTENSION as w, DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN as x, DEFAULT_PRIORITY as y };