@aglyn/plugins-ai 1.0.0-beta.237 → 1.0.0-beta.239

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 (172) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
  3. package/src/lib/components/ai-credits-prompt.component.js +84 -34
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
  5. package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
  6. package/src/lib/components/ai-job-cancel.component.js +156 -0
  7. package/src/lib/components/ai-job-cancel.component.js.map +1 -0
  8. package/src/lib/components/ai-job-links.js +8 -0
  9. package/src/lib/components/ai-job-links.js.map +1 -1
  10. package/src/lib/components/ai-job-plan.component.js +12 -1
  11. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  12. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  13. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  14. package/src/lib/components/ai-jobs-store.d.ts +12 -0
  15. package/src/lib/components/ai-jobs-store.js +15 -0
  16. package/src/lib/components/ai-jobs-store.js.map +1 -1
  17. package/src/lib/components/ai-site-build-page.component.js +22 -2
  18. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  19. package/src/lib/components/ai-usage-meter-refresh.d.ts +35 -0
  20. package/src/lib/components/ai-usage-meter-refresh.js +63 -0
  21. package/src/lib/components/ai-usage-meter-refresh.js.map +1 -0
  22. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  23. package/src/lib/components/ai-usage-strip.component.js +40 -9
  24. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  25. package/src/lib/components/assist-jobs-drawer.component.js +12 -16
  26. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  27. package/src/lib/components/assist-panel.component.js +91 -51
  28. package/src/lib/components/assist-panel.component.js.map +1 -1
  29. package/src/lib/components/use-ai-usage-meter.d.ts +2 -0
  30. package/src/lib/components/use-ai-usage-meter.js +8 -3
  31. package/src/lib/components/use-ai-usage-meter.js.map +1 -1
  32. package/src/lib/jobs/ai-build-unit-outcome.d.ts +3 -1
  33. package/src/lib/jobs/ai-build-unit-outcome.js +19 -3
  34. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  35. package/src/lib/jobs/ai-job-build-step.js +33 -3
  36. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  38. package/src/lib/jobs/ai-job-cancel.js +102 -0
  39. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  40. package/src/lib/jobs/ai-job-component-step.js +4 -1
  41. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-draft-ids.js +7 -2
  43. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  44. package/src/lib/jobs/ai-job-drafts.js +2 -0
  45. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  46. package/src/lib/jobs/ai-job-form-dataset.d.ts +100 -0
  47. package/src/lib/jobs/ai-job-form-dataset.js +161 -0
  48. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -0
  49. package/src/lib/jobs/ai-job-form-step.d.ts +15 -3
  50. package/src/lib/jobs/ai-job-form-step.js +41 -10
  51. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  53. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  54. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  56. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  58. package/src/lib/jobs/ai-job-page-language.js +45 -2
  59. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  60. package/src/lib/jobs/ai-job-page-step.js +12 -1
  61. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-plan-step.d.ts +38 -1
  63. package/src/lib/jobs/ai-job-plan-step.js +90 -13
  64. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
  66. package/src/lib/jobs/ai-job-site-content.js +66 -7
  67. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  68. package/src/lib/jobs/ai-job-site-datasets.d.ts +170 -0
  69. package/src/lib/jobs/ai-job-site-datasets.js +499 -0
  70. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -0
  71. package/src/lib/jobs/ai-job-site-step.d.ts +15 -8
  72. package/src/lib/jobs/ai-job-site-step.js +152 -30
  73. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  74. package/src/lib/jobs/ai-job-template-step.js +4 -1
  75. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  76. package/src/lib/jobs/ai-jobs.d.ts +37 -12
  77. package/src/lib/jobs/ai-jobs.js +184 -28
  78. package/src/lib/jobs/ai-jobs.js.map +1 -1
  79. package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
  80. package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
  81. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  82. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  83. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  84. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  85. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.d.ts +30 -0
  86. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js +637 -0
  87. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js.map +1 -0
  88. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  89. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  90. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  91. package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -0
  92. package/src/lib/layout-language/ai-layout-compiler.js +397 -9
  93. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  94. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  95. package/src/lib/layout-language/ai-layout-design.js +5 -3
  96. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  98. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  99. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  100. package/src/lib/layout-language/ai-layout-gaps.js +17 -1
  101. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  102. package/src/lib/layout-language/ai-layout-listings.d.ts +44 -3
  103. package/src/lib/layout-language/ai-layout-listings.js +118 -50
  104. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  105. package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
  106. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  107. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  108. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  109. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  110. package/src/lib/model/ai-build-job.d.ts +11 -1
  111. package/src/lib/model/ai-build-job.js +35 -6
  112. package/src/lib/model/ai-build-job.js.map +1 -1
  113. package/src/lib/model/ai-build-plan.d.ts +23 -3
  114. package/src/lib/model/ai-build-plan.js +47 -6
  115. package/src/lib/model/ai-build-plan.js.map +1 -1
  116. package/src/lib/model/ai-build-progress.d.ts +2 -2
  117. package/src/lib/model/ai-build-progress.js +6 -4
  118. package/src/lib/model/ai-build-progress.js.map +1 -1
  119. package/src/lib/model/ai-credit-estimate.d.ts +69 -2
  120. package/src/lib/model/ai-credit-estimate.js +91 -3
  121. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  122. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  123. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  124. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  125. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  126. package/src/lib/model/ai-job-failure-copy.js +8 -3
  127. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  128. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  129. package/src/lib/model/ai-jobs.types.js.map +1 -1
  130. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  131. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  132. package/src/lib/model/ai-site-build-progress.js +7 -4
  133. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  134. package/src/lib/model/ai-site-job.d.ts +38 -4
  135. package/src/lib/model/ai-site-job.js +50 -14
  136. package/src/lib/model/ai-site-job.js.map +1 -1
  137. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  138. package/src/lib/model/ai-site-kinds.js +34 -6
  139. package/src/lib/model/ai-site-kinds.js.map +1 -1
  140. package/src/lib/runtime/ai-dataset-generation.d.ts +77 -0
  141. package/src/lib/runtime/ai-dataset-generation.js +127 -0
  142. package/src/lib/runtime/ai-dataset-generation.js.map +1 -0
  143. package/src/lib/runtime/ai-doctrine-validators.d.ts +77 -1
  144. package/src/lib/runtime/ai-doctrine-validators.js +280 -6
  145. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  146. package/src/lib/runtime/ai-doctrine.js +10 -4
  147. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  148. package/src/lib/runtime/ai-node-tree.d.ts +2 -0
  149. package/src/lib/runtime/ai-node-tree.js +16 -0
  150. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  151. package/src/lib/runtime/ai-palette.generated.js +53 -7
  152. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  153. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  154. package/src/lib/server/ai-jobs-cancel.js +27 -9
  155. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  156. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  157. package/src/lib/server/ai-jobs-gate.js +20 -9
  158. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  159. package/src/lib/tools/ai-dataset-tool.d.ts +68 -0
  160. package/src/lib/tools/ai-dataset-tool.js +231 -0
  161. package/src/lib/tools/ai-dataset-tool.js.map +1 -0
  162. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  163. package/src/lib/usage/ai-usage-meter.js +17 -1
  164. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  165. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  166. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  167. package/src/lib/usage/assist-usage.d.ts +7 -0
  168. package/src/lib/usage/assist-usage.js +14 -4
  169. package/src/lib/usage/assist-usage.js.map +1 -1
  170. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  171. package/src/lib/usage/free-ai-credits-left.js +30 -4
  172. package/src/lib/usage/free-ai-credits-left.js.map +1 -1
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
18
+ import type { NodesMap } from '@aglyn/aglyn/types/nodes';
19
+ import type { AiSiteInventory } from '../model/ai-site-inventory';
20
+ /**
21
+ * A FORM THAT WRITES TO A DATASET (AGL-3616; Zach, 2026-10-10: Aglyn AI's
22
+ * forms can write to datasets — a volunteer sign-up into a Volunteers
23
+ * dataset, an RSVP into an event's attendees, a catering inquiry into
24
+ * Inquiries).
25
+ *
26
+ * The plan says which dataset a form writes to (`writesTo` on its creation);
27
+ * it is never inferred from what a section places. The dataset is built
28
+ * first — a site start's dataset unit, a build's dataset item, or one the
29
+ * site already has — and the form step binds the form it generated the way a
30
+ * person binds one on the Form element: the form node's `datasetId`, and
31
+ * each Form Field's `datasetFieldId` where the dataset has a field of the
32
+ * same name and a type the submitted value can be stored as. A field the
33
+ * dataset lacks is left unbound, as the Form element's mapping leaves one:
34
+ * its value stays in the Inbox copy, and a form never adds a field to a
35
+ * dataset.
36
+ *
37
+ * The gates are the platform's. The binding is made only where the plan
38
+ * includes the data store (`dataStore`), the data plugin's writer is loaded
39
+ * here and reads the dataset, and the dataset is one this job made or one the
40
+ * site's inventory shows. Records, their allowance and storage are held at
41
+ * each submission by the data plugin's own record target. Anywhere else the
42
+ * form is the form it always was, its submissions in the Inbox.
43
+ */
44
+ /** The unit input a dataset this job made for the form rides in, by id. */
45
+ export declare const AI_FORM_DATASET_MADE_INPUT = "formDatasetMade";
46
+ /** One field of the dataset a form writes to, as its writer reports it: storage type words. */
47
+ export interface AiFormDatasetField {
48
+ id: string;
49
+ name: string;
50
+ type: string;
51
+ }
52
+ /** The dataset a form writes to, with the fields a submission may fill. */
53
+ export interface AiFormDataset {
54
+ id: string;
55
+ name: string;
56
+ fields: AiFormDatasetField[];
57
+ }
58
+ /** A field name as two spellings of it compare: `fullName`, `full_name` and "Full name" are one. */
59
+ export declare function aiFormFieldKey(name: string): string;
60
+ /**
61
+ * Whether a submitted value of this form field type can be stored in a
62
+ * dataset field of this storage type. Every value a form submits is text, so
63
+ * a text field takes any of them and a number field takes a rating; any
64
+ * other pairing is left unbound rather than refused at every submission.
65
+ */
66
+ export declare function aiFormFieldFitsDataset(fieldType: string, datasetType: string): boolean;
67
+ /** What a binding came to: the fields bound, and the fields the dataset has no place for. */
68
+ export interface AiFormDatasetBinding {
69
+ bound: string[];
70
+ unbound: string[];
71
+ }
72
+ /**
73
+ * Binds a form design to a dataset, in place: the form node's `datasetId`,
74
+ * and `datasetFieldId` on each Form Field the dataset has a field for, by its
75
+ * `fieldName` or its label, each dataset field taken once. The platform's
76
+ * consent field is never bound. A binding the design already carried is
77
+ * replaced, so what the model drew never chooses where a record lands.
78
+ */
79
+ export declare function aiBindFormToDataset(nodes: NodesMap, formNodeId: string, dataset: AiFormDataset): AiFormDatasetBinding;
80
+ export interface AiFormDatasetReadDeps {
81
+ writerFor?: typeof pluginResourceDraftWriter;
82
+ }
83
+ /**
84
+ * The dataset a form may write to, or `null` where it may not: the plan
85
+ * includes no data store, the data plugin's writer is not loaded here, the
86
+ * dataset is neither one this job made nor one the site's inventory shows,
87
+ * or it has no field a submission could fill.
88
+ */
89
+ export declare function aiReadFormDataset(input: {
90
+ hostId: string;
91
+ id: string | null | undefined;
92
+ org: object | null;
93
+ inventory: Pick<AiSiteInventory, 'datasets'> | null;
94
+ /** The dataset this job made for the form, by id. */
95
+ madeId?: unknown;
96
+ }, deps?: AiFormDatasetReadDeps): Promise<AiFormDataset | null>;
97
+ /** What the form's generation is told about the dataset it writes to. */
98
+ export declare function aiFormDatasetPromptLine(dataset: AiFormDataset): string;
99
+ /** What the form's row says about where its submissions are also saved. */
100
+ export declare function aiFormDatasetNote(dataset: AiFormDataset, binding: AiFormDatasetBinding): string;
@@ -0,0 +1,161 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
18
+ import { collectFormFieldNodeIds, FORM_COMPONENT_ID, isMarketingConsentFieldName } from "@aglyn/aglyn/app-utils/forms";
19
+ import { pluginResourceDraftWriter } from "@aglyn/aglyn/plugin-manager/plugin-resource-drafts";
20
+ import { AI_DATASET_DRAFT_RESOURCE } from "./ai-build-unit-outcome.js";
21
+ /**
22
+ * A FORM THAT WRITES TO A DATASET (AGL-3616; Zach, 2026-10-10: Aglyn AI's
23
+ * forms can write to datasets — a volunteer sign-up into a Volunteers
24
+ * dataset, an RSVP into an event's attendees, a catering inquiry into
25
+ * Inquiries).
26
+ *
27
+ * The plan says which dataset a form writes to (`writesTo` on its creation);
28
+ * it is never inferred from what a section places. The dataset is built
29
+ * first — a site start's dataset unit, a build's dataset item, or one the
30
+ * site already has — and the form step binds the form it generated the way a
31
+ * person binds one on the Form element: the form node's `datasetId`, and
32
+ * each Form Field's `datasetFieldId` where the dataset has a field of the
33
+ * same name and a type the submitted value can be stored as. A field the
34
+ * dataset lacks is left unbound, as the Form element's mapping leaves one:
35
+ * its value stays in the Inbox copy, and a form never adds a field to a
36
+ * dataset.
37
+ *
38
+ * The gates are the platform's. The binding is made only where the plan
39
+ * includes the data store (`dataStore`), the data plugin's writer is loaded
40
+ * here and reads the dataset, and the dataset is one this job made or one the
41
+ * site's inventory shows. Records, their allowance and storage are held at
42
+ * each submission by the data plugin's own record target. Anywhere else the
43
+ * form is the form it always was, its submissions in the Inbox.
44
+ */ /** The unit input a dataset this job made for the form rides in, by id. */ export const AI_FORM_DATASET_MADE_INPUT = 'formDatasetMade';
45
+ /** A field name as two spellings of it compare: `fullName`, `full_name` and "Full name" are one. */ export function aiFormFieldKey(name) {
46
+ return name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().replace(/[^a-z0-9]+/g, '');
47
+ }
48
+ const NUMBER_TYPES = new Set([
49
+ 'float',
50
+ 'int32',
51
+ 'int64',
52
+ 'number',
53
+ 'integer'
54
+ ]);
55
+ /**
56
+ * Whether a submitted value of this form field type can be stored in a
57
+ * dataset field of this storage type. Every value a form submits is text, so
58
+ * a text field takes any of them and a number field takes a rating; any
59
+ * other pairing is left unbound rather than refused at every submission.
60
+ */ export function aiFormFieldFitsDataset(fieldType, datasetType) {
61
+ if (datasetType === 'text') return true;
62
+ if (NUMBER_TYPES.has(datasetType)) return fieldType === 'rating';
63
+ return false;
64
+ }
65
+ /**
66
+ * Binds a form design to a dataset, in place: the form node's `datasetId`,
67
+ * and `datasetFieldId` on each Form Field the dataset has a field for, by its
68
+ * `fieldName` or its label, each dataset field taken once. The platform's
69
+ * consent field is never bound. A binding the design already carried is
70
+ * replaced, so what the model drew never chooses where a record lands.
71
+ */ export function aiBindFormToDataset(nodes, formNodeId, dataset) {
72
+ var _form_props;
73
+ const result = {
74
+ bound: [],
75
+ unbound: []
76
+ };
77
+ const form = nodes[formNodeId];
78
+ if ((form == null ? void 0 : form.componentId) !== FORM_COMPONENT_ID) return result;
79
+ const props = _extends({}, (_form_props = form.props) != null ? _form_props : {});
80
+ delete props['datasetName'];
81
+ props['datasetId'] = dataset.id;
82
+ form.props = props;
83
+ const taken = new Set();
84
+ for (const id of collectFormFieldNodeIds(nodes, formNodeId)){
85
+ var _field_props, _fieldProps_fieldName, _fieldProps_label, _fieldProps_fieldType, _matching;
86
+ const field = nodes[id];
87
+ if (!field) continue;
88
+ const fieldProps = _extends({}, (_field_props = field.props) != null ? _field_props : {});
89
+ delete fieldProps['datasetFieldId'];
90
+ field.props = fieldProps;
91
+ const fieldName = String((_fieldProps_fieldName = fieldProps['fieldName']) != null ? _fieldProps_fieldName : '').trim();
92
+ if (!fieldName || isMarketingConsentFieldName(fieldName)) continue;
93
+ const label = String((_fieldProps_label = fieldProps['label']) != null ? _fieldProps_label : '').trim();
94
+ const fieldType = String((_fieldProps_fieldType = fieldProps['fieldType']) != null ? _fieldProps_fieldType : 'text');
95
+ // Its name first, then its label: `fullName` is "Full name" before "Your name" is.
96
+ const matching = (key)=>key ? dataset.fields.find((one)=>!taken.has(one.id) && aiFormFieldKey(one.name) === key && aiFormFieldFitsDataset(fieldType, one.type)) : undefined;
97
+ const match = (_matching = matching(aiFormFieldKey(fieldName))) != null ? _matching : matching(aiFormFieldKey(label));
98
+ if (!match) {
99
+ result.unbound.push(label || fieldName);
100
+ continue;
101
+ }
102
+ taken.add(match.id);
103
+ fieldProps['datasetFieldId'] = match.id;
104
+ result.bound.push(fieldName);
105
+ }
106
+ return result;
107
+ }
108
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
109
+ /**
110
+ * The dataset a form may write to, or `null` where it may not: the plan
111
+ * includes no data store, the data plugin's writer is not loaded here, the
112
+ * dataset is neither one this job made nor one the site's inventory shows,
113
+ * or it has no field a submission could fill.
114
+ */ export async function aiReadFormDataset(input, deps = {}) {
115
+ var _ref, _deps_writerFor, _record_facts;
116
+ var _input_inventory;
117
+ const id = str(input.id);
118
+ if (!id) return null;
119
+ if (!checkEntitlement(input.org, 'dataStore')) return null;
120
+ const known = id === str(input.madeId) || ((_ref = (_input_inventory = input.inventory) == null ? void 0 : _input_inventory.datasets) != null ? _ref : []).some((row)=>row.id === id);
121
+ if (!known) return null;
122
+ const keeper = ((_deps_writerFor = deps.writerFor) != null ? _deps_writerFor : pluginResourceDraftWriter)(AI_DATASET_DRAFT_RESOURCE);
123
+ if (!keeper) return null;
124
+ const record = await keeper.writer.read({
125
+ hostId: input.hostId,
126
+ id
127
+ }).catch((error)=>{
128
+ console.error('ai form: the dataset it writes to could not be read', {
129
+ hostId: input.hostId,
130
+ id,
131
+ error
132
+ });
133
+ return null;
134
+ });
135
+ if (!record) return null;
136
+ const facts = (_record_facts = record.facts) != null ? _record_facts : {};
137
+ const address = str(facts.addressField);
138
+ const fields = (Array.isArray(facts.fields) ? facts.fields : []).map((raw)=>raw && typeof raw === 'object' ? raw : {}).map((raw)=>({
139
+ id: str(raw['id']),
140
+ name: str(raw['name']),
141
+ type: str(raw['type']) || 'text'
142
+ })).filter((field)=>field.id && field.name && field.id !== address);
143
+ if (!fields.length) return null;
144
+ return {
145
+ id,
146
+ name: str(record.name) || 'dataset',
147
+ fields
148
+ };
149
+ }
150
+ /** What the form's generation is told about the dataset it writes to. */ export function aiFormDatasetPromptLine(dataset) {
151
+ const fields = dataset.fields.map((field)=>`${field.name} (${NUMBER_TYPES.has(field.type) ? 'a number' : 'text'})`);
152
+ return `Each submission is also saved as a record of the dataset “${dataset.name}”, whose fields are: ${fields.join(', ')}. Draw a field for each one a visitor can answer, labeled with the field's name.`;
153
+ }
154
+ /** What the form's row says about where its submissions are also saved. */ export function aiFormDatasetNote(dataset, binding) {
155
+ const kept = `Each submission is also saved as a record of the “${dataset.name}” dataset, in Data.`;
156
+ if (!binding.unbound.length) return kept;
157
+ const one = binding.unbound.length === 1;
158
+ return `${kept} The dataset has no field for ${binding.unbound.map((name)=>`“${name}”`).join(', ')}, so ${one ? 'that answer stays' : 'those answers stay'} in the Inbox only.`;
159
+ }
160
+
161
+ //# sourceMappingURL=ai-job-form-dataset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-form-dataset.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n collectFormFieldNodeIds,\n FORM_COMPONENT_ID,\n isMarketingConsentFieldName,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_DATASET_DRAFT_RESOURCE } from './ai-build-unit-outcome'\n\n/**\n * A FORM THAT WRITES TO A DATASET (AGL-3616; Zach, 2026-10-10: Aglyn AI's\n * forms can write to datasets — a volunteer sign-up into a Volunteers\n * dataset, an RSVP into an event's attendees, a catering inquiry into\n * Inquiries).\n *\n * The plan says which dataset a form writes to (`writesTo` on its creation);\n * it is never inferred from what a section places. The dataset is built\n * first — a site start's dataset unit, a build's dataset item, or one the\n * site already has — and the form step binds the form it generated the way a\n * person binds one on the Form element: the form node's `datasetId`, and\n * each Form Field's `datasetFieldId` where the dataset has a field of the\n * same name and a type the submitted value can be stored as. A field the\n * dataset lacks is left unbound, as the Form element's mapping leaves one:\n * its value stays in the Inbox copy, and a form never adds a field to a\n * dataset.\n *\n * The gates are the platform's. The binding is made only where the plan\n * includes the data store (`dataStore`), the data plugin's writer is loaded\n * here and reads the dataset, and the dataset is one this job made or one the\n * site's inventory shows. Records, their allowance and storage are held at\n * each submission by the data plugin's own record target. Anywhere else the\n * form is the form it always was, its submissions in the Inbox.\n */\n\n/** The unit input a dataset this job made for the form rides in, by id. */\nexport const AI_FORM_DATASET_MADE_INPUT = 'formDatasetMade'\n\n/** One field of the dataset a form writes to, as its writer reports it: storage type words. */\nexport interface AiFormDatasetField {\n id: string\n name: string\n type: string\n}\n\n/** The dataset a form writes to, with the fields a submission may fill. */\nexport interface AiFormDataset {\n id: string\n name: string\n fields: AiFormDatasetField[]\n}\n\n/** A field name as two spellings of it compare: `fullName`, `full_name` and \"Full name\" are one. */\nexport function aiFormFieldKey(name: string): string {\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '')\n}\n\nconst NUMBER_TYPES = new Set(['float', 'int32', 'int64', 'number', 'integer'])\n\n/**\n * Whether a submitted value of this form field type can be stored in a\n * dataset field of this storage type. Every value a form submits is text, so\n * a text field takes any of them and a number field takes a rating; any\n * other pairing is left unbound rather than refused at every submission.\n */\nexport function aiFormFieldFitsDataset(fieldType: string, datasetType: string): boolean {\n if (datasetType === 'text') return true\n if (NUMBER_TYPES.has(datasetType)) return fieldType === 'rating'\n return false\n}\n\n/** What a binding came to: the fields bound, and the fields the dataset has no place for. */\nexport interface AiFormDatasetBinding {\n bound: string[]\n unbound: string[]\n}\n\n/**\n * Binds a form design to a dataset, in place: the form node's `datasetId`,\n * and `datasetFieldId` on each Form Field the dataset has a field for, by its\n * `fieldName` or its label, each dataset field taken once. The platform's\n * consent field is never bound. A binding the design already carried is\n * replaced, so what the model drew never chooses where a record lands.\n */\nexport function aiBindFormToDataset(\n nodes: NodesMap,\n formNodeId: string,\n dataset: AiFormDataset,\n): AiFormDatasetBinding {\n const result: AiFormDatasetBinding = { bound: [], unbound: [] }\n const form = nodes[formNodeId]\n if (form?.componentId !== FORM_COMPONENT_ID) return result\n const props = { ...((form.props ?? {}) as Record<string, unknown>) }\n delete props['datasetName']\n props['datasetId'] = dataset.id\n form.props = props as never\n const taken = new Set<string>()\n for (const id of collectFormFieldNodeIds(nodes as never, formNodeId)) {\n const field = nodes[id]\n if (!field) continue\n const fieldProps = { ...((field.props ?? {}) as Record<string, unknown>) }\n delete fieldProps['datasetFieldId']\n field.props = fieldProps as never\n const fieldName = String(fieldProps['fieldName'] ?? '').trim()\n if (!fieldName || isMarketingConsentFieldName(fieldName)) continue\n const label = String(fieldProps['label'] ?? '').trim()\n const fieldType = String(fieldProps['fieldType'] ?? 'text')\n // Its name first, then its label: `fullName` is \"Full name\" before \"Your name\" is.\n const matching = (key: string) =>\n key\n ? dataset.fields.find((one) => !taken.has(one.id) && aiFormFieldKey(one.name) === key && aiFormFieldFitsDataset(fieldType, one.type))\n : undefined\n const match = matching(aiFormFieldKey(fieldName)) ?? matching(aiFormFieldKey(label))\n if (!match) {\n result.unbound.push(label || fieldName)\n continue\n }\n taken.add(match.id)\n fieldProps['datasetFieldId'] = match.id\n result.bound.push(fieldName)\n }\n return result\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nexport interface AiFormDatasetReadDeps {\n writerFor?: typeof pluginResourceDraftWriter\n}\n\n/**\n * The dataset a form may write to, or `null` where it may not: the plan\n * includes no data store, the data plugin's writer is not loaded here, the\n * dataset is neither one this job made nor one the site's inventory shows,\n * or it has no field a submission could fill.\n */\nexport async function aiReadFormDataset(\n input: {\n hostId: string\n id: string | null | undefined\n org: object | null\n inventory: Pick<AiSiteInventory, 'datasets'> | null\n /** The dataset this job made for the form, by id. */\n madeId?: unknown\n },\n deps: AiFormDatasetReadDeps = {},\n): Promise<AiFormDataset | null> {\n const id = str(input.id)\n if (!id) return null\n if (!checkEntitlement(input.org as never, 'dataStore')) return null\n const known = id === str(input.madeId) || (input.inventory?.datasets ?? []).some((row) => row.id === id)\n if (!known) return null\n const keeper = (deps.writerFor ?? pluginResourceDraftWriter)(AI_DATASET_DRAFT_RESOURCE)\n if (!keeper) return null\n const record = await keeper.writer.read({ hostId: input.hostId, id }).catch((error: unknown) => {\n console.error('ai form: the dataset it writes to could not be read', { hostId: input.hostId, id, error })\n return null\n })\n if (!record) return null\n const facts = (record.facts ?? {}) as { fields?: unknown; addressField?: unknown }\n const address = str(facts.addressField)\n const fields = (Array.isArray(facts.fields) ? (facts.fields as unknown[]) : [])\n .map((raw) => (raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {}))\n .map((raw) => ({ id: str(raw['id']), name: str(raw['name']), type: str(raw['type']) || 'text' }))\n .filter((field) => field.id && field.name && field.id !== address)\n if (!fields.length) return null\n return { id, name: str(record.name) || 'dataset', fields }\n}\n\n/** What the form's generation is told about the dataset it writes to. */\nexport function aiFormDatasetPromptLine(dataset: AiFormDataset): string {\n const fields = dataset.fields.map((field) => `${field.name} (${NUMBER_TYPES.has(field.type) ? 'a number' : 'text'})`)\n return `Each submission is also saved as a record of the dataset “${dataset.name}”, whose fields are: ${fields.join(', ')}. Draw a field for each one a visitor can answer, labeled with the field's name.`\n}\n\n/** What the form's row says about where its submissions are also saved. */\nexport function aiFormDatasetNote(dataset: AiFormDataset, binding: AiFormDatasetBinding): string {\n const kept = `Each submission is also saved as a record of the “${dataset.name}” dataset, in Data.`\n if (!binding.unbound.length) return kept\n const one = binding.unbound.length === 1\n return `${kept} The dataset has no field for ${binding.unbound.map((name) => `“${name}”`).join(', ')}, so ${\n one ? 'that answer stays' : 'those answers stay'\n } in the Inbox only.`\n}\n"],"names":["checkEntitlement","collectFormFieldNodeIds","FORM_COMPONENT_ID","isMarketingConsentFieldName","pluginResourceDraftWriter","AI_DATASET_DRAFT_RESOURCE","AI_FORM_DATASET_MADE_INPUT","aiFormFieldKey","name","replace","toLowerCase","NUMBER_TYPES","Set","aiFormFieldFitsDataset","fieldType","datasetType","has","aiBindFormToDataset","nodes","formNodeId","dataset","form","result","bound","unbound","componentId","props","id","taken","field","fieldProps","matching","fieldName","String","trim","label","key","fields","find","one","type","undefined","match","push","add","str","value","aiReadFormDataset","input","deps","record","org","known","madeId","inventory","datasets","some","row","keeper","writerFor","writer","read","hostId","catch","error","console","facts","address","addressField","Array","isArray","map","raw","filter","length","aiFormDatasetPromptLine","join","aiFormDatasetNote","binding","kept"],"mappings":"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"}
@@ -24,6 +24,7 @@ import type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
24
24
  import type { AiSystemBlock, AiTool } from '../runtime/ai-runtime';
