markform 0.1.3 → 0.1.5

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 (36) hide show
  1. package/README.md +110 -70
  2. package/dist/ai-sdk.d.mts +2 -2
  3. package/dist/ai-sdk.mjs +5 -5
  4. package/dist/{apply-00UmzDKL.mjs → apply-BCCiJzQr.mjs} +371 -26
  5. package/dist/bin.mjs +6 -6
  6. package/dist/{cli-D--Lel-e.mjs → cli-D469amuk.mjs} +386 -96
  7. package/dist/cli.mjs +6 -6
  8. package/dist/{coreTypes-BXhhz9Iq.d.mts → coreTypes-9XZSNOv6.d.mts} +1878 -325
  9. package/dist/{coreTypes-Dful87E0.mjs → coreTypes-pyctKRgc.mjs} +79 -5
  10. package/dist/index.d.mts +142 -5
  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-Dm8jZ5dl.mjs → src-Df0XX7UB.mjs} +818 -125
  17. package/docs/markform-apis.md +194 -0
  18. package/{DOCS.md → docs/markform-reference.md} +130 -69
  19. package/{SPEC.md → docs/markform-spec.md} +359 -108
  20. package/examples/earnings-analysis/earnings-analysis.form.md +88 -800
  21. package/examples/earnings-analysis/earnings-analysis.valid.ts +16 -148
  22. package/examples/movie-research/movie-research-basic.form.md +41 -37
  23. package/examples/movie-research/movie-research-deep.form.md +110 -98
  24. package/examples/movie-research/movie-research-minimal.form.md +29 -15
  25. package/examples/simple/simple-mock-filled.form.md +105 -41
  26. package/examples/simple/simple-skipped-filled.form.md +103 -41
  27. package/examples/simple/simple-with-skips.session.yaml +93 -25
  28. package/examples/simple/simple.form.md +86 -32
  29. package/examples/simple/simple.session.yaml +98 -25
  30. package/examples/startup-deep-research/startup-deep-research.form.md +130 -103
  31. package/examples/startup-research/startup-research-mock-filled.form.md +55 -55
  32. package/examples/startup-research/startup-research.form.md +36 -36
  33. package/package.json +18 -19
  34. package/dist/session-DdAtY2Ni.mjs +0 -4
  35. package/dist/shared-D7gf27Tr.mjs +0 -3
  36. package/examples/celebrity-deep-research/celebrity-deep-research.form.md +0 -912
@@ -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.
@@ -246,6 +268,35 @@ function deriveReportPath(basePath) {
246
268
  return base + REPORT_EXTENSION;
247
269
  }
248
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
+ }
299
+
249
300
  //#endregion
250
301
  //#region src/engine/serialize.ts
251
302
  /**
@@ -320,11 +371,18 @@ function serializeAttrValue(value) {
320
371
  if (typeof value === "undefined") return "null";
321
372
  throw new Error(`Cannot serialize value of type ${typeof value} to Markdoc`);
322
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
+ ];
323
380
  /**
324
- * 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.
325
383
  */
