@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.219

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 (177) hide show
  1. package/README.md +2 -2
  2. package/package.json +17 -17
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js.map +1 -1
  5. package/src/lib/activity/ai-activity-actions.js +8 -1
  6. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  7. package/src/lib/components/ai-assist-provider.component.js +2 -2
  8. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  9. package/src/lib/components/ai-brief-dialog.component.js +3 -3
  10. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  11. package/src/lib/components/ai-experiment-cards.component.js +1 -1
  12. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  13. package/src/lib/components/ai-job-plan.component.d.ts +8 -0
  14. package/src/lib/components/ai-job-plan.component.js +21 -4
  15. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  16. package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
  17. package/src/lib/components/ai-permissions-on-host.component.js +12 -3
  18. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  19. package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
  20. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  21. package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
  22. package/src/lib/components/ai-product-import-option.component.js.map +1 -1
  23. package/src/lib/components/ai-product-zones.d.ts +124 -0
  24. package/src/lib/components/ai-product-zones.js +32 -0
  25. package/src/lib/components/ai-product-zones.js.map +1 -0
  26. package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
  27. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  28. package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
  29. package/src/lib/components/ai-products-proposals.component.js.map +1 -1
  30. package/src/lib/components/ai-seo-audit-card.component.js +35 -92
  31. package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
  32. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  33. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  34. package/src/lib/components/assist-jobs-drawer.component.js +21 -15
  35. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  36. package/src/lib/components/assist-panel.component.js +2 -2
  37. package/src/lib/components/assist-panel.component.js.map +1 -1
  38. package/src/lib/components/assist-signals-page.component.js +2 -2
  39. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  40. package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
  41. package/src/lib/jobs/ai-email-bindings.js +75 -42
  42. package/src/lib/jobs/ai-email-bindings.js.map +1 -1
  43. package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
  44. package/src/lib/jobs/ai-job-campaign-step.js +5 -10
  45. package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
  46. package/src/lib/jobs/ai-job-component-step.js +9 -3
  47. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  48. package/src/lib/jobs/ai-job-drafts.js +1 -1
  49. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  50. package/src/lib/jobs/ai-job-email-step.d.ts +0 -2
  51. package/src/lib/jobs/ai-job-email-step.js +1 -3
  52. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  53. package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
  54. package/src/lib/jobs/ai-job-experiment-step.js +1 -1
  55. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  56. package/src/lib/jobs/ai-job-generation.d.ts +1 -1
  57. package/src/lib/jobs/ai-job-generation.js +10 -2
  58. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  59. package/src/lib/jobs/ai-job-layout-step.js +1 -1
  60. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  61. package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
  62. package/src/lib/jobs/ai-job-page-sections.js +7 -4
  63. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  64. package/src/lib/jobs/ai-job-page-step.js +7 -2
  65. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  66. package/src/lib/jobs/ai-job-plan-conformance.js +33 -0
  67. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +3 -0
  69. package/src/lib/jobs/ai-job-plan-step.js +29 -17
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
  72. package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
  73. package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
  74. package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
  75. package/src/lib/jobs/ai-job-products-step.js +54 -32
  76. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  77. package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
  78. package/src/lib/jobs/ai-job-seo-step.js +35 -108
  79. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  80. package/src/lib/jobs/ai-job-template-step.js +1 -0
  81. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
  83. package/src/lib/jobs/ai-job-theme-step.js +1 -1
  84. package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
  85. package/src/lib/jobs/ai-job-workflow-step.js +17 -9
  86. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  87. package/src/lib/jobs/ai-workflow-records.d.ts +19 -11
  88. package/src/lib/jobs/ai-workflow-records.js +60 -27
  89. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  90. package/src/lib/model/ai-allotments.d.ts +11 -3
  91. package/src/lib/model/ai-allotments.js +21 -4
  92. package/src/lib/model/ai-allotments.js.map +1 -1
  93. package/src/lib/model/ai-automation-outline.d.ts +21 -2
  94. package/src/lib/model/ai-automation-outline.js.map +1 -1
  95. package/src/lib/model/ai-build-plan.d.ts +57 -0
  96. package/src/lib/model/ai-build-plan.js +116 -7
  97. package/src/lib/model/ai-build-plan.js.map +1 -1
  98. package/src/lib/model/ai-seo.d.ts +11 -51
  99. package/src/lib/model/ai-seo.js +0 -17
  100. package/src/lib/model/ai-seo.js.map +1 -1
  101. package/src/lib/model/ai-site-inventory.js +1 -1
  102. package/src/lib/model/ai-site-inventory.js.map +1 -1
  103. package/src/lib/model/ai-template-subjects.js +2 -2
  104. package/src/lib/model/ai-template-subjects.js.map +1 -1
  105. package/src/lib/model/ai-theme-proposal.d.ts +1 -1
  106. package/src/lib/model/ai-theme-proposal.js +1 -1
  107. package/src/lib/model/ai-theme-proposal.js.map +1 -1
  108. package/src/lib/model/ai-workflow-job.d.ts +1 -3
  109. package/src/lib/model/ai-workflow-job.js +1 -2
  110. package/src/lib/model/ai-workflow-job.js.map +1 -1
  111. package/src/lib/plan-entitlements.d.ts +149 -0
  112. package/src/lib/plan-entitlements.js +168 -0
  113. package/src/lib/plan-entitlements.js.map +1 -0
  114. package/src/lib/plugin.js +1 -1
  115. package/src/lib/plugin.js.map +1 -1
  116. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
  117. package/src/lib/runtime/ai-doctrine-validators.js +178 -21
  118. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  119. package/src/lib/runtime/ai-doctrine.js +5 -5
  120. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  121. package/src/lib/runtime/ai-eval-live.js +29 -14
  122. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  123. package/src/lib/runtime/ai-eval.d.ts +8 -1
  124. package/src/lib/runtime/ai-eval.js +29 -2
  125. package/src/lib/runtime/ai-eval.js.map +1 -1
  126. package/src/lib/runtime/ai-node-tree.js +3 -3
  127. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  128. package/src/lib/runtime/ai-palette.d.ts +6 -0
  129. package/src/lib/runtime/ai-palette.generated.js +15 -12
  130. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  131. package/src/lib/runtime/ai-palette.js +7 -1
  132. package/src/lib/runtime/ai-palette.js.map +1 -1
  133. package/src/lib/runtime/seo-audit.d.ts +13 -63
  134. package/src/lib/runtime/seo-audit.js +22 -207
  135. package/src/lib/runtime/seo-audit.js.map +1 -1
  136. package/src/lib/runtime/site-inventory.js +1 -1
  137. package/src/lib/runtime/site-inventory.js.map +1 -1
  138. package/src/lib/server/ai-assist-prompts.js +3 -3
  139. package/src/lib/server/ai-assist-prompts.js.map +1 -1
  140. package/src/lib/server/assist-chat.js +11 -10
  141. package/src/lib/server/assist-chat.js.map +1 -1
  142. package/src/lib/server/assist-view-context.js +23 -23
  143. package/src/lib/server/assist-view-context.js.map +1 -1
  144. package/src/lib/server.d.ts +1 -0
  145. package/src/lib/server.js.map +1 -1
  146. package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
  147. package/src/lib/testing/stand-in-automation-index.js +102 -0
  148. package/src/lib/testing/stand-in-automation-index.js.map +1 -0
  149. package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
  150. package/src/lib/testing/stand-in-list-send-time.js +77 -0
  151. package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
  152. package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
  153. package/src/lib/testing/stand-in-pipeline-index.js +59 -0
  154. package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
  155. package/src/lib/testing/stand-in-product-index.d.ts +20 -0
  156. package/src/lib/testing/stand-in-product-index.js +105 -0
  157. package/src/lib/testing/stand-in-product-index.js.map +1 -0
  158. package/src/lib/tools/ai-component-tool.js +2 -2
  159. package/src/lib/tools/ai-component-tool.js.map +1 -1
  160. package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
  161. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  162. package/src/lib/tools/ai-seo-tool.d.ts +6 -18
  163. package/src/lib/tools/ai-seo-tool.js +7 -38
  164. package/src/lib/tools/ai-seo-tool.js.map +1 -1
  165. package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
  166. package/src/lib/tools/ai-workflow-tool.js +1 -1
  167. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  168. package/src/lib/usage/ai-allotment-alerts.js +8 -1
  169. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  170. package/src/lib/usage/assist-free-taste.js +2 -1
  171. package/src/lib/usage/assist-free-taste.js.map +1 -1
  172. package/src/lib/usage-axes.d.ts +39 -0
  173. package/src/lib/usage-axes.js +70 -0
  174. package/src/lib/usage-axes.js.map +1 -0
  175. package/src/lib/runtime/seo-page-facts.d.ts +0 -61
  176. package/src/lib/runtime/seo-page-facts.js +0 -150
  177. package/src/lib/runtime/seo-page-facts.js.map +0 -1
