@case-framework/survey-assistant 0.2.0 → 0.2.1

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 (71) hide show
  1. package/dist/{capabilities-DZ0a_o2y.mjs → capabilities-DKbNCnBf.mjs} +2 -2
  2. package/dist/{capabilities-DZ0a_o2y.mjs.map → capabilities-DKbNCnBf.mjs.map} +1 -1
  3. package/dist/capabilities-default.d.mts +1 -1
  4. package/dist/capabilities-default.mjs +1 -1
  5. package/dist/{constants-56jf_4ls.d.mts → constants-CgV4cTg5.d.mts} +7 -7
  6. package/dist/constants-CgV4cTg5.d.mts.map +1 -0
  7. package/dist/{controller-proxy-ClsY4lFI.mjs → controller-proxy-0WJZUZYx.mjs} +2 -2
  8. package/dist/{controller-proxy-ClsY4lFI.mjs.map → controller-proxy-0WJZUZYx.mjs.map} +1 -1
  9. package/dist/{controller-proxy-2VH0b_5E.d.mts → controller-proxy-BzpEOygv.d.mts} +2 -2
  10. package/dist/{controller-proxy-2VH0b_5E.d.mts.map → controller-proxy-BzpEOygv.d.mts.map} +1 -1
  11. package/dist/{engine-DQrCO_ur.mjs → engine-0AJrv0hI.mjs} +543 -76
  12. package/dist/engine-0AJrv0hI.mjs.map +1 -0
  13. package/dist/engine.d.mts +2 -2
  14. package/dist/engine.mjs +3 -3
  15. package/dist/{index-C_x686M7.d.mts → index-BNdvGEaq.d.mts} +3 -3
  16. package/dist/{index-C_x686M7.d.mts.map → index-BNdvGEaq.d.mts.map} +1 -1
  17. package/dist/{index-B_x2IYi7.d.mts → index-BUF-sE8j.d.mts} +1 -10
  18. package/dist/index-BUF-sE8j.d.mts.map +1 -0
  19. package/dist/{index-DZ6KBwVR.d.mts → index-BdXb6jER.d.mts} +45 -14
  20. package/dist/index-BdXb6jER.d.mts.map +1 -0
  21. package/dist/{index-DVxVyhAu.d.mts → index-BiTT71Xp.d.mts} +1 -9
  22. package/dist/{index-DVxVyhAu.d.mts.map → index-BiTT71Xp.d.mts.map} +1 -1
  23. package/dist/{memory-thread-repository-Cm1BJyRw.mjs → memory-thread-repository-1ocPOda8.mjs} +17 -17
  24. package/dist/memory-thread-repository-1ocPOda8.mjs.map +1 -0
  25. package/dist/{protocol-7EEgcYja.mjs → protocol-DpCYmCjr.mjs} +1 -8
  26. package/dist/{protocol-7EEgcYja.mjs.map → protocol-DpCYmCjr.mjs.map} +1 -1
  27. package/dist/protocol.d.mts +1 -1
  28. package/dist/protocol.mjs +1 -1
  29. package/dist/{react-B_5smfiK.mjs → react-DXK5zWNo.mjs} +3 -3
  30. package/dist/{react-B_5smfiK.mjs.map → react-DXK5zWNo.mjs.map} +1 -1
  31. package/dist/react-integration.d.mts +1 -1
  32. package/dist/react-integration.mjs +1 -1
  33. package/dist/react.d.mts +2 -2
  34. package/dist/react.mjs +2 -2
  35. package/dist/references/assistant-operations.md +9 -14
  36. package/dist/references/localization.md +2 -0
  37. package/dist/references/source-material-surveys.md +1 -1
  38. package/dist/{request-context-BDBw4-Gw.mjs → request-context-BBzU3VdG.mjs} +17 -10
  39. package/dist/request-context-BBzU3VdG.mjs.map +1 -0
  40. package/dist/server-agent.d.mts +2 -10
  41. package/dist/server-agent.mjs +13 -13
  42. package/dist/server-agent.mjs.map +1 -1
  43. package/dist/server-runtime.d.mts +4 -4
  44. package/dist/server-runtime.mjs +2 -2
  45. package/dist/server-tasks.d.mts +1 -1
  46. package/dist/server-tasks.mjs +1 -1
  47. package/dist/server-tools.d.mts +1 -1
  48. package/dist/server-tools.mjs +1 -1
  49. package/dist/server.d.mts +6 -6
  50. package/dist/server.d.mts.map +1 -1
  51. package/dist/server.mjs +2 -2
  52. package/dist/{tasks-COQnhSeT.d.mts → tasks-BuLnWrR4.d.mts} +3 -3
  53. package/dist/{tasks-COQnhSeT.d.mts.map → tasks-BuLnWrR4.d.mts.map} +1 -1
  54. package/dist/thread-documents-BlxNjc7S.d.mts.map +1 -1
  55. package/dist/{thread-handlers-l79JBl5m.d.mts → thread-handlers-6L8sAreR.d.mts} +3 -3
  56. package/dist/{thread-handlers-l79JBl5m.d.mts.map → thread-handlers-6L8sAreR.d.mts.map} +1 -1
  57. package/dist/{tools-DDXaJ8Za.mjs → tools-wgLen05n.mjs} +97 -103
  58. package/dist/tools-wgLen05n.mjs.map +1 -0
  59. package/dist/ui.css +1 -1
  60. package/dist/ui.d.mts +3 -3
  61. package/dist/ui.d.mts.map +1 -1
  62. package/dist/ui.mjs +36 -6
  63. package/dist/ui.mjs.map +1 -1
  64. package/package.json +1 -1
  65. package/dist/constants-56jf_4ls.d.mts.map +0 -1
  66. package/dist/engine-DQrCO_ur.mjs.map +0 -1
  67. package/dist/index-B_x2IYi7.d.mts.map +0 -1
  68. package/dist/index-DZ6KBwVR.d.mts.map +0 -1
  69. package/dist/memory-thread-repository-Cm1BJyRw.mjs.map +0 -1
  70. package/dist/request-context-BDBw4-Gw.mjs.map +0 -1
  71. package/dist/tools-DDXaJ8Za.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