25
25
  import { readSiteInventory } from '../runtime/site-inventory';
26
26
  import { type AiJobAdmission } from './ai-job-admission';
27
+ import { type AiFormDataset, type AiFormDatasetBinding, type AiFormDatasetReadDeps } from './ai-job-form-dataset';
27
28
  import type { AiJobStepRunner } from './ai-job-text-step';
28
29
  /**
29
30
  * The form step (AGL-2913): the generation step of a `form` job, run once a
@@ -140,18 +141,26 @@ export interface AiFormDraft {
140
141
  consentFieldName: string | null;
141
142
  routing: FormRouting | null;
142
143
  answer: AiFormAnswer;
144
+ /** The dataset its submissions are also written to, and what was bound (AGL-3616). */
145
+ dataset?: {
146
+ dataset: AiFormDataset;
147
+ binding: AiFormDatasetBinding;
148
+ };
143
149
  }
144
150
  /**
145
151
  * The draft a validated design and its answer make: the form node bound to
146
- * `formId` and captioned `name`, no dataset binding, the platform's consent
147
- * field when the fields can yield an email address, the canvas root above,
148
- * and the declaration read off the result. Pure; the input tree is not
152
+ * `formId` and captioned `name`, the platform's consent field when the
153
+ * fields can yield an email address, the canvas root above, and the
154
+ * declaration read off the result. A dataset binding is made only where the
155
+ * plan said the form writes to one this step may bind (`identity.dataset`,
156
+ * AGL-3616); any the model drew is dropped. Pure; the input tree is not
149
157
  * changed.
150
158
  */