@@ -0,0 +1,102 @@
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
+ */ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
17
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
18
+ /**
19
+ * The workflow, webhook and action indexes the plugin that keeps a site's
20
+ * automations publishes (AGL-3080), stood in over a spec's own Firestore
21
+ * double — this plugin may not load the workflows plugin that registers the
22
+ * real ones. It answers in the shape the workflows plugin's
23
+ * `automation-record-index.ts` documents: live records only, an unnamed
24
+ * workflow or webhook left out, an unnamed action named by its id, and a
25
+ * webhook's direction and switch but never its URL or secret. What the
26
+ * workflows plugin itself answers is held in its own spec.
27
+ */ const OWNER = 'workflows';
28
+ const COLLECTION = {
29
+ workflow: 'workflows',
30
+ webhook: 'webhooks',
31
+ action: 'actions'
32
+ };
33
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
34
+ function record(kind, id, data) {
35
+ var _data_trigger;
36
+ if (!data || data['deletedAt'] != null) return null;
37
+ const name = str(data['name']);
38
+ const steps = Array.isArray(data['steps']) ? data['steps'] : [];
39
+ if (kind === 'action') {
40
+ var _data_trigger1;
41
+ return {
42
+ id,
43
+ name: name || id,
44
+ facts: {
45
+ trigger: (_data_trigger1 = data['trigger']) != null ? _data_trigger1 : null,
46
+ steps,
47
+ enabled: data['enabled'] !== false
48
+ }
49
+ };
50
+ }
51
+ if (!name) return null;
52
+ if (kind === 'webhook') {
53
+ return {
54
+ id,
55
+ name,
56
+ facts: {
57
+ direction: data['direction'],
58
+ enabled: data['enabled'] !== false
59
+ }
60
+ };
61
+ }
62
+ return {
63
+ id,
64
+ name,
65
+ facts: {
66
+ steps,
67
+ returnValue: str(data['returnValue']) || null,
68
+ trigger: (_data_trigger = data['trigger']) != null ? _data_trigger : null
69
+ }
70
+ };
71
+ }
72
+ /** Registers all three, reading through `firestore`. */ export function standInAutomationIndexes(firestore) {
73
+ for (const kind of Object.keys(COLLECTION)){
74
+ const site = (hostId)=>firestore.collection('hosts').doc(hostId).collection(COLLECTION[kind]);
75
+ registerPluginRecordIndex(kind, {
76
+ async list ({ hostId, limit }) {
77
+ if (!hostId) return {
78
+ records: [],
79
+ truncated: false
80
+ };
81
+ const snapshot = await site(hostId).limit(limit + 1).get();
82
+ const records = snapshot.docs.map((doc)=>record(kind, doc.id, doc.data())).filter((one)=>one !== null);
83
+ return {
84
+ records: records.slice(0, limit),
85
+ truncated: snapshot.docs.length > limit
86
+ };
87
+ },
88
+ async get ({ hostId, id }) {
89
+ if (!hostId) return null;
90
+ const snapshot = await site(hostId).doc(id).get();
91
+ return snapshot.exists ? record(kind, snapshot.id, snapshot.data()) : null;
92
+ }
93
+ }, {
94
+ pluginId: OWNER
95
+ });
96
+ }
97
+ }
98
+ /** Forgets all three, so the next spec starts with no plugin keeping automations. */ export function removeStandInAutomationIndexes() {
99
+ unregisterPluginServices(OWNER);
100
+ }
101
+
102
+ //# sourceMappingURL=stand-in-automation-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-automation-index.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 {\n registerPluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The workflow, webhook and action indexes the plugin that keeps a site's\n * automations publishes (AGL-3080), stood in over a spec's own Firestore\n * double — this plugin may not load the workflows plugin that registers the\n * real ones. It answers in the shape the workflows plugin's\n * `automation-record-index.ts` documents: live records only, an unnamed\n * workflow or webhook left out, an unnamed action named by its id, and a\n * webhook's direction and switch but never its URL or secret. What the\n * workflows plugin itself answers is held in its own spec.\n */\n\nconst OWNER = 'workflows'\n\ntype Kind = 'workflow' | 'webhook' | 'action'\n\nconst COLLECTION: Record<Kind, string> = { workflow: 'workflows', webhook: 'webhooks', action: 'actions' }\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction record(kind: Kind, id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const name = str(data['name'])\n const steps = Array.isArray(data['steps']) ? data['steps'] : []\n if (kind === 'action') {\n return { id, name: name || id, facts: { trigger: data['trigger'] ?? null, steps, enabled: data['enabled'] !== false } }\n }\n if (!name) return null\n if (kind === 'webhook') {\n return { id, name, facts: { direction: data['direction'], enabled: data['enabled'] !== false } }\n }\n return { id, name, facts: { steps, returnValue: str(data['returnValue']) || null, trigger: data['trigger'] ?? null } }\n}\n\n/** Registers all three, reading through `firestore`. */\nexport function standInAutomationIndexes(firestore: FirebaseFirestore.Firestore): void {\n for (const kind of Object.keys(COLLECTION) as Kind[]) {\n const site = (hostId: string) => firestore.collection('hosts').doc(hostId).collection(COLLECTION[kind])\n registerPluginRecordIndex(\n kind,\n {\n async list({ hostId, limit }) {\n if (!hostId) return { records: [], truncated: false }\n const snapshot = await site(hostId).limit(limit + 1).get()\n const records = snapshot.docs\n .map((doc) => record(kind, doc.id, doc.data()))\n .filter((one): one is PluginIndexedRecord => one !== null)\n return { records: records.slice(0, limit), truncated: snapshot.docs.length > limit }\n },\n async get({ hostId, id }) {\n if (!hostId) return null\n const snapshot = await site(hostId).doc(id).get()\n return snapshot.exists ? record(kind, snapshot.id, snapshot.data()) : null\n },\n },\n { pluginId: OWNER },\n )\n }\n}\n\n/** Forgets all three, so the next spec starts with no plugin keeping automations. */\nexport function removeStandInAutomationIndexes(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["registerPluginRecordIndex","unregisterPluginServices","OWNER","COLLECTION","workflow","webhook","action","str","value","trim","record","kind","id","data","name","steps","Array","isArray","facts","trigger","enabled","direction","returnValue","standInAutomationIndexes","firestore","Object","keys","site","hostId","collection","doc","list","limit","records","truncated","snapshot","get","docs","map","filter","one","slice","length","exists","pluginId","removeStandInAutomationIndexes"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,yBAAyB,QAEpB,kDAAiD;AACxD,SAASC,wBAAwB,QAAQ,8CAA6C;AAEtF;;;;;;;;;CASC,GAED,MAAMC,QAAQ;AAId,MAAMC,aAAmC;IAAEC,UAAU;IAAaC,SAAS;IAAYC,QAAQ;AAAU;AAEzG,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,SAASC,OAAOC,IAAU,EAAEC,EAAU,EAAEC,IAAyC;QAWYA;IAV3F,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,OAAOP,IAAIM,IAAI,CAAC,OAAO;IAC7B,MAAME,QAAQC,MAAMC,OAAO,CAACJ,IAAI,CAAC,QAAQ,IAAIA,IAAI,CAAC,QAAQ,GAAG,EAAE;IAC/D,IAAIF,SAAS,UAAU;YAC4BE;QAAjD,OAAO;YAAED;YAAIE,MAAMA,QAAQF;YAAIM,OAAO;gBAAEC,OAAO,GAAEN,iBAAAA,IAAI,CAAC,UAAU,YAAfA,iBAAmB;gBAAME;gBAAOK,SAASP,IAAI,CAAC,UAAU,KAAK;YAAM;QAAE;IACxH;IACA,IAAI,CAACC,MAAM,OAAO;IAClB,IAAIH,SAAS,WAAW;QACtB,OAAO;YAAEC;YAAIE;YAAMI,OAAO;gBAAEG,WAAWR,IAAI,CAAC,YAAY;gBAAEO,SAASP,IAAI,CAAC,UAAU,KAAK;YAAM;QAAE;IACjG;IACA,OAAO;QAAED;QAAIE;QAAMI,OAAO;YAAEH;YAAOO,aAAaf,IAAIM,IAAI,CAAC,cAAc,KAAK;YAAMM,OAAO,GAAEN,gBAAAA,IAAI,CAAC,UAAU,YAAfA,gBAAmB;QAAK;IAAE;AACvH;AAEA,sDAAsD,GACtD,OAAO,SAASU,yBAAyBC,SAAsC;IAC7E,KAAK,MAAMb,QAAQc,OAAOC,IAAI,CAACvB,YAAuB;QACpD,MAAMwB,OAAO,CAACC,SAAmBJ,UAAUK,UAAU,CAAC,SAASC,GAAG,CAACF,QAAQC,UAAU,CAAC1B,UAAU,CAACQ,KAAK;QACtGX,0BACEW,MACA;YACE,MAAMoB,MAAK,EAAEH,MAAM,EAAEI,KAAK,EAAE;gBAC1B,IAAI,CAACJ,QAAQ,OAAO;oBAAEK,SAAS,EAAE;oBAAEC,WAAW;gBAAM;gBACpD,MAAMC,WAAW,MAAMR,KAAKC,QAAQI,KAAK,CAACA,QAAQ,GAAGI,GAAG;gBACxD,MAAMH,UAAUE,SAASE,IAAI,CAC1BC,GAAG,CAAC,CAACR,MAAQpB,OAAOC,MAAMmB,IAAIlB,EAAE,EAAEkB,IAAIjB,IAAI,KAC1C0B,MAAM,CAAC,CAACC,MAAoCA,QAAQ;gBACvD,OAAO;oBAAEP,SAASA,QAAQQ,KAAK,CAAC,GAAGT;oBAAQE,WAAWC,SAASE,IAAI,CAACK,MAAM,GAAGV;gBAAM;YACrF;YACA,MAAMI,KAAI,EAAER,MAAM,EAAEhB,EAAE,EAAE;gBACtB,IAAI,CAACgB,QAAQ,OAAO;gBACpB,MAAMO,WAAW,MAAMR,KAAKC,QAAQE,GAAG,CAAClB,IAAIwB,GAAG;gBAC/C,OAAOD,SAASQ,MAAM,GAAGjC,OAAOC,MAAMwB,SAASvB,EAAE,EAAEuB,SAAStB,IAAI,MAAM;YACxE;QACF,GACA;YAAE+B,UAAU1C;QAAM;IAEtB;AACF;AAEA,mFAAmF,GACnF,OAAO,SAAS2C;IACd5C,yBAAyBC;AAC3B"}
@@ -0,0 +1,4 @@
1
+ /** Registers the reader, reading through `firestore`. */
2
+ export declare function standInListSendTime(firestore: FirebaseFirestore.Firestore): void;
3
+ /** Forgets it, so the next spec starts with nothing keeping sends. */
4
+ export declare function removeStandInListSendTime(): void;
@@ -0,0 +1,77 @@
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
+ */ import { registerPluginRecordFactsReader } from "@aglyn/aglyn/plugin-manager/plugin-record-facts";
17
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
18
+ import { AI_LIST_SEND_TIME_RESOURCE } from "../jobs/ai-email-bindings.js";
19
+ /**
20
+ * A list's send time, as the plugin that keeps a site's sends answers it
21
+ * (AGL-3080), stood in over a spec's own Firestore double — this plugin may
22
+ * not load the marketing plugin that registers the real reader. It answers in
23
+ * the shape that reader documents, `{ label, measured }`, from the sends to
24
+ * the list AS the site asked about. Which slot the real rule picks, and why,
25
+ * is held in the marketing plugin's own spec; this answers the slot of the
26
+ * first measurable send, with three of them the least it suggests from.
27
+ */ const OWNER = 'stand-in-send-history';
28
+ const WEEKDAYS = [
29
+ 'Sunday',
30
+ 'Monday',
31
+ 'Tuesday',
32
+ 'Wednesday',
33
+ 'Thursday',
34
+ 'Friday',
35
+ 'Saturday'
36
+ ];
37
+ const millisOf = (value)=>value instanceof Date ? value.getTime() : typeof value === 'number' ? value : 0;
38
+ /** Registers the reader, reading through `firestore`. */ export function standInListSendTime(firestore) {
39
+ registerPluginRecordFactsReader(AI_LIST_SEND_TIME_RESOURCE, {
40
+ async read ({ orgId, hostId, id }) {
41
+ if (!hostId) return {
42
+ ok: false,
43
+ status: 400,
44
+ error: 'A send time is read for one site.'
45
+ };
46
+ const snapshot = await firestore.collection('orgs').doc(orgId).collection('campaigns').where('hostId', '==', hostId).where('listId', '==', id).where('status', '==', 'sent').get();
47
+ const measured = snapshot.docs.filter((doc)=>{
48
+ var _doc_get;
49
+ return Number((_doc_get = doc.get('stats.delivered')) != null ? _doc_get : 0) >= 20;
50
+ });
51
+ if (measured.length < 3) return {
52
+ ok: true,
53
+ facts: {
54
+ label: null,
55
+ measured: 0
56
+ }
57
+ };
58
+ const at = new Date(millisOf(measured[0].get('sentAt')));
59
+ const hour = at.getUTCHours();
60
+ const label = `${WEEKDAYS[at.getUTCDay()]}s around ${hour % 12 || 12} ${hour < 12 ? 'AM' : 'PM'} (UTC)`;
61
+ return {
62
+ ok: true,
63
+ facts: {
64
+ label,
65
+ measured: measured.length
66
+ }
67
+ };
68
+ }
69
+ }, {
70
+ pluginId: OWNER
71
+ });
72
+ }
73
+ /** Forgets it, so the next spec starts with nothing keeping sends. */ export function removeStandInListSendTime() {
74
+ unregisterPluginServices(OWNER);
75
+ }
76
+
77
+ //# sourceMappingURL=stand-in-list-send-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-list-send-time.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 */\nimport { registerPluginRecordFactsReader } from '@aglyn/aglyn/plugin-manager/plugin-record-facts'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { AI_LIST_SEND_TIME_RESOURCE } from '../jobs/ai-email-bindings'\n\n/**\n * A list's send time, as the plugin that keeps a site's sends answers it\n * (AGL-3080), stood in over a spec's own Firestore double — this plugin may\n * not load the marketing plugin that registers the real reader. It answers in\n * the shape that reader documents, `{ label, measured }`, from the sends to\n * the list AS the site asked about. Which slot the real rule picks, and why,\n * is held in the marketing plugin's own spec; this answers the slot of the\n * first measurable send, with three of them the least it suggests from.\n */\n\nconst OWNER = 'stand-in-send-history'\n\nconst WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\nconst millisOf = (value: unknown): number =>\n value instanceof Date ? value.getTime() : typeof value === 'number' ? value : 0\n\n/** Registers the reader, reading through `firestore`. */\nexport function standInListSendTime(firestore: FirebaseFirestore.Firestore): void {\n registerPluginRecordFactsReader(\n AI_LIST_SEND_TIME_RESOURCE,\n {\n async read({ orgId, hostId, id }) {\n if (!hostId) return { ok: false, status: 400, error: 'A send time is read for one site.' }\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection('campaigns')\n .where('hostId', '==', hostId)\n .where('listId', '==', id)\n .where('status', '==', 'sent')\n .get()\n const measured = snapshot.docs.filter((doc) => Number(doc.get('stats.delivered') ?? 0) >= 20)\n if (measured.length < 3) return { ok: true, facts: { label: null, measured: 0 } }\n const at = new Date(millisOf(measured[0].get('sentAt')))\n const hour = at.getUTCHours()\n const label = `${WEEKDAYS[at.getUTCDay()]}s around ${hour % 12 || 12} ${hour < 12 ? 'AM' : 'PM'} (UTC)`\n return { ok: true, facts: { label, measured: measured.length } }\n },\n },\n { pluginId: OWNER },\n )\n}\n\n/** Forgets it, so the next spec starts with nothing keeping sends. */\nexport function removeStandInListSendTime(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["registerPluginRecordFactsReader","unregisterPluginServices","AI_LIST_SEND_TIME_RESOURCE","OWNER","WEEKDAYS","millisOf","value","Date","getTime","standInListSendTime","firestore","read","orgId","hostId","id","ok","status","error","snapshot","collection","doc","where","get","measured","docs","filter","Number","length","facts","label","at","hour","getUTCHours","getUTCDay","pluginId","removeStandInListSendTime"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,+BAA+B,QAAQ,kDAAiD;AACjG,SAASC,wBAAwB,QAAQ,8CAA6C;AACtF,SAASC,0BAA0B,QAAQ,+BAA2B;AAEtE;;;;;;;;CAQC,GAED,MAAMC,QAAQ;AAEd,MAAMC,WAAW;IAAC;IAAU;IAAU;IAAW;IAAa;IAAY;IAAU;CAAW;AAE/F,MAAMC,WAAW,CAACC,QAChBA,iBAAiBC,OAAOD,MAAME,OAAO,KAAK,OAAOF,UAAU,WAAWA,QAAQ;AAEhF,uDAAuD,GACvD,OAAO,SAASG,oBAAoBC,SAAsC;IACxEV,gCACEE,4BACA;QACE,MAAMS,MAAK,EAAEC,KAAK,EAAEC,MAAM,EAAEC,EAAE,EAAE;YAC9B,IAAI,CAACD,QAAQ,OAAO;gBAAEE,IAAI;gBAAOC,QAAQ;gBAAKC,OAAO;YAAoC;YACzF,MAAMC,WAAW,MAAMR,UACpBS,UAAU,CAAC,QACXC,GAAG,CAACR,OACJO,UAAU,CAAC,aACXE,KAAK,CAAC,UAAU,MAAMR,QACtBQ,KAAK,CAAC,UAAU,MAAMP,IACtBO,KAAK,CAAC,UAAU,MAAM,QACtBC,GAAG;YACN,MAAMC,WAAWL,SAASM,IAAI,CAACC,MAAM,CAAC,CAACL;oBAAeA;uBAAPM,QAAON,WAAAA,IAAIE,GAAG,CAAC,8BAARF,WAA8B,MAAM;;YAC1F,IAAIG,SAASI,MAAM,GAAG,GAAG,OAAO;gBAAEZ,IAAI;gBAAMa,OAAO;oBAAEC,OAAO;oBAAMN,UAAU;gBAAE;YAAE;YAChF,MAAMO,KAAK,IAAIvB,KAAKF,SAASkB,QAAQ,CAAC,EAAE,CAACD,GAAG,CAAC;YAC7C,MAAMS,OAAOD,GAAGE,WAAW;YAC3B,MAAMH,QAAQ,GAAGzB,QAAQ,CAAC0B,GAAGG,SAAS,GAAG,CAAC,SAAS,EAAEF,OAAO,MAAM,GAAG,CAAC,EAAEA,OAAO,KAAK,OAAO,KAAK,MAAM,CAAC;YACvG,OAAO;gBAAEhB,IAAI;gBAAMa,OAAO;oBAAEC;oBAAON,UAAUA,SAASI,MAAM;gBAAC;YAAE;QACjE;IACF,GACA;QAAEO,UAAU/B;IAAM;AAEtB;AAEA,oEAAoE,GACpE,OAAO,SAASgC;IACdlC,yBAAyBE;AAC3B"}
@@ -0,0 +1,32 @@
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 { type PluginRecordIndexScope } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
18
+ export interface StoodInPipelineIndex {
19
+ /** Every `list` and `get` asked, in order. */
20
+ asked: Array<PluginRecordIndexScope & {
21
+ limit?: number;
22
+ id?: string;
23
+ }>;
24
+ }
25
+ /** Registers the index, answering `pipelines` to every site of every org. */
26
+ export declare function standInPipelineIndex(pipelines: ReadonlyArray<{
27
+ id: string;
28
+ name: string;
29
+ stages: ReadonlyArray<Record<string, unknown>>;
30
+ }>): StoodInPipelineIndex;
31
+ /** Forgets it, so the next spec starts with no plugin keeping pipelines. */
32
+ export declare function removeStandInPipelineIndex(): void;
@@ -0,0 +1,59 @@
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
+ */ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
17
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
18
+ /**
19
+ * The `pipeline` index the plugin that keeps pipelines publishes (AGL-3080),
20
+ * stood in with the answers a spec hands it — this plugin may not load the
21
+ * CRM that registers the real one. It answers in the shape the CRM's
22
+ * `pipeline-record-index.ts` documents (live pipelines, `facts.stages`), and
23
+ * records every question, so a spec can say what was asked. What the CRM
24
+ * itself answers is held in its own spec.
25
+ */ const OWNER = 'crm';
26
+ /** Registers the index, answering `pipelines` to every site of every org. */ export function standInPipelineIndex(pipelines) {
27
+ const stood = {
28
+ asked: []
29
+ };
30
+ const records = pipelines.map((pipeline)=>({
31
+ id: pipeline.id,
32
+ name: pipeline.name,
33
+ facts: {
34
+ stages: pipeline.stages
35
+ }
36
+ }));
37
+ registerPluginRecordIndex('pipeline', {
38
+ async list (request) {
39
+ stood.asked.push(request);
40
+ return {
41
+ records: records.slice(0, request.limit),
42
+ truncated: records.length > request.limit
43
+ };
44
+ },
45
+ async get (request) {
46
+ var _records_find;
47
+ stood.asked.push(request);
48
+ return (_records_find = records.find((record)=>record.id === request.id)) != null ? _records_find : null;
49
+ }
50
+ }, {
51
+ pluginId: OWNER
52
+ });
53
+ return stood;
54
+ }
55
+ /** Forgets it, so the next spec starts with no plugin keeping pipelines. */ export function removeStandInPipelineIndex() {
56
+ unregisterPluginServices(OWNER);
57
+ }
58
+
59
+ //# sourceMappingURL=stand-in-pipeline-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-pipeline-index.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 {\n registerPluginRecordIndex,\n type PluginIndexedRecord,\n type PluginRecordIndexScope,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The `pipeline` index the plugin that keeps pipelines publishes (AGL-3080),\n * stood in with the answers a spec hands it — this plugin may not load the\n * CRM that registers the real one. It answers in the shape the CRM's\n * `pipeline-record-index.ts` documents (live pipelines, `facts.stages`), and\n * records every question, so a spec can say what was asked. What the CRM\n * itself answers is held in its own spec.\n */\n\nconst OWNER = 'crm'\n\nexport interface StoodInPipelineIndex {\n /** Every `list` and `get` asked, in order. */\n asked: Array<PluginRecordIndexScope & { limit?: number; id?: string }>\n}\n\n/** Registers the index, answering `pipelines` to every site of every org. */\nexport function standInPipelineIndex(\n pipelines: ReadonlyArray<{ id: string; name: string; stages: ReadonlyArray<Record<string, unknown>> }>,\n): StoodInPipelineIndex {\n const stood: StoodInPipelineIndex = { asked: [] }\n const records: PluginIndexedRecord[] = pipelines.map((pipeline) => ({\n id: pipeline.id,\n name: pipeline.name,\n facts: { stages: pipeline.stages },\n }))\n registerPluginRecordIndex(\n 'pipeline',\n {\n async list(request) {\n stood.asked.push(request)\n return { records: records.slice(0, request.limit), truncated: records.length > request.limit }\n },\n async get(request) {\n stood.asked.push(request)\n return records.find((record) => record.id === request.id) ?? null\n },\n },\n { pluginId: OWNER },\n )\n return stood\n}\n\n/** Forgets it, so the next spec starts with no plugin keeping pipelines. */\nexport function removeStandInPipelineIndex(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["registerPluginRecordIndex","unregisterPluginServices","OWNER","standInPipelineIndex","pipelines","stood","asked","records","map","pipeline","id","name","facts","stages","list","request","push","slice","limit","truncated","length","get","find","record","pluginId","removeStandInPipelineIndex"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,yBAAyB,QAGpB,kDAAiD;AACxD,SAASC,wBAAwB,QAAQ,8CAA6C;AAEtF;;;;;;;CAOC,GAED,MAAMC,QAAQ;AAOd,2EAA2E,GAC3E,OAAO,SAASC,qBACdC,SAAsG;IAEtG,MAAMC,QAA8B;QAAEC,OAAO,EAAE;IAAC;IAChD,MAAMC,UAAiCH,UAAUI,GAAG,CAAC,CAACC,WAAc,CAAA;YAClEC,IAAID,SAASC,EAAE;YACfC,MAAMF,SAASE,IAAI;YACnBC,OAAO;gBAAEC,QAAQJ,SAASI,MAAM;YAAC;QACnC,CAAA;IACAb,0BACE,YACA;QACE,MAAMc,MAAKC,OAAO;YAChBV,MAAMC,KAAK,CAACU,IAAI,CAACD;YACjB,OAAO;gBAAER,SAASA,QAAQU,KAAK,CAAC,GAAGF,QAAQG,KAAK;gBAAGC,WAAWZ,QAAQa,MAAM,GAAGL,QAAQG,KAAK;YAAC;QAC/F;QACA,MAAMG,KAAIN,OAAO;gBAERR;YADPF,MAAMC,KAAK,CAACU,IAAI,CAACD;YACjB,QAAOR,gBAAAA,QAAQe,IAAI,CAAC,CAACC,SAAWA,OAAOb,EAAE,KAAKK,QAAQL,EAAE,aAAjDH,gBAAsD;QAC/D;IACF,GACA;QAAEiB,UAAUtB;IAAM;IAEpB,OAAOG;AACT;AAEA,0EAA0E,GAC1E,OAAO,SAASoB;IACdxB,yBAAyBC;AAC3B"}
@@ -0,0 +1,20 @@
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
+ /** Registers both indexes, reading through `firestore`. */
18
+ export declare function standInProductIndexes(firestore: FirebaseFirestore.Firestore): void;
19
+ /** Forgets both, so the next spec starts with no plugin keeping products. */
20
+ export declare function removeStandInProductIndexes(): void;
@@ -0,0 +1,105 @@
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
+ */ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
17
+ import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
18
+ /**
19
+ * The product and category indexes a plugin that keeps products publishes
20
+ * (AGL-3080), stood in over a spec's own Firestore double — this plugin may
21
+ * not load the commerce plugin that registers the real ones. It answers in
22
+ * the shape the commerce plugin's `product-record-index.ts` documents: live,
23
+ * named records only, with the product facts its docblock lists. What the
24
+ * commerce plugin itself answers is held in its own spec.
25
+ */ const OWNER = 'commerce';
26
+ const str = (value)=>typeof value === 'string' ? value : '';
27
+ const strings = (value)=>(Array.isArray(value) ? value : []).map(str).filter(Boolean);
28
+ function product(id, data) {
29
+ var _data_seo;
30
+ if (!data || data['deletedAt'] != null || !str(data['name'])) return null;
31
+ const seo = (_data_seo = data['seo']) != null ? _data_seo : {};
32
+ const mediaUrls = Array.isArray(data['mediaUrls']) ? data['mediaUrls'] : [];
33
+ return {
34
+ id,
35
+ name: str(data['name']),
36
+ facts: {
37
+ type: data['type'],
38
+ description: str(data['description']),
39
+ tags: strings(data['tags']),
40
+ categoryIds: strings(data['categoryIds']),
41
+ options: (Array.isArray(data['options']) ? data['options'] : []).map((option)=>{
42
+ const record = option != null ? option : {};
43
+ return {
44
+ name: str(record['name']),
45
+ values: strings(record['values'])
46
+ };
47
+ }),
48
+ imageUrl: str(mediaUrls[0]) || str(data['imageUrl']) || null,
49
+ seoTitle: str(seo['title']),
50
+ seoDescription: str(seo['description'])
51
+ }
52
+ };
53
+ }
54
+ /** Registers both indexes, reading through `firestore`. */ export function standInProductIndexes(firestore) {
55
+ const site = (hostId, name)=>firestore.collection('hosts').doc(hostId).collection(name);
56
+ registerPluginRecordIndex('product', {
57
+ async list ({ hostId, limit }) {
58
+ if (!hostId) return {
59
+ records: [],
60
+ truncated: false
61
+ };
62
+ const snapshot = await site(hostId, 'products').limit(limit + 1).get();
63
+ const records = snapshot.docs.map((doc)=>product(doc.id, doc.data())).filter((record)=>record !== null);
64
+ return {
65
+ records: records.slice(0, limit),
66
+ truncated: snapshot.docs.length > limit
67
+ };
68
+ },
69
+ async get ({ hostId, id }) {
70
+ if (!hostId) return null;
71
+ const snapshot = await site(hostId, 'products').doc(id).get();
72
+ return snapshot.exists ? product(snapshot.id, snapshot.data()) : null;
73
+ }
74
+ }, {
75
+ pluginId: OWNER
76
+ });
77
+ registerPluginRecordIndex('productCategory', {
78
+ async list ({ hostId, limit }) {
79
+ if (!hostId) return {
80
+ records: [],
81
+ truncated: false
82
+ };
83
+ const snapshot = await site(hostId, 'productCategories').limit(limit + 1).get();
84
+ const records = snapshot.docs.map((doc)=>({
85
+ id: doc.id,
86
+ name: str(doc.get('name')),
87
+ facts: {}
88
+ })).filter((record)=>record.name);
89
+ return {
90
+ records: records.slice(0, limit),
91
+ truncated: snapshot.docs.length > limit
92
+ };
93
+ },
94
+ async get () {
95
+ return null;
96
+ }
97
+ }, {
98
+ pluginId: OWNER
99
+ });
100
+ }
101
+ /** Forgets both, so the next spec starts with no plugin keeping products. */ export function removeStandInProductIndexes() {
102
+ unregisterPluginServices(OWNER);
103
+ }
104
+
105
+ //# sourceMappingURL=stand-in-product-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/testing/stand-in-product-index.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 {\n registerPluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\n\n/**\n * The product and category indexes a plugin that keeps products publishes\n * (AGL-3080), stood in over a spec's own Firestore double — this plugin may\n * not load the commerce plugin that registers the real ones. It answers in\n * the shape the commerce plugin's `product-record-index.ts` documents: live,\n * named records only, with the product facts its docblock lists. What the\n * commerce plugin itself answers is held in its own spec.\n */\n\nconst OWNER = 'commerce'\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value : '')\nconst strings = (value: unknown): string[] =>\n (Array.isArray(value) ? value : []).map(str).filter(Boolean)\n\nfunction product(id: string, data: Record<string, unknown> | undefined): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null || !str(data['name'])) return null\n const seo = (data['seo'] ?? {}) as Record<string, unknown>\n const mediaUrls = Array.isArray(data['mediaUrls']) ? data['mediaUrls'] : []\n return {\n id,\n name: str(data['name']),\n facts: {\n type: data['type'],\n description: str(data['description']),\n tags: strings(data['tags']),\n categoryIds: strings(data['categoryIds']),\n options: (Array.isArray(data['options']) ? data['options'] : []).map((option) => {\n const record = (option ?? {}) as Record<string, unknown>\n return { name: str(record['name']), values: strings(record['values']) }\n }),\n imageUrl: str(mediaUrls[0]) || str(data['imageUrl']) || null,\n seoTitle: str(seo['title']),\n seoDescription: str(seo['description']),\n },\n }\n}\n\n/** Registers both indexes, reading through `firestore`. */\nexport function standInProductIndexes(firestore: FirebaseFirestore.Firestore): void {\n const site = (hostId: string, name: string) =>\n firestore.collection('hosts').doc(hostId).collection(name)\n registerPluginRecordIndex(\n 'product',\n {\n async list({ hostId, limit }) {\n if (!hostId) return { records: [], truncated: false }\n const snapshot = await site(hostId, 'products').limit(limit + 1).get()\n const records = snapshot.docs\n .map((doc) => product(doc.id, doc.data()))\n .filter((record): record is PluginIndexedRecord => record !== null)\n return { records: records.slice(0, limit), truncated: snapshot.docs.length > limit }\n },\n async get({ hostId, id }) {\n if (!hostId) return null\n const snapshot = await site(hostId, 'products').doc(id).get()\n return snapshot.exists ? product(snapshot.id, snapshot.data()) : null\n },\n },\n { pluginId: OWNER },\n )\n registerPluginRecordIndex(\n 'productCategory',\n {\n async list({ hostId, limit }) {\n if (!hostId) return { records: [], truncated: false }\n const snapshot = await site(hostId, 'productCategories').limit(limit + 1).get()\n const records = snapshot.docs\n .map((doc) => ({ id: doc.id, name: str(doc.get('name')), facts: {} }))\n .filter((record) => record.name)\n return { records: records.slice(0, limit), truncated: snapshot.docs.length > limit }\n },\n async get() {\n return null\n },\n },\n { pluginId: OWNER },\n )\n}\n\n/** Forgets both, so the next spec starts with no plugin keeping products. */\nexport function removeStandInProductIndexes(): void {\n unregisterPluginServices(OWNER)\n}\n"],"names":["registerPluginRecordIndex","unregisterPluginServices","OWNER","str","value","strings","Array","isArray","map","filter","Boolean","product","id","data","seo","mediaUrls","name","facts","type","description","tags","categoryIds","options","option","record","values","imageUrl","seoTitle","seoDescription","standInProductIndexes","firestore","site","hostId","collection","doc","list","limit","records","truncated","snapshot","get","docs","slice","length","exists","pluginId","removeStandInProductIndexes"],"mappings":"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"}
@@ -132,7 +132,7 @@ const ANSWER_MAX = 100;
132
132
  },