- import { P as rawSurveyDocumentSchema, c as surveyAssistantTurnFinalSchema, u as surveyChangeSetSchema } from "./protocol-7EEgcYja.mjs";
2
- import { n as assertSurveyAssistantCapabilities } from "./capabilities-DZ0a_o2y.mjs";
1
+ import { P as rawSurveyDocumentSchema, c as surveyAssistantTurnFinalSchema, u as surveyChangeSetSchema } from "./protocol-DpCYmCjr.mjs";
2
+ import { n as assertSurveyAssistantCapabilities } from "./capabilities-DKbNCnBf.mjs";
3
3
  import { z } from "zod";
4
- import { ContentType, Expression, ExpressionType, FunctionExpressionNames, JsonPatchError, ReservedSurveyItemTypes, SurveyTranslations, ValueReferenceMethod, ValueType, applyJsonPatch, generateId, getContentPlainText, isContentEmpty, parseJsonPointer, structuredCloneMethod } from "@case-framework/survey-core";
4
+ import { ContentType, Expression, ExpressionType, FunctionExpressionNames, JsonPatchError, ReservedSurveyItemTypes, SurveyItemTranslations, SurveyTranslations, ValueReferenceMethod, ValueType, applyJsonPatch, generateId, getContentPlainText, isContentEmpty, parseJsonPointer, structuredCloneMethod } from "@case-framework/survey-core";
5
5
  //#region src/engine/context.ts
6
6
  const isRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7
7
  const asString$1 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
@@ -1938,6 +1938,7 @@ const normalizeSurveyChangeOperations = (operations, defaultLocale) => operation
1938
1938
  //#region src/engine/operations.ts
1939
1939
  const ITEM_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
1940
1940
  const MAX_SURVEY_ASSISTANT_OPERATIONS_PER_PROPOSAL = 200;
1941
+ const MAX_SURVEY_ASSISTANT_OPERATIONS_PER_TURN_DRAFT = 1e3;
1941
1942
  const CHOICE_ITEM_TYPE = "choiceItem";
1942
1943
  const FORM_ITEM_TYPE = "formItem";
1943
1944
  const CHOICE_OPTION_LABEL_CONTENT_KEY_PATTERN = /^options\.([^.]+)\.label$/;
@@ -3370,7 +3371,7 @@ const getDeletedAncestor = (itemId, parentByChildId, deletedItemIds) => {
3370
3371
  };
3371
3372
  const buildSurveyDocumentVersion = (snapshot) => ({ ...snapshot.version });
3372
3373
  const resolveEngineContext = (snapshot) => "document" in snapshot ? snapshot : createSurveyAssistantEngineContext(snapshot);