151
159
  export declare function aiFormDraft(tree: Pick<AiValidatedTree, 'rootId' | 'nodes'>, answer: Record<string, unknown>, identity: {
152
160
  formId: string;
153
161
  name: string;
154
162
  decisions?: AiFormDecisions;
163
+ dataset?: AiFormDataset | null;
155
164
  }): AiFormDraft;
156
165
  /**
157
166
  * What a draft would break once published, as building-rule violations
@@ -170,6 +179,7 @@ export declare function aiFormDraftCheck(identity: {
170
179
  formId: string;
171
180
  name: string;
172
181
  decisions?: AiFormDecisions;
182
+ dataset?: AiFormDataset | null;
173
183
  }): {
174
184
  extend: (tree: AiValidatedTree, answer: Record<string, unknown>) => AiDoctrineViolation[];
175
185
  draftFor: (tree: AiValidatedTree) => AiFormDraft | undefined;
@@ -280,6 +290,8 @@ export interface AiJobFormStepDeps {
280
290
  readInventory?: typeof readSiteInventory;
281
291
  /** The platform's duplicate module, for a plan that starts from a copy. */
282
292
  duplicate?: typeof duplicateResource;
293
+ /** The data plugin's writer lookup the form's dataset is read through (AGL-3616); specs hand in a fake. */
294
+ datasetWriterFor?: AiFormDatasetReadDeps['writerFor'];
283
295
  }