326
384
  function serializeAttrs(attrs) {
327
- const keys = Object.keys(attrs).sort();
385
+ const keys = Object.keys(attrs).sort(priorityKeyComparator(ATTR_PRIORITY_KEYS));
328
386
  const parts = [];
329
387
  for (const key of keys) {
330
388
  const value = attrs[key];
@@ -354,6 +412,7 @@ function getMarker(state) {
354
412
  */
355
413
  function serializeStringField(field, response) {
356
414
  const attrs = {
415
+ kind: "string",
357
416
  id: field.id,
358
417
  label: field.label
359
418
  };
@@ -366,6 +425,8 @@ function serializeStringField(field, response) {
366
425
  if (field.maxLength !== void 0) attrs.maxLength = field.maxLength;
367
426
  if (field.validate) attrs.validate = field.validate;
368
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;
369
430
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
370
431
  const attrStr = serializeAttrs(attrs);
371
432
  let content = "";
@@ -375,13 +436,14 @@ function serializeStringField(field, response) {
375
436
  }
376
437
  const sentinelContent = getSentinelContent(response);
377
438
  if (sentinelContent) content = sentinelContent;
378
- return `{% string-field ${attrStr} %}${content}{% /string-field %}`;
439
+ return `{% field ${attrStr} %}${content}{% /field %}`;
379
440
  }
380
441
  /**
381
442
  * Serialize a number field.
382
443
  */
383
444
  function serializeNumberField(field, response) {
384
445
  const attrs = {
446
+ kind: "number",
385
447
  id: field.id,
386
448
  label: field.label
387
449
  };
@@ -393,6 +455,8 @@ function serializeNumberField(field, response) {
393
455
  if (field.integer) attrs.integer = field.integer;
394
456
  if (field.validate) attrs.validate = field.validate;
395
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;
396
460
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
397
461
  const attrStr = serializeAttrs(attrs);
398
462
  let content = "";
@@ -402,13 +466,14 @@ function serializeNumberField(field, response) {
402
466
  }
403
467
  const sentinelContent = getSentinelContent(response);
404
468
  if (sentinelContent) content = sentinelContent;
405
- return `{% number-field ${attrStr} %}${content}{% /number-field %}`;
469
+ return `{% field ${attrStr} %}${content}{% /field %}`;
406
470
  }
407
471
  /**
408
472
  * Serialize a string-list field.
409
473
  */
410
474
  function serializeStringListField(field, response) {
411
475
  const attrs = {
476
+ kind: "string_list",
412
477
  id: field.id,
413
478
  label: field.label
414
479
  };
@@ -422,6 +487,8 @@ function serializeStringListField(field, response) {
422
487
  if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
423
488
  if (field.validate) attrs.validate = field.validate;
424
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;
425
492
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
426
493
  const attrStr = serializeAttrs(attrs);
427
494
  let content = "";
@@ -431,7 +498,7 @@ function serializeStringListField(field, response) {
431
498
  }
432
499
  const sentinelContent = getSentinelContent(response);
433
500
  if (sentinelContent) content = sentinelContent;
434
- return `{% string-list ${attrStr} %}${content}{% /string-list %}`;
501
+ return `{% field ${attrStr} %}${content}{% /field %}`;
435
502
  }
436
503
  /**
437
504
  * Serialize options (for single-select, multi-select, checkboxes).
@@ -449,6 +516,7 @@ function serializeOptions(options, selected) {
449
516
  */
450
517
  function serializeSingleSelectField(field, response) {
451
518
  const attrs = {
519
+ kind: "single_select",
452
520
  id: field.id,
453
521
  label: field.label
454
522
  };
@@ -463,13 +531,14 @@ function serializeSingleSelectField(field, response) {
463
531
  if (response?.state === "answered" && response.value) value = response.value;
464
532
  const selected = {};
465
533
  for (const opt of field.options) selected[opt.id] = opt.id === value?.selected ? "done" : "todo";
466
- return `{% single-select ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /single-select %}`;
534
+ return `{% field ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /field %}`;
467
535
  }
468
536
  /**
469
537
  * Serialize a multi-select field.
470
538
  */
471
539
  function serializeMultiSelectField(field, response) {
472
540
  const attrs = {
541
+ kind: "multi_select",
473
542
  id: field.id,
474
543
  label: field.label
475
544
  };
@@ -487,13 +556,14 @@ function serializeMultiSelectField(field, response) {
487
556
  const selected = {};
488
557
  const selectedSet = new Set(value?.selected ?? []);
489
558
  for (const opt of field.options) selected[opt.id] = selectedSet.has(opt.id) ? "done" : "todo";
490
- return `{% multi-select ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /multi-select %}`;
559
+ return `{% field ${attrStr} %}\n${serializeOptions(field.options, selected)}\n{% /field %}`;
491
560
  }
492
561
  /**
493
562
  * Serialize a checkboxes field.
494
563
  */
495
564
  function serializeCheckboxesField(field, response) {
496
565
  const attrs = {
566
+ kind: "checkboxes",
497
567
  id: field.id,
498
568
  label: field.label
499
569
  };
@@ -509,13 +579,14 @@ function serializeCheckboxesField(field, response) {
509
579
  const attrStr = serializeAttrs(attrs);
510
580
  let value;
511
581
  if (response?.state === "answered" && response.value) value = response.value;
512
- return `{% checkboxes ${attrStr} %}\n${serializeOptions(field.options, value?.values ?? {})}\n{% /checkboxes %}`;
582
+ return `{% field ${attrStr} %}\n${serializeOptions(field.options, value?.values ?? {})}\n{% /field %}`;
513
583
  }
514
584
  /**
515
585
  * Serialize a url-field.
516
586
  */
517
587
  function serializeUrlField(field, response) {
518
588
  const attrs = {
589
+ kind: "url",
519
590
  id: field.id,
520
591
  label: field.label
521
592
  };
@@ -524,6 +595,8 @@ function serializeUrlField(field, response) {
524
595
  if (field.role !== AGENT_ROLE) attrs.role = field.role;
525
596
  if (field.validate) attrs.validate = field.validate;
526
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;
527
600
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
528
601
  const attrStr = serializeAttrs(attrs);
529
602
  let content = "";
@@ -533,13 +606,14 @@ function serializeUrlField(field, response) {
533
606
  }
534
607
  const sentinelContent = getSentinelContent(response);
535
608
  if (sentinelContent) content = sentinelContent;
536
- return `{% url-field ${attrStr} %}${content}{% /url-field %}`;
609
+ return `{% field ${attrStr} %}${content}{% /field %}`;
537
610
  }
538
611
  /**
539
612
  * Serialize a url-list field.
540
613
  */
541
614
  function serializeUrlListField(field, response) {
542
615
  const attrs = {
616
+ kind: "url_list",
543
617
  id: field.id,
544
618
  label: field.label
545
619
  };
@@ -551,6 +625,8 @@ function serializeUrlListField(field, response) {
551
625
  if (field.uniqueItems) attrs.uniqueItems = field.uniqueItems;
552
626
  if (field.validate) attrs.validate = field.validate;
553
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;
554
630
  if (response?.state === "skipped" || response?.state === "aborted") attrs.state = response.state;
555
631
  const attrStr = serializeAttrs(attrs);
556
632
  let content = "";
@@ -560,13 +636,14 @@ function serializeUrlListField(field, response) {
560
636
  }
561
637
  const sentinelContent = getSentinelContent(response);
562
638
  if (sentinelContent) content = sentinelContent;
563
- return `{% url-list ${attrStr} %}${content}{% /url-list %}`;
639
+ return `{% field ${attrStr} %}${content}{% /field %}`;
564
640
  }
565
641
  /**
566
642
  * Serialize a date-field.
567
643
  */
568
644
  function serializeDateField(field, response) {
569
645
  const attrs = {
646
+ kind: "date",
570
647
  id: field.id,
571
648
  label: field.label
572
649
  };
@@ -586,13 +663,14 @@ function serializeDateField(field, response) {
586
663
  }
587
664
  const sentinelContent = getSentinelContent(response);
588
665
  if (sentinelContent) content = sentinelContent;
589
- return `{% date-field ${attrStr} %}${content}{% /date-field %}`;
666
+ return `{% field ${attrStr} %}${content}{% /field %}`;
590
667
  }
591
668
  /**
592
669
  * Serialize a year-field.
593
670
  */
594
671
  function serializeYearField(field, response) {
595
672
  const attrs = {
673
+ kind: "year",
596
674
  id: field.id,
597
675
  label: field.label
598
676
  };
@@ -612,7 +690,74 @@ function serializeYearField(field, response) {
612
690
  }
613
691
  const sentinelContent = getSentinelContent(response);
614
692
  if (sentinelContent) content = sentinelContent;
615
- 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 %}`;
616
761
  }
617
762
  /**
618
763
  * Serialize a field to Markdoc format.
@@ -630,6 +775,7 @@ function serializeField(field, responses) {
630
775
  case "url_list": return serializeUrlListField(field, response);
631
776
  case "date": return serializeDateField(field, response);
632
777
  case "year": return serializeYearField(field, response);
778
+ case "table": return serializeTableField(field, response);
633
779
  }
634
780
  }
635
781
  /**
@@ -664,13 +810,19 @@ function serializeNotes(notes) {
664
810
  }
665
811
  /**
666
812
  * Serialize a field group.
813
+ * Implicit groups (fields placed directly under the form) are serialized
814
+ * without the group wrapper tags.
667
815
  */
668
816
  function serializeFieldGroup(group, responses, docs) {
669
- const attrs = { id: group.id };
670
- if (group.title) attrs.title = group.title;
671
- if (group.validate) attrs.validate = group.validate;
672
- if (group.report !== void 0) attrs.report = group.report;
673
- 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(`{% group ${attrStr} %}`);
825
+ }
674
826
  const docsByRef = /* @__PURE__ */ new Map();
675
827
  for (const doc of docs) {
676
828
  const list = docsByRef.get(doc.ref) ?? [];
@@ -686,8 +838,10 @@ function serializeFieldGroup(group, responses, docs) {
686
838
  lines.push(serializeDocBlock(doc));
687
839
  }
688
840
  }
689
- lines.push("");
690
- lines.push("{% /field-group %}");
841
+ if (!group.implicit) {
842
+ lines.push("");
843
+ lines.push("{% /group %}");
844
+ }
691
845
  return lines.join("\n");
692
846
  }
693
847
  /**
@@ -819,6 +973,12 @@ function serializeFieldRaw(field, responses) {
819
973
  else lines.push("_(empty)_");
820
974
  break;
821
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
+ }
822
982
  }
823
983
  return lines.join("\n");
824
984
  }
@@ -953,14 +1113,17 @@ function computeStructureSummary(schema) {
953
1113
  url: 0,
954
1114
  url_list: 0,
955
1115
  date: 0,
956
- year: 0
1116
+ year: 0,
1117
+ table: 0
957
1118
  };
958
1119
  const groupsById = {};
959
1120
  const fieldsById = {};
960
1121
  const optionsById = {};
1122
+ const columnsById = {};
961
1123
  let groupCount = 0;
962
1124
  let fieldCount = 0;
963
1125
  let optionCount = 0;
1126
+ let columnCount = 0;
964
1127
  for (const group of schema.groups) {
965
1128
  groupCount++;
966
1129
  groupsById[group.id] = "field_group";
@@ -976,16 +1139,26 @@ function computeStructureSummary(schema) {
976
1139
  parentFieldKind: field.kind
977
1140
  };
978
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
+ }
979
1150
  }
980
1151
  }
981
1152
  return {
982
1153
  groupCount,
983
1154
  fieldCount,
984
1155
  optionCount,
1156
+ columnCount,
985
1157
  fieldCountByKind,
986
1158
  groupsById,
987
1159
  fieldsById,
988
- optionsById
1160
+ optionsById,
1161
+ columnsById
989
1162
  };
990
1163
  }
991
1164
  /**
@@ -1024,6 +1197,7 @@ function isFieldSubmitted(field, value) {
1024
1197
  return v.value !== null && v.value.trim() !== "";
1025
1198
  }
1026
1199
  case "year": return value.value !== null;
1200
+ case "table": return value.rows.length > 0;
1027
1201
  }
1028
1202
  }
1029
1203
  /**
@@ -1629,6 +1803,116 @@ function validateYearField(field, value) {
1629
1803
  return issues;
1630
1804
  }
1631
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
+ /**
1632
1916
  * Validate a single field.
1633
1917
  */
1634
1918
  function validateField(field, responses) {
@@ -1645,6 +1929,7 @@ function validateField(field, responses) {
1645
1929
  case "url_list": return validateUrlListField(field, value);
1646
1930
  case "date": return validateDateField(field, value);
1647
1931
  case "year": return validateYearField(field, value);
1932
+ case "table": return validateTableField(field, value);
1648
1933
  }
1649
1934
  }
1650
1935
  /**
@@ -2164,6 +2449,19 @@ function validatePatch(form, patch, index) {
2164
2449
  message: `Cannot apply set_year to ${field.kind} field "${field.id}"`
2165
2450
  };
2166
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
+ }
2167
2465
  case "clear_field": break;
2168
2466
  case "skip_field":
2169
2467
  if (field.required) return {
@@ -2323,6 +2621,50 @@ function applySetYear(responses, patch) {
2323
2621
  };
2324
2622
  }
2325
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
+ /**
2326
2668
  * Apply a clear_field patch.
2327
2669
  */
2328
2670
  function applyClearField(responses, patch) {
@@ -2404,6 +2746,9 @@ function applyPatch(form, responses, patch) {
2404
2746
  case "set_year":
2405
2747
  applySetYear(responses, patch);
2406
2748
  break;
2749
+ case "set_table":
2750
+ applySetTable(responses, patch);
2751
+ break;
2407
2752
  case "clear_field":
2408
2753
  applyClearField(responses, patch);
2409
2754
  break;
@@ -2479,4 +2824,4 @@ function applyPatches(form, patches) {
2479
2824
  }
2480
2825
 
2481
2826
  //#endregion
2482
- export { parseRolesFlag as A, DEFAULT_ROLE_INSTRUCTIONS as C, deriveReportPath as D, deriveExportPath as E, hasWebSearchSupport as F, 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 };
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 };
package/dist/bin.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import "./coreTypes-Dful87E0.mjs";
3
- import "./apply-00UmzDKL.mjs";
4
- import "./src-Dm8jZ5dl.mjs";
5
- import "./session-Bqnwi9wp.mjs";
6
- import "./shared-N_s1M-_K.mjs";
7
- import { t as runCli } from "./cli-D--Lel-e.mjs";
2
+ import "./coreTypes-pyctKRgc.mjs";
3
+ import "./apply-BCCiJzQr.mjs";
4
+ import "./src-Df0XX7UB.mjs";
5
+ import "./session-uF0e6m6k.mjs";
6
+ import "./shared-BqPnYXrn.mjs";
7
+ import { t as runCli } from "./cli-D469amuk.mjs";
8
8
  import { resolve } from "node:path";
9
9
  import { existsSync } from "node:fs";
10
10
  import { config } from "dotenv";