3373
- const validateSurveyOperations = ({ operations, snapshot: snapshotInput }) => {
3374
+ const validateSurveyOperations = ({ maxOperations = 200, operations, snapshot: snapshotInput }) => {
3374
3375
  if (!snapshotInput) return createEmptyValidationResult(createIssue({
3375
3376
  code: "missing-snapshot",
3376
3377
  message: "No live editor snapshot was provided for this assistant turn."
@@ -3380,9 +3381,9 @@ const validateSurveyOperations = ({ operations, snapshot: snapshotInput }) => {
3380
3381
  code: "empty-operations",
3381
3382
  message: "No survey change operations were provided."
3382
3383
  }), operations);
3383
- if (operations.length > 200) return createEmptyValidationResult(createIssue({
3384
+ if (operations.length > maxOperations) return createEmptyValidationResult(createIssue({
3384
3385
  code: "too-many-operations",
3385
- message: `At most 200 operations can be proposed at once.`
3386
+ message: `At most ${maxOperations} operations can be validated in this change set.`
3386
3387
  }), operations);
3387
3388
  const surveyJsonPatchOperations = operations.filter(isSurveyJsonPatchOperation);
3388
3389
  if (surveyJsonPatchOperations.length > 0) {
@@ -4558,8 +4559,421 @@ const validateSurveyOperations = ({ operations, snapshot: snapshotInput }) => {
4558
4559
  normalizedOperations
4559
4560
  };
4560
4561
  };
4561
- const createSurveyChangeSet = ({ operations, snapshot, summary, turnId }) => {
4562
+ const buildDraftContent = ({ content, plainText }) => content ? structuredCloneMethod(content) : {
4563
+ type: ContentType.md,
4564
+ content: plainText ?? ""
4565
+ };
4566
+ const updateDraftItemTranslations = ({ itemId, survey, translations }) => {
4567
+ if (!translations?.length) return;
4568
+ const surveyTranslations = new SurveyTranslations(survey.translations);
4569
+ const itemTranslations = surveyTranslations.getItemTranslations(itemId)?.clone() ?? new SurveyItemTranslations();
4570
+ for (const translation of translations) itemTranslations.setContent(translation.locale, translation.contentKey, buildDraftContent(translation));
4571
+ surveyTranslations.setItemTranslations(itemId, itemTranslations);
4572
+ survey.translations = surveyTranslations.serialize();
4573
+ };
4574
+ const removeDraftTranslationPrefix = ({ itemId, prefix, survey }) => {
4575
+ const surveyTranslations = new SurveyTranslations(survey.translations);
4576
+ const itemTranslations = surveyTranslations.getItemTranslations(itemId)?.clone();
4577
+ if (!itemTranslations) return;
4578
+ itemTranslations.removeContentKeysWithPrefix(prefix);
4579
+ surveyTranslations.setItemTranslations(itemId, itemTranslations.compact());
4580
+ survey.translations = surveyTranslations.serialize();
4581
+ };
4582
+ /**
4583
+ * Applies already-normalized, validated operations to a request-local raw
4584
+ * survey. This is simulation only; the editor remains the final mutation and
4585
+ * undo/redo owner.
4586
+ */
4587
+ const applyValidatedSurveyOperationsToDraft = ({ operations, survey: inputSurvey }) => {
4588
+ let survey = structuredCloneMethod(inputSurvey);
4589
+ const getItem = (itemId) => {
4590
+ const item = survey.surveyItems.find((candidate) => candidate.id === itemId);
4591
+ if (!item) throw new Error(`Draft item "${itemId}" was not found.`);
4592
+ return item;
4593
+ };
4594
+ const replaceItem = (item) => {
4595
+ const index = survey.surveyItems.findIndex((candidate) => candidate.id === item.id);
4596
+ if (index < 0) throw new Error(`Draft item "${item.id}" was not found.`);
4597
+ survey.surveyItems[index] = item;
4598
+ };
4599
+ for (const operation of operations) {
4600
+ if (operation.kind === "survey-json-patch") {
4601
+ survey = applyJsonPatch(survey, operation.patches);
4602
+ continue;
4603
+ }
4604
+ switch (operation.kind) {
4605
+ case "create-item": {
4606
+ const item = structuredCloneMethod(operation.item);
4607
+ survey.surveyItems.push(item);
4608
+ const parent = getItem(operation.target.parentItemId);
4609
+ const children = getGroupChildren(parent);
4610
+ children.splice(Math.min(operation.target.index ?? children.length, children.length), 0, item.id);
4611
+ replaceItem({
4612
+ ...parent,
4613
+ config: {
4614
+ ...isRecord$2(parent.config) ? parent.config : {},
4615
+ items: children
4616
+ }
4617
+ });
4618
+ updateDraftItemTranslations({
4619
+ itemId: item.id,
4620
+ survey,
4621
+ translations: operation.translations
4622
+ });
4623
+ break;
4624
+ }
4625
+ case "update-item-key":
4626
+ replaceItem({
4627
+ ...getItem(operation.itemId),
4628
+ key: operation.key
4629
+ });
4630
+ break;
4631
+ case "update-item-label": {
4632
+ const item = getItem(operation.itemId);
4633
+ const metadata = { ...item.metadata ?? {} };
4634
+ if (operation.itemLabel) metadata.itemLabel = operation.itemLabel;
4635
+ else delete metadata.itemLabel;
4636
+ replaceItem({
4637
+ ...item,
4638
+ metadata: Object.keys(metadata).length ? metadata : void 0
4639
+ });
4640
+ break;
4641
+ }
4642
+ case "update-item-color": {
4643
+ const item = getItem(operation.itemId);
4644
+ replaceItem({
4645
+ ...item,
4646
+ metadata: {
4647
+ ...item.metadata ?? {},
4648
+ editorItemColor: operation.color
4649
+ }
4650
+ });
4651
+ break;
4652
+ }
4653
+ case "update-item-translation":
4654
+ updateDraftItemTranslations({
4655
+ itemId: operation.itemId,
4656
+ survey,
4657
+ translations: [operation]
4658
+ });
4659
+ break;
4660
+ case "update-placed-image": {
4661
+ const surveyTranslations = new SurveyTranslations(survey.translations);
4662
+ const itemTranslations = surveyTranslations.getItemTranslations(operation.itemId)?.clone() ?? new SurveyItemTranslations();
4663
+ const content = itemTranslations.getContent(operation.locale, operation.contentKey);
4664
+ if (content?.type !== ContentType.richText) throw new Error("Draft image placement is not rich text.");
4665
+ let imageIndex = -1;
4666
+ const blocks = content.doc.blocks.map((block) => {
4667
+ if (block.type !== "image") return block;
4668
+ imageIndex += 1;
4669
+ if (imageIndex !== operation.imageIndex) return block;
4670
+ const next = structuredCloneMethod(block);
4671
+ const changes = operation.changes;
4672
+ if (changes.source !== void 0) next.source = changes.source;
4673
+ if (changes.alt !== void 0) if (changes.alt === null) delete next.alt;
4674
+ else next.alt = changes.alt;
4675
+ if (changes.caption !== void 0) if (changes.caption === null || changes.caption.length === 0) delete next.caption;
4676
+ else next.caption = [{
4677
+ type: "paragraph",
4678
+ children: [{
4679
+ type: "text",
4680
+ text: changes.caption
4681
+ }]
4682
+ }];
4683
+ const size = { ...next.size ?? {} };
4684
+ for (const key of [
4685
+ "width",
4686
+ "height",
4687
+ "maxWidth",
4688
+ "maxHeight"
4689
+ ]) {
4690
+ const value = changes[key];
4691
+ if (value === null) delete size[key];
4692
+ else if (value !== void 0) size[key] = value;
4693
+ }
4694
+ if (Object.keys(size).length) next.size = size;
4695
+ else delete next.size;
4696
+ if (changes.alignment === null) delete next.alignment;
4697
+ else if (changes.alignment !== void 0) next.alignment = changes.alignment;
4698
+ return next;
4699
+ });
4700
+ itemTranslations.setContent(operation.locale, operation.contentKey, {
4701
+ ...content,
4702
+ doc: {
4703
+ ...content.doc,
4704
+ blocks
4705
+ }
4706
+ });
4707
+ surveyTranslations.setItemTranslations(operation.itemId, itemTranslations);
4708
+ survey.translations = surveyTranslations.serialize();
4709
+ break;
4710
+ }
4711
+ case "patch-item-config": {
4712
+ const item = getItem(operation.itemId);
4713
+ replaceItem({
4714
+ ...item,
4715
+ config: applyJsonPatch(item.config ?? {}, operation.patches)
4716
+ });
4717
+ break;
4718
+ }
4719
+ case "add-choice-option": {
4720
+ const item = getItem(operation.itemId);
4721
+ const options = getChoiceOptionsFromRawItem(item);
4722
+ options.splice(operation.index ?? options.length, 0, {
4723
+ id: operation.option.id,
4724
+ ...operation.option.key ? { key: operation.option.key } : {}
4725
+ });
4726
+ replaceItem(withChoiceOptionsOnRawItem(item, options));
4727
+ updateDraftItemTranslations({
4728
+ itemId: item.id,
4729
+ survey,
4730
+ translations: [{
4731
+ locale: operation.locale ?? "en",
4732
+ contentKey: createChoiceOptionLabelContentKey(operation.option.id),
4733
+ plainText: operation.option.label
4734
+ }]
4735
+ });
4736
+ break;
4737
+ }
4738
+ case "update-choice-option-label":
4739
+ updateDraftItemTranslations({
4740
+ itemId: operation.itemId,
4741
+ survey,
4742
+ translations: [{
4743
+ locale: operation.locale ?? "en",
4744
+ contentKey: createChoiceOptionLabelContentKey(operation.optionId),
4745
+ plainText: operation.label
4746
+ }]
4747
+ });
4748
+ break;
4749
+ case "remove-choice-option": {
4750
+ const item = getItem(operation.itemId);
4751
+ replaceItem(withChoiceOptionsOnRawItem(item, getChoiceOptionsFromRawItem(item).filter((option) => option.id !== operation.optionId)));
4752
+ removeDraftTranslationPrefix({
4753
+ itemId: item.id,
4754
+ prefix: `options.${operation.optionId}`,
4755
+ survey
4756
+ });
4757
+ removeDraftTranslationPrefix({
4758
+ itemId: item.id,
4759
+ prefix: `embeddedForms.${operation.optionId}`,
4760
+ survey
4761
+ });
4762
+ break;
4763
+ }
4764
+ case "reorder-choice-options": {
4765
+ const item = getItem(operation.itemId);
4766
+ const options = new Map(getChoiceOptionsFromRawItem(item).map((option) => [option.id, option]));
4767
+ replaceItem(withChoiceOptionsOnRawItem(item, operation.optionIds.map((optionId) => options.get(optionId))));
4768
+ break;
4769
+ }
4770
+ case "set-embedded-form":
4771
+ case "remove-embedded-form": {
4772
+ const item = getItem(operation.itemId);
4773
+ const options = getChoiceOptionsFromRawItem(item);
4774
+ const optionIndex = options.findIndex((option) => option.id === operation.optionId);
4775
+ const option = { ...options[optionIndex] };
4776
+ if (operation.kind === "set-embedded-form") option.embeddedForm = operation.form;
4777
+ else {
4778
+ delete option.embeddedForm;
4779
+ delete option.embeddedFormItemId;
4780
+ }
4781
+ options[optionIndex] = option;
4782
+ replaceItem(withChoiceOptionsOnRawItem(item, options));
4783
+ if (operation.kind === "set-embedded-form") updateDraftItemTranslations({
4784
+ itemId: item.id,
4785
+ survey,
4786
+ translations: operation.translations
4787
+ });
4788
+ else removeDraftTranslationPrefix({
4789
+ itemId: item.id,
4790
+ prefix: `embeddedForms.${operation.optionId}`,
4791
+ survey
4792
+ });
4793
+ break;
4794
+ }
4795
+ case "upsert-form-field-group":
4796
+ case "remove-form-field-group":
4797
+ case "reorder-form-field-groups":
4798
+ case "upsert-form-field":
4799
+ case "remove-form-field":
4800
+ case "move-form-field": {
4801
+ const item = getItem(operation.target.itemId);
4802
+ let groups = getFormFieldGroupsForTarget(item, operation.target.optionId) ?? [];
4803
+ groups = groups.map((group) => ({
4804
+ ...group,
4805
+ fields: [...group.fields]
4806
+ }));
4807
+ if (operation.kind === "upsert-form-field-group") {
4808
+ const index = groups.findIndex((group) => group.id === operation.group.id);
4809
+ const group = normalizeFormFieldGroupRecord({
4810
+ ...index >= 0 ? groups[index] : {},
4811
+ ...operation.group
4812
+ });
4813
+ if (index >= 0) groups[index] = group;
4814
+ else groups.splice(operation.index ?? groups.length, 0, group);
4815
+ updateDraftItemTranslations({
4816
+ itemId: item.id,
4817
+ survey,
4818
+ translations: operation.translations
4819
+ });
4820
+ } else if (operation.kind === "remove-form-field-group") {
4821
+ groups = groups.filter((group) => group.id !== operation.groupId);
4822
+ removeDraftTranslationPrefix({
4823
+ itemId: item.id,
4824
+ prefix: `${operation.target.optionId ? `embeddedForms.${operation.target.optionId}.` : ""}fieldGroups.${operation.groupId}`,
4825
+ survey
4826
+ });
4827
+ } else if (operation.kind === "reorder-form-field-groups") {
4828
+ const byId = new Map(groups.map((group) => [group.id, group]));
4829
+ groups = operation.groupIds.map((groupId) => byId.get(groupId));
4830
+ } else if (operation.kind === "upsert-form-field") {
4831
+ let field;
4832
+ for (const group of groups) {
4833
+ const index = group.fields.findIndex((candidate) => candidate.id === operation.field.id);
4834
+ if (index >= 0) [field] = group.fields.splice(index, 1);
4835
+ }
4836
+ const group = groups.find((candidate) => candidate.id === operation.groupId);
4837
+ group.fields.splice(operation.index ?? group.fields.length, 0, {
4838
+ ...field,
4839
+ ...operation.field
4840
+ });
4841
+ updateDraftItemTranslations({
4842
+ itemId: item.id,
4843
+ survey,
4844
+ translations: operation.translations
4845
+ });
4846
+ } else {
4847
+ let field;
4848
+ for (const group of groups) {
4849
+ const index = group.fields.findIndex((candidate) => candidate.id === operation.fieldId);
4850
+ if (index >= 0) [field] = group.fields.splice(index, 1);
4851
+ }
4852
+ if (operation.kind === "move-form-field") {
4853
+ const group = groups.find((candidate) => candidate.id === operation.targetGroupId);
4854
+ group.fields.splice(operation.index ?? group.fields.length, 0, field);
4855
+ } else removeDraftTranslationPrefix({
4856
+ itemId: item.id,
4857
+ prefix: `${operation.target.optionId ? `embeddedForms.${operation.target.optionId}.` : ""}fields.${operation.fieldId}`,
4858
+ survey
4859
+ });
4860
+ }
4861
+ replaceItem(withFormFieldGroupsForTarget(item, groups, operation.target.optionId));
4862
+ break;
4863
+ }
4864
+ case "remove-item": {
4865
+ const removeIds = collectDescendantIds(operation.itemId, new Map(survey.surveyItems.map((item) => [item.id, item])));
4866
+ removeIds.add(operation.itemId);
4867
+ survey.surveyItems = survey.surveyItems.filter((item) => !removeIds.has(item.id)).map((item) => ({
4868
+ ...item,
4869
+ config: isRecord$2(item.config) ? {
4870
+ ...item.config,
4871
+ items: getGroupChildren(item).filter((id) => !removeIds.has(id))
4872
+ } : item.config
4873
+ }));
4874
+ const translations = new SurveyTranslations(survey.translations);
4875
+ for (const itemId of removeIds) translations.setItemTranslations(itemId, void 0);
4876
+ survey.translations = translations.serialize();
4877
+ break;
4878
+ }
4879
+ case "move-item": {
4880
+ const itemId = operation.itemId;
4881
+ for (const item of survey.surveyItems) {
4882
+ const children = getGroupChildren(item);
4883
+ if (!children.includes(itemId)) continue;
4884
+ replaceItem({
4885
+ ...item,
4886
+ config: {
4887
+ ...isRecord$2(item.config) ? item.config : {},
4888
+ items: children.filter((id) => id !== itemId)
4889
+ }
4890
+ });
4891
+ }
4892
+ const parent = getItem(operation.target.parentItemId);
4893
+ const children = getGroupChildren(parent);
4894
+ children.splice(operation.target.index ?? children.length, 0, itemId);
4895
+ replaceItem({
4896
+ ...parent,
4897
+ config: {
4898
+ ...isRecord$2(parent.config) ? parent.config : {},
4899
+ items: children
4900
+ }
4901
+ });
4902
+ break;
4903
+ }
4904
+ case "reorder-items": {
4905
+ const parent = getItem(operation.parentItemId);
4906
+ replaceItem({
4907
+ ...parent,
4908
+ config: {
4909
+ ...isRecord$2(parent.config) ? parent.config : {},
4910
+ items: operation.itemIds
4911
+ }
4912
+ });
4913
+ break;
4914
+ }
4915
+ case "update-survey-metadata": {
4916
+ const metadata = { ...survey.metadata ?? {} };
4917
+ for (const [key, value] of Object.entries(operation.values)) if (value === null) delete metadata[key];
4918
+ else metadata[key] = value;
4919
+ survey.metadata = Object.keys(metadata).length ? metadata : void 0;
4920
+ break;
4921
+ }
4922
+ case "update-survey-pagination":
4923
+ survey.maxItemsPerPage = operation.value ?? void 0;
4924
+ break;
4925
+ case "update-survey-translation": {
4926
+ const locale = survey.translations?.[operation.locale] ?? {};
4927
+ const surface = { ...locale[operation.surface] ?? {} };
4928
+ surface[operation.contentKey] = buildDraftContent(operation);
4929
+ survey.translations = {
4930
+ ...survey.translations ?? {},
4931
+ [operation.locale]: {
4932
+ ...locale,
4933
+ [operation.surface]: surface
4934
+ }
4935
+ };
4936
+ break;
4937
+ }
4938
+ case "remove-survey-locale": {
4939
+ const translations = { ...survey.translations ?? {} };
4940
+ delete translations[operation.locale];
4941
+ survey.translations = Object.keys(translations).length ? translations : void 0;
4942
+ break;
4943
+ }
4944
+ case "set-image-asset":
4945
+ survey.assets = {
4946
+ ...survey.assets ?? {},
4947
+ [operation.assetId]: operation.asset
4948
+ };
4949
+ break;
4950
+ case "update-image-asset-metadata": {
4951
+ const asset = survey.assets?.[operation.assetId];
4952
+ const metadata = { ...asset.metadata ?? {} };
4953
+ for (const [key, value] of Object.entries(operation.metadata)) if (value === null) delete metadata[key];
4954
+ else if (value !== void 0) Object.assign(metadata, { [key]: value });
4955
+ survey.assets = {
4956
+ ...survey.assets ?? {},
4957
+ [operation.assetId]: {
4958
+ ...asset,
4959
+ metadata: Object.keys(metadata).length ? metadata : void 0
4960
+ }
4961
+ };
4962
+ break;
4963
+ }
4964
+ case "remove-image-asset": {
4965
+ const assets = { ...survey.assets ?? {} };
4966
+ delete assets[operation.assetId];
4967
+ survey.assets = Object.keys(assets).length ? assets : void 0;
4968
+ break;
4969
+ }
4970
+ }
4971
+ }
4972
+ return rawSurveyDocumentSchema.parse(survey);
4973
+ };
4974
+ const createSurveyChangeSet = ({ maxOperations, operations, snapshot, summary, turnId }) => {
4562
4975
  const validation = validateSurveyOperations({
4976
+ maxOperations,
4563
4977
  operations,
4564
4978
  snapshot
4565
4979
  });
@@ -4576,94 +4990,138 @@ const createSurveyChangeSet = ({ operations, snapshot, summary, turnId }) => {
4576
4990
  };
4577
4991
  };
4578
4992
  //#endregion
4579
- //#region src/engine/turn-change-collector.ts
4993
+ //#region src/engine/turn-survey-draft.ts
4580
4994
  const createId = () => globalThis.crypto.randomUUID();
4581
- const SURVEY_CHANGE_CONTINUATION_MODES = ["append", "replace"];
4582
- const SURVEY_CHANGE_CONTINUATION_DESCRIPTION = "Omit only for the first mutation call in this turn. On every later mutation call, use \"append\" for independent additional work or \"replace\" for a correction that should discard all currently active candidates.";
4583
- var TurnChangeCollector = class {
4995
+ const operationTargets = (operation) => {
4996
+ switch (operation.kind) {
4997
+ case "create-item": return [`item:${operation.item.id}`];
4998
+ case "update-item-key":
4999
+ case "update-item-label":
5000
+ case "update-item-color":
5001
+ case "update-item-translation":
5002
+ case "update-placed-image":
5003
+ case "patch-item-config":
5004
+ case "add-choice-option":
5005
+ case "update-choice-option-label":
5006
+ case "remove-choice-option":
5007
+ case "reorder-choice-options":
5008
+ case "set-embedded-form":
5009
+ case "remove-embedded-form":
5010
+ case "remove-item":
5011
+ case "move-item": return [`item:${operation.itemId}`];
5012
+ case "upsert-form-field-group":
5013
+ case "remove-form-field-group":
5014
+ case "reorder-form-field-groups":
5015
+ case "upsert-form-field":
5016
+ case "remove-form-field":
5017
+ case "move-form-field": return [`item:${operation.target.itemId}`];
5018
+ case "reorder-items": return [`item:${operation.parentItemId}`];
5019
+ case "set-image-asset":
5020
+ case "update-image-asset-metadata":
5021
+ case "remove-image-asset": return [`asset:${operation.assetId}`];
5022
+ case "update-survey-metadata":
5023
+ case "update-survey-pagination":
5024
+ case "update-survey-translation":
5025
+ case "remove-survey-locale": return ["survey"];
5026
+ case "survey-json-patch": return [];
5027
+ }
5028
+ };
5029
+ const collectOperationTargets = (operations) => new Set(operations.flatMap(operationTargets));
5030
+ /**
5031
+ * Request-scoped transactional draft for one assistant response.
5032
+ *
5033
+ * Mutation tools submit deltas. A successful delta replaces the previous
5034
+ * candidate with a newly validated, cumulative change set. A rejected delta
5035
+ * leaves the accepted draft intact and blocks authorization until a later
5036
+ * delta validates. Only the latest cumulative candidate can be authorized.
5037
+ */
5038
+ var TurnSurveyDraft = class {
4584
5039
  turnId;
4585
5040
  #candidates = /* @__PURE__ */ new Map();
4586
- #activeChangeSetIds = [];
4587
- #blockedChangeSetIds = /* @__PURE__ */ new Set();
4588
- #ambiguousChangeSetIds = /* @__PURE__ */ new Set();
4589
- #requiresReplacement = false;
5041
+ #draftOperations = [];
5042
+ #context;
5043
+ #latestChangeSetId;
5044
+ #unresolvedCandidates = /* @__PURE__ */ new Map();
4590
5045
  #final = null;
4591
5046
  constructor(turnId = createId()) {
4592
5047
  this.turnId = turnId;
4593
5048
  }
4594
- register(candidate, { continuationMode } = {}) {
4595
- if (this.#final) throw new Error("Cannot register a survey change after the turn was finalized.");
5049
+ initialize(context) {
5050
+ if (this.#context) throw new Error("The survey turn draft was initialized more than once.");
5051
+ this.#context = context;
5052
+ }
5053
+ stage({ addressedTargets = [], operations, snapshot, summary }) {
5054
+ this.#assertOpen();
5055
+ if (!this.#context && snapshot && "document" in snapshot && "trustedCapabilities" in snapshot) this.#context = snapshot;
5056
+ const currentContext = this.#context ?? snapshot;
5057
+ const targets = new Set([...collectOperationTargets(operations), ...addressedTargets]);
5058
+ const delta = createSurveyChangeSet({
5059
+ operations,
5060
+ snapshot: currentContext,
5061
+ summary,
5062
+ turnId: this.turnId
5063
+ });
5064
+ if (delta.status !== "validated" || !delta.validation.valid) return this.reject(delta, targets);
5065
+ const cumulativeOperations = [...this.#draftOperations, ...delta.operations];
5066
+ if (cumulativeOperations.length > 1e3) return this.reject(createSurveyChangeSet({
5067
+ maxOperations: MAX_SURVEY_ASSISTANT_OPERATIONS_PER_TURN_DRAFT,
5068
+ operations: cumulativeOperations,
5069
+ snapshot: currentContext,
5070
+ summary,
5071
+ turnId: this.turnId
5072
+ }), targets);
5073
+ if (this.#context) {
5074
+ const survey = applyValidatedSurveyOperationsToDraft({
5075
+ operations: delta.operations,
5076
+ survey: this.#context.survey
5077
+ });
5078
+ this.#context = createSurveyAssistantEngineContext({
5079
+ ...this.#context.document,
5080
+ survey
5081
+ }, this.#context.trustedCapabilities);
5082
+ }
5083
+ const registered = surveyChangeSetSchema.parse({
5084
+ ...delta,
5085
+ operations: cumulativeOperations,
5086
+ validation: {
5087
+ valid: true,
5088
+ issues: [],
5089
+ warnings: delta.validation.warnings,
5090
+ normalizedOperations: cumulativeOperations
5091
+ },
5092
+ ...this.#latestChangeSetId ? { supersedesChangeIds: [this.#latestChangeSetId] } : {}
5093
+ });
5094
+ this.#candidates.set(registered.id, registered);
5095
+ this.#draftOperations = registered.operations;
5096
+ this.#latestChangeSetId = registered.id;
5097
+ for (const [changeSetId, unresolvedTargets] of this.#unresolvedCandidates) if (unresolvedTargets.size === 0 || [...unresolvedTargets].some((target) => targets.has(target))) this.#unresolvedCandidates.delete(changeSetId);
5098
+ return registered;
5099
+ }
5100
+ reject(candidate, addressedTargets = []) {
5101
+ this.#assertOpen();
4596
5102
  if (this.#candidates.has(candidate.id)) throw new Error(`Survey change "${candidate.id}" was registered more than once.`);
4597
- const modeRequired = this.#candidates.size > 0 && continuationMode === void 0;
4598
- const replacementRequired = this.#requiresReplacement && continuationMode !== "replace";
4599
- const continuationIssue = modeRequired ? {
4600
- severity: "error",
4601
- code: "change-mode-required",
4602
- message: "A prior mutation candidate exists in this turn. Retry this complete proposal with continuationMode \"append\" for independent additional work or \"replace\" for a correction that discards the active candidates."
4603
- } : replacementRequired ? {
4604
- severity: "error",
4605
- code: "change-mode-repair-required",
4606
- message: "An earlier mutation proposal was rejected and blocked turn authorization. Retry the complete intended proposal with continuationMode \"replace\" before appending any later independent chunks."
4607
- } : void 0;
4608
- const candidateWasRejected = candidate.status !== "validated" || !candidate.validation.valid;
4609
- const ambiguousModeOnly = modeRequired && !candidateWasRejected && continuationIssue?.code === "change-mode-required";
4610
- const shouldClearActive = continuationMode === "replace" || continuationIssue !== void 0 && !ambiguousModeOnly || candidateWasRejected;
4611
- const supersedesChangeIds = shouldClearActive ? [...this.#activeChangeSetIds] : [];
4612
- const recovery = continuationIssue !== void 0 || candidateWasRejected ? ambiguousModeOnly ? {
4613
- turnAuthorization: "blocked",
4614
- candidateAuthorization: "not-authorized",
4615
- requiredAction: "choose-continuation-mode",
4616
- allowedContinuationModes: ["append", "replace"]
4617
- } : {
4618
- turnAuthorization: "blocked",
4619
- candidateAuthorization: "not-authorized",
4620
- requiredAction: "resubmit-complete-proposal",
4621
- allowedContinuationModes: ["replace"],
4622
- requiredContinuationMode: "replace"
4623
- } : void 0;
4624
5103
  const registered = surveyChangeSetSchema.parse({
4625
5104
  ...candidate,
4626
5105
  turnId: this.turnId,
4627
- ...continuationIssue ? {
4628
- status: "rejected",
4629
- validation: {
4630
- ...candidate.validation,
4631
- valid: false,
4632
- issues: [...candidate.validation.issues, continuationIssue]
4633
- }
4634
- } : {},
4635
- ...supersedesChangeIds.length > 0 ? { supersedesChangeIds } : {},
4636
- ...recovery ? { recovery } : {}
5106
+ status: "rejected",
5107
+ validation: {
5108
+ ...candidate.validation,
5109
+ valid: false
5110
+ }
4637
5111
  });
4638
5112
  this.#candidates.set(registered.id, registered);
4639
- const isValidated = registered.status === "validated" && registered.validation.valid;
4640
- if (shouldClearActive) this.#activeChangeSetIds = [];
4641
- if (ambiguousModeOnly) {
4642
- this.#blockedChangeSetIds.add(registered.id);
4643
- this.#ambiguousChangeSetIds.add(registered.id);
4644
- } else if (!isValidated) {
4645
- this.#blockedChangeSetIds.add(registered.id);
4646
- this.#requiresReplacement = true;
4647
- } else if (continuationMode === "replace") {
4648
- this.#blockedChangeSetIds.clear();
4649
- this.#ambiguousChangeSetIds.clear();
4650
- this.#requiresReplacement = false;
4651
- } else if (continuationMode === "append" && this.#ambiguousChangeSetIds.size > 0) {
4652
- for (const changeSetId of this.#ambiguousChangeSetIds) this.#blockedChangeSetIds.delete(changeSetId);
4653
- this.#ambiguousChangeSetIds.clear();
4654
- }
4655
- if (!this.#requiresReplacement && isValidated) this.#activeChangeSetIds.push(registered.id);
5113
+ this.#unresolvedCandidates.set(registered.id, new Set(addressedTargets));
4656
5114
  return registered;
4657
5115
  }
4658
5116
  finalize(status, createdAt = (/* @__PURE__ */ new Date()).toISOString()) {
4659
5117
  if (this.#final) return this.#final;
4660
- const blockedChangeSetIds = [...this.#blockedChangeSetIds];
4661
- const canAuthorize = status === "completed" && !this.#requiresReplacement && blockedChangeSetIds.length === 0;
5118
+ const blockedChangeSetIds = [...this.#unresolvedCandidates.keys()];
5119
+ const canAuthorize = status === "completed" && blockedChangeSetIds.length === 0 && this.#latestChangeSetId !== void 0;
4662
5120
  this.#final = surveyAssistantTurnFinalSchema.parse({
4663
5121
  protocolVersion: 1,
4664
5122
  turnId: this.turnId,
4665
5123
  status,
4666
- executableChangeSetIds: canAuthorize ? this.#activeChangeSetIds : [],
5124
+ executableChangeSetIds: canAuthorize ? [this.#latestChangeSetId] : [],
4667
5125
  ...blockedChangeSetIds.length > 0 ? { blockedChangeSetIds } : {},
4668
5126
  createdAt
4669
5127
  });
@@ -4672,9 +5130,18 @@ var TurnChangeCollector = class {
4672
5130
  getCandidate(changeSetId) {
4673
5131
  return this.#candidates.get(changeSetId);
4674
5132
  }
5133
+ get draftOperations() {
5134
+ return this.#draftOperations;
5135
+ }
5136
+ get context() {
5137
+ return this.#context;
5138
+ }
4675
5139
  get final() {
4676
5140
  return this.#final;
4677
5141
  }
5142
+ #assertOpen() {
5143
+ if (this.#final) throw new Error("Cannot register a survey change after the turn was finalized.");
5144
+ }
4678
5145
  };
4679
5146
  //#endregion
4680
5147
  //#region src/engine/task-context.ts
@@ -5194,6 +5661,6 @@ const auditSurvey = (context, options = {}) => {
5194
5661
  };
5195
5662
  };
5196
5663
  //#endregion
5197
- export { deriveResponseSlotTypes as $, getRawSurvey$1 as A, resolveItemSummary as B, clampLimit as C, formFieldGroupLayoutSchema as D, formFieldGroupDetailsSchema as E, mapKeyIndexById as F, summarizeKeyIndexEntry as G, summarizeFocusItemNode as H, mapOutlineById as I, translationEntrySchema as J, summarizeOutlineNode as K, mapRawItemsById as L, itemConfigDetailsSchema as M, itemMatchesQuery as N, formSelectOptionDetailsSchema as O, itemSummarySchema as P, createSurveyAssistantEngineContext as Q, normalizeText as R, choiceOptionDetailsSchema as S, formFieldDetailsSchema as T, summarizeItemById as U, sliceWithCursor as V, summarizeKeyIndexEntries as W, inferExpressionReturnType as X, unique as Y, validateExpression as Z, buildConfigSearchText as _, SURVEY_CHANGE_CONTINUATION_MODES as a, buildItemTranslationEntries as b, buildSurveyDocumentVersion as c, normalizeSurveyChangeOperations as d, buildExpressionMutationPatches as f, MAX_LIMIT as g, DEFAULT_LIMIT as h, SURVEY_CHANGE_CONTINUATION_DESCRIPTION as i, getRawSurveyItems as j, getKeyIndexEntries as k, createSurveyChangeSet as l, expressionSchema as m, SurveyAssistantTaskResultCache as n, TurnChangeCollector as o, expressionMutationSchema as p, summarizeRawItem as q, buildSurveyAssistantTaskContext as r, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_PROPOSAL as s, auditSurvey as t, validateSurveyOperations as u, buildEmbeddedFormDetails as v, findDirectChildrenByFullKey as w, buildSurveyTranslations as x, buildItemConfigDetails as y, parseCursor as z };
5664
+ export { deriveResponseSlotTypes as $, getRawSurvey$1 as A, resolveItemSummary as B, clampLimit as C, formFieldGroupLayoutSchema as D, formFieldGroupDetailsSchema as E, mapKeyIndexById as F, summarizeKeyIndexEntry as G, summarizeFocusItemNode as H, mapOutlineById as I, translationEntrySchema as J, summarizeOutlineNode as K, mapRawItemsById as L, itemConfigDetailsSchema as M, itemMatchesQuery as N, formSelectOptionDetailsSchema as O, itemSummarySchema as P, createSurveyAssistantEngineContext as Q, normalizeText as R, choiceOptionDetailsSchema as S, formFieldDetailsSchema as T, summarizeItemById as U, sliceWithCursor as V, summarizeKeyIndexEntries as W, inferExpressionReturnType as X, unique as Y, validateExpression as Z, buildConfigSearchText as _, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_PROPOSAL as a, buildItemTranslationEntries as b, buildSurveyDocumentVersion as c, normalizeSurveyChangeOperations as d, buildExpressionMutationPatches as f, MAX_LIMIT as g, DEFAULT_LIMIT as h, TurnSurveyDraft as i, getRawSurveyItems as j, getKeyIndexEntries as k, createSurveyChangeSet as l, expressionSchema as m, SurveyAssistantTaskResultCache as n, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_TURN_DRAFT as o, expressionMutationSchema as p, summarizeRawItem as q, buildSurveyAssistantTaskContext as r, applyValidatedSurveyOperationsToDraft as s, auditSurvey as t, validateSurveyOperations as u, buildEmbeddedFormDetails as v, findDirectChildrenByFullKey as w, buildSurveyTranslations as x, buildItemConfigDetails as y, parseCursor as z };
5198
5665
 
5199
- //# sourceMappingURL=engine-DQrCO_ur.mjs.map
5666
+ //# sourceMappingURL=engine-0AJrv0hI.mjs.map