284
296
  export declare function createAiJobFormStep(deps?: AiJobFormStepDeps): AiJobStepRunner;
285
297
  export declare const runAiJobFormStep: AiJobStepRunner;
@@ -14,7 +14,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { checkFormContract, formFieldsCanYieldAnEmail } from "@aglyn/aglyn/app-utils/form-contract";
17
+ */ import { throwIfAiJobCanceled } from "./ai-job-cancel.js";
18
+ import { checkFormContract, formFieldsCanYieldAnEmail } from "@aglyn/aglyn/app-utils/form-contract";
18
19
  import { collectFormFieldNodeIds, FORM_COMPONENT_ID, FORM_FIELD_COMPONENT_ID, FORM_ID_PROP, formFieldDeclsFromNodes, formNodeIdIn, isMarketingConsentFieldName, MARKETING_CONSENT_FORM_FIELD } from "@aglyn/aglyn/app-utils/forms";
19
20
  import { decodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
20
21
  import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
@@ -27,6 +28,7 @@ import { aiDoctrineTreeTool, runValidatedGeneration } from "../runtime/ai-doctri
27
28
  import { AI_PALETTE } from "../runtime/ai-palette.generated.js";
28
29
  import { readSiteInventory } from "../runtime/site-inventory.js";
29
30
  import { registerAiJobAdmission } from "./ai-job-admission.js";
31
+ import { AI_FORM_DATASET_MADE_INPUT, aiBindFormToDataset, aiFormDatasetNote, aiFormDatasetPromptLine, aiReadFormDataset } from "./ai-job-form-dataset.js";
30
32
  import { aiJobPlanCreationsRefusal } from "./ai-job-plan-creations.js";
31
33
  import { aiJobDraftId } from "./ai-job-draft-ids.js";
32
34
  import { aiDraftAdmissionRefusal, aiDraftAllowanceRefusal, aiSiteSubdomain, readAiDraft, writeAiDraft } from "./ai-job-drafts.js";
@@ -208,9 +210,11 @@ function oneLine(value, max) {
208
210
  }
209
211
  /**
210
212
  * The draft a validated design and its answer make: the form node bound to
211
- * `formId` and captioned `name`, no dataset binding, the platform's consent
212
- * field when the fields can yield an email address, the canvas root above,
213
- * and the declaration read off the result. Pure; the input tree is not
213
+ * `formId` and captioned `name`, the platform's consent field when the
214
+ * fields can yield an email address, the canvas root above, and the
215
+ * declaration read off the result. A dataset binding is made only where the
216
+ * plan said the form writes to one this step may bind (`identity.dataset`,
217
+ * AGL-3616); any the model drew is dropped. Pure; the input tree is not
214
218
  * changed.
215
219
  */ export function aiFormDraft(tree, answer, identity) {
216
220
  var _identity_decisions;
@@ -232,8 +236,8 @@ function oneLine(value, max) {
232
236
  let consentFieldName = null;
233
237
  if ((form == null ? void 0 : form.componentId) === FORM_COMPONENT_ID) {
234
238
  const props = form.props;
235
- // A dataset binding decides where every submission is also written; it
236
- // stays the person's choice, made on the Form element.
239
+ // A dataset binding decides where every submission is also written: the
240
+ // plan's to say (AGL-3616), bound below, never the model's to draw.
237
241
  delete props['datasetId'];
238
242
  delete props['datasetName'];
239
243
  props[FORM_ID_PROP] = identity.formId;
@@ -268,6 +272,12 @@ function oneLine(value, max) {
268
272
  }
269
273
  form.parentId = CANVAS_ROOT_ELEMENT_ID;
270
274
  }
275
+ // The dataset the plan says the form writes to (AGL-3616), bound as the
276
+ // Form element binds one.
277
+ const dataset = identity.dataset && (form == null ? void 0 : form.componentId) === FORM_COMPONENT_ID ? {
278
+ dataset: identity.dataset,
279
+ binding: aiBindFormToDataset(nodes, formNodeId, identity.dataset)
280
+ } : undefined;
271
281
  nodes[CANVAS_ROOT_ELEMENT_ID] = {
272
282
  $id: CANVAS_ROOT_ELEMENT_ID,
273
283
  componentId: 'div',
@@ -275,7 +285,7 @@ function oneLine(value, max) {
275
285
  formNodeId
276
286
  ]
277
287
  };
278
- return {
288
+ return _extends({
279
289
  nodes,
280
290
  rootId: CANVAS_ROOT_ELEMENT_ID,
281
291
  formNodeId,
@@ -285,7 +295,9 @@ function oneLine(value, max) {
285
295
  lead: true
286
296
  } : null,
287
297
  answer: parsed
288
- };
298
+ }, dataset ? {
299
+ dataset
300
+ } : {});
289
301
  }
290
302
  /**
291
303
  * What a draft would break once published, as building-rule violations
@@ -535,6 +547,7 @@ export function createAiJobFormStep(deps = {}) {
535
547
  const duplicate = (_deps_duplicate = deps.duplicate) != null ? _deps_duplicate : duplicateResource;
536
548
  return async ({ job, now, signal, firestore, modelFor })=>{
537
549
  var _ref, _orgSnapshot_data;
550
+ var _job_inputs;
538
551
  const hostId = job.hostId;
539
552
  if (!hostId) throw new Error('a form job names no site, and its admission refuses one');
540
553
  const output = (draft, extra = {})=>_extends({
@@ -661,10 +674,22 @@ export function createAiJobFormStep(deps = {}) {
661
674
  submissions: aiSiteSubmissions(job.inputs)
662
675
  };
663
676
  const { audience } = aiSiteWords(job.inputs);
677
+ // The dataset the plan says the form writes to (AGL-3616), where this
678
+ // workspace may bind one; otherwise the form is the form it always was.
679
+ const dataset = await aiReadFormDataset({
680
+ hostId,
681
+ id: creation == null ? void 0 : creation.writesTo,
682
+ org,
683
+ inventory,
684
+ madeId: (_job_inputs = job.inputs) == null ? void 0 : _job_inputs[AI_FORM_DATASET_MADE_INPUT]
685
+ }, deps.datasetWriterFor ? {
686
+ writerFor: deps.datasetWriterFor
687
+ } : {});
664
688
  const check = aiFormDraftCheck({
665
689
  formId: draftId,
666
690
  name,
667
- decisions
691
+ decisions,
692
+ dataset
668
693
  });
669
694
  const result = await runValidatedGeneration('form', _extends({
670
695
  step: 'job.form',
@@ -679,7 +704,10 @@ export function createAiJobFormStep(deps = {}) {
679
704
  audience,
680
705
  submissions: decisions.submissions
681
706
  }),
682
- ...sourceLines
707
+ ...sourceLines,
708
+ ...dataset ? [
709
+ aiFormDatasetPromptLine(dataset)
710
+ ] : []
683
711
  ].join('\n')
684
712
  }
685
713
  ],
@@ -699,6 +727,8 @@ export function createAiJobFormStep(deps = {}) {
699
727
  });
700
728
  const draft = check.draftFor(result.value);
701
729
  if (!draft) throw new Error('a form the doctrine admitted has no draft from its check');
730
+ // Never written once the job is canceled (AGL-3616).
731
+ throwIfAiJobCanceled(signal);
702
732
  const write = await writeAiDraft(firestore, {
703
733
  kind: 'form',
704
734
  hostId,
@@ -725,6 +755,7 @@ export function createAiJobFormStep(deps = {}) {
725
755
  // What the person decides next, then the facts the brief did not give (AGL-3056).
726
756
  const note = [
727
757
  aiFormOutputNote(draft.answer),
758
+ draft.dataset ? aiFormDatasetNote(draft.dataset.dataset, draft.dataset.binding) : null,
728
759
  aiBracketedFactsNote({
729
760
  tree: {
730
761
  rootId: result.value.rootId,