133
133
  defaultValue: {
134
134
  type: 'string',
135
- description: 'What the component shows until a page sets it: copy for text and richText, a screen id from the site inventory or "" for href, "" for image and icon, a number for number, "true" or "false" for boolean, one answer’s value for choice.'
135
+ description: 'What the component shows until a page sets it: copy for text and richText, a page id from the site inventory or "" for href, "" for image and icon, a number for number, "true" or "false" for boolean, one answer’s value for choice.'
136
136
  },
137
137
  options: {
138
138
  type: 'array',
@@ -210,7 +210,7 @@ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.
210
210
  return scope.screenIds.has(value) || isRootRelativePath(value) || isHttpsUrl(value) ? {
211
211
  value
212
212
  } : {
213
- error: 'is neither a screen id from the site inventory, a path on this site nor an https: address',
213
+ error: 'is neither a page id from the site inventory, a path on this site nor an https: address',
214
214
  rule: 1
215
215
  };
216
216
  case 'number':
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-component-tool.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 { parseMediaRef } from '@aglyn/aglyn/app-utils/media-ref'\nimport { COMPONENT_PROP_NAME_PATTERN } from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { FieldComponentType } from '@aglyn/aglyn/foundation/definitions/components.types'\nimport type {\n ReusableComponentProp,\n ReusableComponentPropOption,\n ReusableComponentPropType,\n ReusableComponentPropValue,\n} from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport { REUSABLE_PROP_KINDS } from '@aglyn/aglyn/foundation/definitions/property-kinds'\nimport type { AiTool } from '../providers/contract'\nimport { aiDoctrineTreeTool } from '../runtime/ai-doctrine'\nimport type {\n AiDoctrineRuleNumber,\n AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { HOSTILE_TEXT, isHttpsUrl, isRootRelativePath } from '../runtime/ai-node-tree'\nimport { AI_TEXT_LIMITS } from '../runtime/ai-palette'\n\n/**\n * The tool a generated reusable component arrives through (AGL-2908), and\n * the reading of the properties it declares.\n *\n * ── The kinds are the Properties dialog's ────────────────────────────────\n *\n * FILE ▸ Properties… declares a property as one of `REUSABLE_PROP_KINDS`,\n * and so does this tool: the kinds it offers are that table less the ones\n * `AI_COMPONENT_PROP_KINDS_NOT_OFFERED` names, each with the reason it is\n * left out, and a spec holds the two in both directions. A kind added to the\n * dialog does not compile here until it is offered or given a reason.\n *\n * What is offered is what a generated element can carry and a brief can\n * fill: copy (Text, Long text), a picture (Image), a link (Link), an icon\n * the site owner picks (Icon), a number (Number), a switch or a part's\n * visibility (Yes / no), and a setting with fixed answers (Choice).\n *\n * ── A declaration as the dialog saves one ────────────────────────────────\n *\n * `readAiComponentProps` returns each property in the shape the dialog's\n * cleaner stores: trimmed, with only the fields its kind uses, and a default\n * in the kind's own type — a Yes / no a real `true` or `false`, a Number a\n * number. Every refusal is a building-rule finding the doctrine's re-ask\n * quotes by its path.\n */\n\ntype OfferedKind = 'text' | 'richText' | 'image' | 'href' | 'icon' | 'number' | 'boolean' | 'choice'\n\nconst STYLE_REASON =\n 'Styling comes from the theme and the component’s own styles (rules 5 and 12); a page restyles one placement with an override, not a property.'\nconst RECORD_REASON =\n 'A pick from the site’s own records is made where a page places the component (rule 8), never invented as a generated default.'\nconst PLUGIN_REASON =\n 'Plugin settings belong to the plugin’s own element, which a generated component never places.'\n\n/**\n * Why each property kind the dialog declares is not offered to a model.\n * Keyed by every kind outside the offered ones, so the table is complete by\n * type.\n */\nexport const AI_COMPONENT_PROP_KINDS_NOT_OFFERED: Readonly<\n Record<Exclude<ReusableComponentPropType, OfferedKind>, string>\n> = {\n [FieldComponentType.MARKDOWN]:\n 'Long text binds to the same formatted-document fields, with a default a brief can write.',\n [FieldComponentType.DATA_TABLE]:\n 'A table is rows and columns a page fills in, not one value a brief describes.',\n [FieldComponentType.SLIDER]:\n 'Number binds to every slider, without the range settings a Slider property must declare.',\n [FieldComponentType.CHECKBOX]:\n 'Yes / no binds to every switch and single tick box, and no generated element offers a list of tick boxes.',\n [FieldComponentType.RADIO]:\n 'Choice binds to every field Radio buttons binds to, with its answers checked in one place.',\n [FieldComponentType.TOGGLE_BUTTON]:\n 'Choice binds to every field Toggle buttons binds to, with its answers checked in one place.',\n [FieldComponentType.DUAL_LIST_SELECT]:\n 'A pick list holds several answers, and no generated element offers a field that takes several.',\n [FieldComponentType.COLOR_PICKER]:\n 'Colors come from the theme (rule 5); a page recolors one placement with an override, not a property.',\n [FieldComponentType.CSS_DIMENSION]: STYLE_REASON,\n [FieldComponentType.CSS_BORDER]: STYLE_REASON,\n [FieldComponentType.CSS_GRADIENT]: STYLE_REASON,\n [FieldComponentType.BREAKPOINT_SPAN]: STYLE_REASON,\n [FieldComponentType.PRESET_CHOICE]: STYLE_REASON,\n [FieldComponentType.THEME_SCALE]: STYLE_REASON,\n [FieldComponentType.DATE_PICKER]:\n 'A date is a fact the brief would have to give (rule 14), not copy a component shows until a page sets it.',\n [FieldComponentType.TIME_PICKER]:\n 'A time is a fact the brief would have to give (rule 14), not copy a component shows until a page sets it.',\n [FieldComponentType.NODE_SELECT]: RECORD_REASON,\n [FieldComponentType.PRODUCT_SELECT]: RECORD_REASON,\n [FieldComponentType.COLLECTION_SELECT]: RECORD_REASON,\n [FieldComponentType.CATEGORY_SELECT]: RECORD_REASON,\n [FieldComponentType.DATASET_SELECT]: RECORD_REASON,\n [FieldComponentType.DATASET_FIELD_SELECT]: RECORD_REASON,\n [FieldComponentType.FORM_SELECT]:\n 'A form is placed by its id from the Forms page (rule 3), never chosen through a generated property.',\n [FieldComponentType.PLUGIN_SELECT]: PLUGIN_REASON,\n [FieldComponentType.PLUGIN_SETTINGS]: PLUGIN_REASON,\n}\n\n/**\n * The kinds a generated component may declare: the dialog's table less the\n * kinds above, in the dialog's own order.\n */\nexport const AI_COMPONENT_PROP_KINDS: readonly OfferedKind[] = (\n Object.keys(REUSABLE_PROP_KINDS) as ReusableComponentPropType[]\n).filter(\n (type): type is OfferedKind =>\n !Object.prototype.hasOwnProperty.call(AI_COMPONENT_PROP_KINDS_NOT_OFFERED, type),\n)\n\nexport type AiComponentPropKind = OfferedKind\n\n/**\n * The kinds a property proposed for a section already on a page may take\n * (AGL-3054): every offered kind but an Icon. That door reads each default off\n * the live field it binds, and an icon's default is drawn from a path the\n * picker stores beside its id, which the field it reads does not carry.\n */\nexport const AI_COMPONENT_SELECTION_PROP_KINDS: readonly AiComponentPropKind[] =\n AI_COMPONENT_PROP_KINDS.filter((type) => type !== 'icon')\n\n/** The kinds as a model reads them: the stored type and the name a page reads. */\nexport function aiComponentPropKindWords(\n kinds: readonly AiComponentPropKind[] = AI_COMPONENT_PROP_KINDS,\n): string {\n return kinds.map((type) => `${type} (${REUSABLE_PROP_KINDS[type].label})`).join(', ')\n}\n\nexport const AI_COMPONENT_TOOL_NAME = 'submit_component'\n\n/** The most properties one generated component declares. */\nexport const AI_COMPONENT_MAX_PROPS = 20\n\nconst NAME_MAX = 40\nconst LABEL_MAX = 80\nconst HELP_MAX = 200\nconst ANSWER_MAX = 100\n\n/**\n * The strict tool a component arrives through: the doctrine's tree, as the\n * doctrine's own component tool carries it, and the properties beside it.\n * Every field of a property is required and flat, because optional\n * properties are not portable across strict tool implementations; a kind\n * that has no answers sends `[]`.\n */\nexport function aiComponentTool(): AiTool {\n const doctrine = aiDoctrineTreeTool('component')\n const tree = (doctrine.inputSchema as { properties: Record<string, unknown> }).properties['tree']\n return {\n name: AI_COMPONENT_TOOL_NAME,\n description:\n 'Submit the reusable component: its tree as one flat node map, and every property the tree binds.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree', 'props'],\n properties: {\n tree,\n props: {\n type: 'array',\n description:\n 'Every property the tree binds with {{prop.<name>}}, in the order a page sets them.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'type', 'label', 'description', 'defaultValue', 'options'],\n properties: {\n name: {\n type: 'string',\n description:\n 'Letters, digits and underscores, starting with a letter; bound as {{prop.<name>}}.',\n },\n type: {\n type: 'string',\n enum: [...AI_COMPONENT_PROP_KINDS],\n description: `The kind, as type (the name a page reads): ${aiComponentPropKindWords()}.`,\n },\n label: { type: 'string', description: 'The words a page reads beside the field.' },\n description: { type: 'string', description: 'Help beside the field, or \"\".' },\n defaultValue: {\n type: 'string',\n description:\n 'What the component shows until a page sets it: copy for text and richText, a screen id from the site inventory or \"\" for href, \"\" for image and icon, a number for number, \"true\" or \"false\" for boolean, one answer’s value for choice.',\n },\n options: {\n type: 'array',\n description: 'The answers of a choice; [] for every other kind.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['value', 'label'],\n properties: {\n value: {\n type: 'string',\n description: 'What the bound field receives: one of the values that field lists.',\n },\n label: { type: 'string', description: 'What a page picks.' },\n },\n },\n },\n },\n },\n },\n },\n },\n }\n}\n\nexport interface AiComponentPropsScope {\n /** The screens a Link default may name: the site inventory's. */\n screenIds: ReadonlySet<string>\n}\n\nexport interface AiComponentPropsReading {\n /** Every property that could be read, as the Properties dialog stores one. */\n props: ReusableComponentProp[]\n /**\n * The names of properties that were declared under a usable name and\n * refused for something else — their kind, answers, default or label — so\n * a check of the tree does not report their tokens a second time.\n */\n refused: string[]\n violations: AiDoctrineViolation[]\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** A scalar the answer wrote, as trimmed text. */\nfunction scalar(value: unknown): string {\n return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value).trim()\n : ''\n}\n\ntype DefaultReading =\n | { value: ReusableComponentPropValue | undefined }\n | { error: string; rule: AiDoctrineRuleNumber }\n\n/** A default as the kind stores it, or why it cannot be one. */\nfunction readDefault(\n type: OfferedKind,\n raw: unknown,\n options: readonly ReusableComponentPropOption[],\n scope: AiComponentPropsScope,\n): DefaultReading {\n const value = scalar(raw)\n if (HOSTILE_TEXT.test(value)) return { error: 'carries markup or script', rule: 1 }\n // Nothing set is a default of its own: the kind's \"not set\".\n if (!value) return { value: undefined }\n switch (type) {\n case 'text':\n case 'richText':\n return value.length > AI_TEXT_LIMITS.body\n ? { error: `runs past ${AI_TEXT_LIMITS.body} characters`, rule: 1 }\n : { value }\n case 'icon':\n // The picker stores an icon with the drawing a model cannot name, so\n // the site owner picks it where a page places the component.\n return { error: 'is an icon, which the site owner picks; leave it \"\"', rule: 1 }\n case 'image':\n if (/^https?:\\/\\//i.test(value)) {\n return { error: 'links a picture from another website; leave it empty for an upload', rule: 9 }\n }\n return parseMediaRef(value)\n ? { value }\n : { error: 'is not a picture from the media library; leave it empty for an upload', rule: 9 }\n case 'href':\n return scope.screenIds.has(value) || isRootRelativePath(value) || isHttpsUrl(value)\n ? { value }\n : {\n error: 'is neither a screen id from the site inventory, a path on this site nor an https: address',\n rule: 1,\n }\n case 'number': {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? { value: parsed } : { error: 'is not a number', rule: 1 }\n }\n case 'boolean':\n if (value === 'true') return { value: true }\n if (value === 'false') return { value: false }\n return { error: 'is neither true nor false', rule: 1 }\n case 'choice':\n return options.some((option) => option.value === value)\n ? { value }\n : { error: 'is not the value of one of its answers', rule: 1 }\n }\n}\n\n/**\n * The properties a component answer declares, read into the shape the\n * Properties dialog stores, with a finding for everything that would not\n * save there or would not read as the brief's own copy.\n */\nexport function readAiComponentProps(\n answer: Record<string, unknown>,\n scope: AiComponentPropsScope,\n): AiComponentPropsReading {\n const raw = answer['props']\n if (!Array.isArray(raw)) {\n return {\n props: [],\n refused: [],\n violations: [\n {\n rule: 1,\n code: 'props-missing',\n message:\n 'The component came without its list of properties. Declare every property the tree binds, or an empty list.',\n paths: ['props'],\n },\n ],\n }\n }\n const violations: AiDoctrineViolation[] = []\n const finding = (\n code: string,\n message: string,\n path: string,\n rule: AiDoctrineRuleNumber = 1,\n ): void => {\n violations.push({ rule, code, message, paths: [path] })\n }\n if (raw.length > AI_COMPONENT_MAX_PROPS) {\n finding(\n 'props-over-limit',\n `The component declares ${raw.length} properties, and one component declares at most ${AI_COMPONENT_MAX_PROPS}. Keep a property for each value that differs between placements.`,\n 'props',\n )\n }\n const kindNames = AI_COMPONENT_PROP_KINDS.map((type) => REUSABLE_PROP_KINDS[type].label)\n const props: ReusableComponentProp[] = []\n const seen = new Set<string>()\n raw.slice(0, AI_COMPONENT_MAX_PROPS).forEach((entry, index) => {\n const at = `props[${index}]`\n if (!isRecord(entry)) {\n finding('prop-shape', 'A property could not be read. Give each one a name, a kind and a default.', at)\n return\n }\n const name = scalar(entry['name'])\n const called = name ? `\"${name}\"` : `number ${index + 1}`\n if (!COMPONENT_PROP_NAME_PATTERN.test(name) || name.length > NAME_MAX) {\n finding(\n 'prop-name',\n `The property ${called} is not a name a property can have. Use letters, digits and underscores, starting with a letter, in ${NAME_MAX} characters or fewer.`,\n `${at}.name`,\n )\n return\n }\n if (seen.has(name)) {\n finding('prop-duplicate', `Two properties are named \"${name}\". Give each property its own name.`, `${at}.name`)\n return\n }\n seen.add(name)\n const type = scalar(entry['type']) as OfferedKind\n if (!AI_COMPONENT_PROP_KINDS.includes(type)) {\n finding(\n 'prop-kind',\n `The property \"${name}\" is a kind a generated component does not declare. Use one of: ${kindNames.join(', ')}.`,\n `${at}.type`,\n )\n return\n }\n const options: ReusableComponentPropOption[] = []\n if (type === 'choice') {\n const answers = Array.isArray(entry['options']) ? entry['options'] : []\n const values = new Set<string>()\n for (const option of answers) {\n const value = isRecord(option) ? scalar(option['value']).slice(0, ANSWER_MAX) : ''\n const label = isRecord(option) ? scalar(option['label']).slice(0, LABEL_MAX) : ''\n if (!value || HOSTILE_TEXT.test(value) || HOSTILE_TEXT.test(label)) {\n finding('prop-answers', `Every answer of \"${name}\" needs a plain value.`, `${at}.options`)\n return\n }\n if (values.has(value)) {\n finding('prop-answers', `Two answers of \"${name}\" share the value \"${value}\".`, `${at}.options`)\n return\n }\n values.add(value)\n options.push({ value, ...(label && label !== value ? { label } : {}) })\n }\n if (!options.length) {\n finding(\n 'prop-answers',\n `The Choice \"${name}\" lists no answers. Give it the answers a page picks from.`,\n `${at}.options`,\n )\n return\n }\n }\n const read = readDefault(type, entry['defaultValue'], options, scope)\n if ('error' in read) {\n finding('prop-default', `The default of \"${name}\" ${read.error}.`, `${at}.defaultValue`, read.rule)\n return\n }\n const label = scalar(entry['label']).slice(0, LABEL_MAX)\n const description = scalar(entry['description']).slice(0, HELP_MAX)\n if (HOSTILE_TEXT.test(label) || HOSTILE_TEXT.test(description)) {\n finding('prop-shape', `The label or help of \"${name}\" carries markup or script.`, at)\n return\n }\n props.push({\n name,\n type,\n ...(label ? { label } : {}),\n ...(description ? { description } : {}),\n ...(read.value !== undefined ? { defaultValue: read.value } : {}),\n ...(type === 'choice' ? { options } : {}),\n })\n })\n // A name that read, and was then refused for its kind, answers, default or label.\n const refused = [...seen].filter((name) => !props.some((prop) => prop.name === name))\n return { props, refused, violations }\n}\n"],"names":["parseMediaRef","COMPONENT_PROP_NAME_PATTERN","FieldComponentType","REUSABLE_PROP_KINDS","aiDoctrineTreeTool","HOSTILE_TEXT","isHttpsUrl","isRootRelativePath","AI_TEXT_LIMITS","STYLE_REASON","RECORD_REASON","PLUGIN_REASON","AI_COMPONENT_PROP_KINDS_NOT_OFFERED","MARKDOWN","DATA_TABLE","SLIDER","CHECKBOX","RADIO","TOGGLE_BUTTON","DUAL_LIST_SELECT","COLOR_PICKER","CSS_DIMENSION","CSS_BORDER","CSS_GRADIENT","BREAKPOINT_SPAN","PRESET_CHOICE","THEME_SCALE","DATE_PICKER","TIME_PICKER","NODE_SELECT","PRODUCT_SELECT","COLLECTION_SELECT","CATEGORY_SELECT","DATASET_SELECT","DATASET_FIELD_SELECT","FORM_SELECT","PLUGIN_SELECT","PLUGIN_SETTINGS","AI_COMPONENT_PROP_KINDS","Object","keys","filter","type","prototype","hasOwnProperty","call","AI_COMPONENT_SELECTION_PROP_KINDS","aiComponentPropKindWords","kinds","map","label","join","AI_COMPONENT_TOOL_NAME","AI_COMPONENT_MAX_PROPS","NAME_MAX","LABEL_MAX","HELP_MAX","ANSWER_MAX","aiComponentTool","doctrine","tree","inputSchema","properties","name","description","strict","additionalProperties","required","props","items","enum","defaultValue","options","value","isRecord","Array","isArray","scalar","String","trim","readDefault","raw","scope","test","error","rule","undefined","length","body","screenIds","has","parsed","Number","isFinite","some","option","readAiComponentProps","answer","refused","violations","code","message","paths","finding","path","push","kindNames","seen","Set","slice","forEach","entry","index","at","called","add","includes","answers","values","read","prop"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/tools/ai-component-tool.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 { parseMediaRef } from '@aglyn/aglyn/app-utils/media-ref'\nimport { COMPONENT_PROP_NAME_PATTERN } from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport { FieldComponentType } from '@aglyn/aglyn/foundation/definitions/components.types'\nimport type {\n ReusableComponentProp,\n ReusableComponentPropOption,\n ReusableComponentPropType,\n ReusableComponentPropValue,\n} from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport { REUSABLE_PROP_KINDS } from '@aglyn/aglyn/foundation/definitions/property-kinds'\nimport type { AiTool } from '../providers/contract'\nimport { aiDoctrineTreeTool } from '../runtime/ai-doctrine'\nimport type {\n AiDoctrineRuleNumber,\n AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { HOSTILE_TEXT, isHttpsUrl, isRootRelativePath } from '../runtime/ai-node-tree'\nimport { AI_TEXT_LIMITS } from '../runtime/ai-palette'\n\n/**\n * The tool a generated reusable component arrives through (AGL-2908), and\n * the reading of the properties it declares.\n *\n * ── The kinds are the Properties dialog's ────────────────────────────────\n *\n * FILE ▸ Properties… declares a property as one of `REUSABLE_PROP_KINDS`,\n * and so does this tool: the kinds it offers are that table less the ones\n * `AI_COMPONENT_PROP_KINDS_NOT_OFFERED` names, each with the reason it is\n * left out, and a spec holds the two in both directions. A kind added to the\n * dialog does not compile here until it is offered or given a reason.\n *\n * What is offered is what a generated element can carry and a brief can\n * fill: copy (Text, Long text), a picture (Image), a link (Link), an icon\n * the site owner picks (Icon), a number (Number), a switch or a part's\n * visibility (Yes / no), and a setting with fixed answers (Choice).\n *\n * ── A declaration as the dialog saves one ────────────────────────────────\n *\n * `readAiComponentProps` returns each property in the shape the dialog's\n * cleaner stores: trimmed, with only the fields its kind uses, and a default\n * in the kind's own type — a Yes / no a real `true` or `false`, a Number a\n * number. Every refusal is a building-rule finding the doctrine's re-ask\n * quotes by its path.\n */\n\ntype OfferedKind = 'text' | 'richText' | 'image' | 'href' | 'icon' | 'number' | 'boolean' | 'choice'\n\nconst STYLE_REASON =\n 'Styling comes from the theme and the component’s own styles (rules 5 and 12); a page restyles one placement with an override, not a property.'\nconst RECORD_REASON =\n 'A pick from the site’s own records is made where a page places the component (rule 8), never invented as a generated default.'\nconst PLUGIN_REASON =\n 'Plugin settings belong to the plugin’s own element, which a generated component never places.'\n\n/**\n * Why each property kind the dialog declares is not offered to a model.\n * Keyed by every kind outside the offered ones, so the table is complete by\n * type.\n */\nexport const AI_COMPONENT_PROP_KINDS_NOT_OFFERED: Readonly<\n Record<Exclude<ReusableComponentPropType, OfferedKind>, string>\n> = {\n [FieldComponentType.MARKDOWN]:\n 'Long text binds to the same formatted-document fields, with a default a brief can write.',\n [FieldComponentType.DATA_TABLE]:\n 'A table is rows and columns a page fills in, not one value a brief describes.',\n [FieldComponentType.SLIDER]:\n 'Number binds to every slider, without the range settings a Slider property must declare.',\n [FieldComponentType.CHECKBOX]:\n 'Yes / no binds to every switch and single tick box, and no generated element offers a list of tick boxes.',\n [FieldComponentType.RADIO]:\n 'Choice binds to every field Radio buttons binds to, with its answers checked in one place.',\n [FieldComponentType.TOGGLE_BUTTON]:\n 'Choice binds to every field Toggle buttons binds to, with its answers checked in one place.',\n [FieldComponentType.DUAL_LIST_SELECT]:\n 'A pick list holds several answers, and no generated element offers a field that takes several.',\n [FieldComponentType.COLOR_PICKER]:\n 'Colors come from the theme (rule 5); a page recolors one placement with an override, not a property.',\n [FieldComponentType.CSS_DIMENSION]: STYLE_REASON,\n [FieldComponentType.CSS_BORDER]: STYLE_REASON,\n [FieldComponentType.CSS_GRADIENT]: STYLE_REASON,\n [FieldComponentType.BREAKPOINT_SPAN]: STYLE_REASON,\n [FieldComponentType.PRESET_CHOICE]: STYLE_REASON,\n [FieldComponentType.THEME_SCALE]: STYLE_REASON,\n [FieldComponentType.DATE_PICKER]:\n 'A date is a fact the brief would have to give (rule 14), not copy a component shows until a page sets it.',\n [FieldComponentType.TIME_PICKER]:\n 'A time is a fact the brief would have to give (rule 14), not copy a component shows until a page sets it.',\n [FieldComponentType.NODE_SELECT]: RECORD_REASON,\n [FieldComponentType.PRODUCT_SELECT]: RECORD_REASON,\n [FieldComponentType.COLLECTION_SELECT]: RECORD_REASON,\n [FieldComponentType.CATEGORY_SELECT]: RECORD_REASON,\n [FieldComponentType.DATASET_SELECT]: RECORD_REASON,\n [FieldComponentType.DATASET_FIELD_SELECT]: RECORD_REASON,\n [FieldComponentType.FORM_SELECT]:\n 'A form is placed by its id from the Forms page (rule 3), never chosen through a generated property.',\n [FieldComponentType.PLUGIN_SELECT]: PLUGIN_REASON,\n [FieldComponentType.PLUGIN_SETTINGS]: PLUGIN_REASON,\n}\n\n/**\n * The kinds a generated component may declare: the dialog's table less the\n * kinds above, in the dialog's own order.\n */\nexport const AI_COMPONENT_PROP_KINDS: readonly OfferedKind[] = (\n Object.keys(REUSABLE_PROP_KINDS) as ReusableComponentPropType[]\n).filter(\n (type): type is OfferedKind =>\n !Object.prototype.hasOwnProperty.call(AI_COMPONENT_PROP_KINDS_NOT_OFFERED, type),\n)\n\nexport type AiComponentPropKind = OfferedKind\n\n/**\n * The kinds a property proposed for a section already on a page may take\n * (AGL-3054): every offered kind but an Icon. That door reads each default off\n * the live field it binds, and an icon's default is drawn from a path the\n * picker stores beside its id, which the field it reads does not carry.\n */\nexport const AI_COMPONENT_SELECTION_PROP_KINDS: readonly AiComponentPropKind[] =\n AI_COMPONENT_PROP_KINDS.filter((type) => type !== 'icon')\n\n/** The kinds as a model reads them: the stored type and the name a page reads. */\nexport function aiComponentPropKindWords(\n kinds: readonly AiComponentPropKind[] = AI_COMPONENT_PROP_KINDS,\n): string {\n return kinds.map((type) => `${type} (${REUSABLE_PROP_KINDS[type].label})`).join(', ')\n}\n\nexport const AI_COMPONENT_TOOL_NAME = 'submit_component'\n\n/** The most properties one generated component declares. */\nexport const AI_COMPONENT_MAX_PROPS = 20\n\nconst NAME_MAX = 40\nconst LABEL_MAX = 80\nconst HELP_MAX = 200\nconst ANSWER_MAX = 100\n\n/**\n * The strict tool a component arrives through: the doctrine's tree, as the\n * doctrine's own component tool carries it, and the properties beside it.\n * Every field of a property is required and flat, because optional\n * properties are not portable across strict tool implementations; a kind\n * that has no answers sends `[]`.\n */\nexport function aiComponentTool(): AiTool {\n const doctrine = aiDoctrineTreeTool('component')\n const tree = (doctrine.inputSchema as { properties: Record<string, unknown> }).properties['tree']\n return {\n name: AI_COMPONENT_TOOL_NAME,\n description:\n 'Submit the reusable component: its tree as one flat node map, and every property the tree binds.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['tree', 'props'],\n properties: {\n tree,\n props: {\n type: 'array',\n description:\n 'Every property the tree binds with {{prop.<name>}}, in the order a page sets them.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'type', 'label', 'description', 'defaultValue', 'options'],\n properties: {\n name: {\n type: 'string',\n description:\n 'Letters, digits and underscores, starting with a letter; bound as {{prop.<name>}}.',\n },\n type: {\n type: 'string',\n enum: [...AI_COMPONENT_PROP_KINDS],\n description: `The kind, as type (the name a page reads): ${aiComponentPropKindWords()}.`,\n },\n label: { type: 'string', description: 'The words a page reads beside the field.' },\n description: { type: 'string', description: 'Help beside the field, or \"\".' },\n defaultValue: {\n type: 'string',\n description:\n 'What the component shows until a page sets it: copy for text and richText, a page id from the site inventory or \"\" for href, \"\" for image and icon, a number for number, \"true\" or \"false\" for boolean, one answer’s value for choice.',\n },\n options: {\n type: 'array',\n description: 'The answers of a choice; [] for every other kind.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['value', 'label'],\n properties: {\n value: {\n type: 'string',\n description: 'What the bound field receives: one of the values that field lists.',\n },\n label: { type: 'string', description: 'What a page picks.' },\n },\n },\n },\n },\n },\n },\n },\n },\n }\n}\n\nexport interface AiComponentPropsScope {\n /** The screens a Link default may name: the site inventory's. */\n screenIds: ReadonlySet<string>\n}\n\nexport interface AiComponentPropsReading {\n /** Every property that could be read, as the Properties dialog stores one. */\n props: ReusableComponentProp[]\n /**\n * The names of properties that were declared under a usable name and\n * refused for something else — their kind, answers, default or label — so\n * a check of the tree does not report their tokens a second time.\n */\n refused: string[]\n violations: AiDoctrineViolation[]\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** A scalar the answer wrote, as trimmed text. */\nfunction scalar(value: unknown): string {\n return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n ? String(value).trim()\n : ''\n}\n\ntype DefaultReading =\n | { value: ReusableComponentPropValue | undefined }\n | { error: string; rule: AiDoctrineRuleNumber }\n\n/** A default as the kind stores it, or why it cannot be one. */\nfunction readDefault(\n type: OfferedKind,\n raw: unknown,\n options: readonly ReusableComponentPropOption[],\n scope: AiComponentPropsScope,\n): DefaultReading {\n const value = scalar(raw)\n if (HOSTILE_TEXT.test(value)) return { error: 'carries markup or script', rule: 1 }\n // Nothing set is a default of its own: the kind's \"not set\".\n if (!value) return { value: undefined }\n switch (type) {\n case 'text':\n case 'richText':\n return value.length > AI_TEXT_LIMITS.body\n ? { error: `runs past ${AI_TEXT_LIMITS.body} characters`, rule: 1 }\n : { value }\n case 'icon':\n // The picker stores an icon with the drawing a model cannot name, so\n // the site owner picks it where a page places the component.\n return { error: 'is an icon, which the site owner picks; leave it \"\"', rule: 1 }\n case 'image':\n if (/^https?:\\/\\//i.test(value)) {\n return { error: 'links a picture from another website; leave it empty for an upload', rule: 9 }\n }\n return parseMediaRef(value)\n ? { value }\n : { error: 'is not a picture from the media library; leave it empty for an upload', rule: 9 }\n case 'href':\n return scope.screenIds.has(value) || isRootRelativePath(value) || isHttpsUrl(value)\n ? { value }\n : {\n error: 'is neither a page id from the site inventory, a path on this site nor an https: address',\n rule: 1,\n }\n case 'number': {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? { value: parsed } : { error: 'is not a number', rule: 1 }\n }\n case 'boolean':\n if (value === 'true') return { value: true }\n if (value === 'false') return { value: false }\n return { error: 'is neither true nor false', rule: 1 }\n case 'choice':\n return options.some((option) => option.value === value)\n ? { value }\n : { error: 'is not the value of one of its answers', rule: 1 }\n }\n}\n\n/**\n * The properties a component answer declares, read into the shape the\n * Properties dialog stores, with a finding for everything that would not\n * save there or would not read as the brief's own copy.\n */\nexport function readAiComponentProps(\n answer: Record<string, unknown>,\n scope: AiComponentPropsScope,\n): AiComponentPropsReading {\n const raw = answer['props']\n if (!Array.isArray(raw)) {\n return {\n props: [],\n refused: [],\n violations: [\n {\n rule: 1,\n code: 'props-missing',\n message:\n 'The component came without its list of properties. Declare every property the tree binds, or an empty list.',\n paths: ['props'],\n },\n ],\n }\n }\n const violations: AiDoctrineViolation[] = []\n const finding = (\n code: string,\n message: string,\n path: string,\n rule: AiDoctrineRuleNumber = 1,\n ): void => {\n violations.push({ rule, code, message, paths: [path] })\n }\n if (raw.length > AI_COMPONENT_MAX_PROPS) {\n finding(\n 'props-over-limit',\n `The component declares ${raw.length} properties, and one component declares at most ${AI_COMPONENT_MAX_PROPS}. Keep a property for each value that differs between placements.`,\n 'props',\n )\n }\n const kindNames = AI_COMPONENT_PROP_KINDS.map((type) => REUSABLE_PROP_KINDS[type].label)\n const props: ReusableComponentProp[] = []\n const seen = new Set<string>()\n raw.slice(0, AI_COMPONENT_MAX_PROPS).forEach((entry, index) => {\n const at = `props[${index}]`\n if (!isRecord(entry)) {\n finding('prop-shape', 'A property could not be read. Give each one a name, a kind and a default.', at)\n return\n }\n const name = scalar(entry['name'])\n const called = name ? `\"${name}\"` : `number ${index + 1}`\n if (!COMPONENT_PROP_NAME_PATTERN.test(name) || name.length > NAME_MAX) {\n finding(\n 'prop-name',\n `The property ${called} is not a name a property can have. Use letters, digits and underscores, starting with a letter, in ${NAME_MAX} characters or fewer.`,\n `${at}.name`,\n )\n return\n }\n if (seen.has(name)) {\n finding('prop-duplicate', `Two properties are named \"${name}\". Give each property its own name.`, `${at}.name`)\n return\n }\n seen.add(name)\n const type = scalar(entry['type']) as OfferedKind\n if (!AI_COMPONENT_PROP_KINDS.includes(type)) {\n finding(\n 'prop-kind',\n `The property \"${name}\" is a kind a generated component does not declare. Use one of: ${kindNames.join(', ')}.`,\n `${at}.type`,\n )\n return\n }\n const options: ReusableComponentPropOption[] = []\n if (type === 'choice') {\n const answers = Array.isArray(entry['options']) ? entry['options'] : []\n const values = new Set<string>()\n for (const option of answers) {\n const value = isRecord(option) ? scalar(option['value']).slice(0, ANSWER_MAX) : ''\n const label = isRecord(option) ? scalar(option['label']).slice(0, LABEL_MAX) : ''\n if (!value || HOSTILE_TEXT.test(value) || HOSTILE_TEXT.test(label)) {\n finding('prop-answers', `Every answer of \"${name}\" needs a plain value.`, `${at}.options`)\n return\n }\n if (values.has(value)) {\n finding('prop-answers', `Two answers of \"${name}\" share the value \"${value}\".`, `${at}.options`)\n return\n }\n values.add(value)\n options.push({ value, ...(label && label !== value ? { label } : {}) })\n }\n if (!options.length) {\n finding(\n 'prop-answers',\n `The Choice \"${name}\" lists no answers. Give it the answers a page picks from.`,\n `${at}.options`,\n )\n return\n }\n }\n const read = readDefault(type, entry['defaultValue'], options, scope)\n if ('error' in read) {\n finding('prop-default', `The default of \"${name}\" ${read.error}.`, `${at}.defaultValue`, read.rule)\n return\n }\n const label = scalar(entry['label']).slice(0, LABEL_MAX)\n const description = scalar(entry['description']).slice(0, HELP_MAX)\n if (HOSTILE_TEXT.test(label) || HOSTILE_TEXT.test(description)) {\n finding('prop-shape', `The label or help of \"${name}\" carries markup or script.`, at)\n return\n }\n props.push({\n name,\n type,\n ...(label ? { label } : {}),\n ...(description ? { description } : {}),\n ...(read.value !== undefined ? { defaultValue: read.value } : {}),\n ...(type === 'choice' ? { options } : {}),\n })\n })\n // A name that read, and was then refused for its kind, answers, default or label.\n const refused = [...seen].filter((name) => !props.some((prop) => prop.name === name))\n return { props, refused, violations }\n}\n"],"names":["parseMediaRef","COMPONENT_PROP_NAME_PATTERN","FieldComponentType","REUSABLE_PROP_KINDS","aiDoctrineTreeTool","HOSTILE_TEXT","isHttpsUrl","isRootRelativePath","AI_TEXT_LIMITS","STYLE_REASON","RECORD_REASON","PLUGIN_REASON","AI_COMPONENT_PROP_KINDS_NOT_OFFERED","MARKDOWN","DATA_TABLE","SLIDER","CHECKBOX","RADIO","TOGGLE_BUTTON","DUAL_LIST_SELECT","COLOR_PICKER","CSS_DIMENSION","CSS_BORDER","CSS_GRADIENT","BREAKPOINT_SPAN","PRESET_CHOICE","THEME_SCALE","DATE_PICKER","TIME_PICKER","NODE_SELECT","PRODUCT_SELECT","COLLECTION_SELECT","CATEGORY_SELECT","DATASET_SELECT","DATASET_FIELD_SELECT","FORM_SELECT","PLUGIN_SELECT","PLUGIN_SETTINGS","AI_COMPONENT_PROP_KINDS","Object","keys","filter","type","prototype","hasOwnProperty","call","AI_COMPONENT_SELECTION_PROP_KINDS","aiComponentPropKindWords","kinds","map","label","join","AI_COMPONENT_TOOL_NAME","AI_COMPONENT_MAX_PROPS","NAME_MAX","LABEL_MAX","HELP_MAX","ANSWER_MAX","aiComponentTool","doctrine","tree","inputSchema","properties","name","description","strict","additionalProperties","required","props","items","enum","defaultValue","options","value","isRecord","Array","isArray","scalar","String","trim","readDefault","raw","scope","test","error","rule","undefined","length","body","screenIds","has","parsed","Number","isFinite","some","option","readAiComponentProps","answer","refused","violations","code","message","paths","finding","path","push","kindNames","seen","Set","slice","forEach","entry","index","at","called","add","includes","answers","values","read","prop"],"mappings":"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"}