@aglyn/plugins-ai 1.0.0-beta.217 → 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 (187) 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 +10 -2
  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.d.ts +9 -1
  49. package/src/lib/jobs/ai-job-drafts.js +12 -5
  50. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  51. package/src/lib/jobs/ai-job-email-step.d.ts +0 -2
  52. package/src/lib/jobs/ai-job-email-step.js +1 -3
  53. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
  55. package/src/lib/jobs/ai-job-experiment-step.js +1 -1
  56. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-generation.d.ts +1 -1
  58. package/src/lib/jobs/ai-job-generation.js +10 -2
  59. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  60. package/src/lib/jobs/ai-job-layout-step.js +1 -1
  61. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
  63. package/src/lib/jobs/ai-job-page-sections.js +7 -4
  64. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  65. package/src/lib/jobs/ai-job-page-step.js +7 -2
  66. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-plan-conformance.js +33 -0
  68. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  69. package/src/lib/jobs/ai-job-plan-step.d.ts +3 -0
  70. package/src/lib/jobs/ai-job-plan-step.js +29 -17
  71. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  72. package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
  73. package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
  74. package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
  75. package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
  76. package/src/lib/jobs/ai-job-products-step.js +54 -32
  77. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  78. package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
  79. package/src/lib/jobs/ai-job-seo-step.js +35 -108
  80. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  81. package/src/lib/jobs/ai-job-template-step.js +1 -0
  82. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  83. package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
  84. package/src/lib/jobs/ai-job-theme-step.js +1 -1
  85. package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
  86. package/src/lib/jobs/ai-job-workflow-step.js +17 -9
  87. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  88. package/src/lib/jobs/ai-workflow-records.d.ts +19 -11
  89. package/src/lib/jobs/ai-workflow-records.js +60 -27
  90. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  91. package/src/lib/model/ai-allotments.d.ts +11 -3
  92. package/src/lib/model/ai-allotments.js +21 -4
  93. package/src/lib/model/ai-allotments.js.map +1 -1
  94. package/src/lib/model/ai-automation-outline.d.ts +21 -2
  95. package/src/lib/model/ai-automation-outline.js.map +1 -1
  96. package/src/lib/model/ai-build-plan.d.ts +57 -0
  97. package/src/lib/model/ai-build-plan.js +116 -7
  98. package/src/lib/model/ai-build-plan.js.map +1 -1
  99. package/src/lib/model/ai-seo.d.ts +11 -51
  100. package/src/lib/model/ai-seo.js +0 -17
  101. package/src/lib/model/ai-seo.js.map +1 -1
  102. package/src/lib/model/ai-site-inventory.js +1 -1
  103. package/src/lib/model/ai-site-inventory.js.map +1 -1
  104. package/src/lib/model/ai-template-subjects.js +2 -2
  105. package/src/lib/model/ai-template-subjects.js.map +1 -1
  106. package/src/lib/model/ai-theme-proposal.d.ts +1 -1
  107. package/src/lib/model/ai-theme-proposal.js +1 -1
  108. package/src/lib/model/ai-theme-proposal.js.map +1 -1
  109. package/src/lib/model/ai-workflow-job.d.ts +1 -3
  110. package/src/lib/model/ai-workflow-job.js +1 -2
  111. package/src/lib/model/ai-workflow-job.js.map +1 -1
  112. package/src/lib/plan-entitlements.d.ts +149 -0
  113. package/src/lib/plan-entitlements.js +168 -0
  114. package/src/lib/plan-entitlements.js.map +1 -0
  115. package/src/lib/plugin.js +1 -1
  116. package/src/lib/plugin.js.map +1 -1
  117. package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
  118. package/src/lib/runtime/ai-doctrine-validators.js +179 -23
  119. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  120. package/src/lib/runtime/ai-doctrine.js +5 -5
  121. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  122. package/src/lib/runtime/ai-eval-live.js +29 -14
  123. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  124. package/src/lib/runtime/ai-eval.d.ts +8 -1
  125. package/src/lib/runtime/ai-eval.js +29 -2
  126. package/src/lib/runtime/ai-eval.js.map +1 -1
  127. package/src/lib/runtime/ai-node-tree.js +3 -3
  128. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  129. package/src/lib/runtime/ai-palette.d.ts +6 -0
  130. package/src/lib/runtime/ai-palette.generated.js +21 -255
  131. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  132. package/src/lib/runtime/ai-palette.js +7 -1
  133. package/src/lib/runtime/ai-palette.js.map +1 -1
  134. package/src/lib/runtime/seo-audit.d.ts +13 -63
  135. package/src/lib/runtime/seo-audit.js +22 -207
  136. package/src/lib/runtime/seo-audit.js.map +1 -1
  137. package/src/lib/runtime/site-inventory.js +1 -1
  138. package/src/lib/runtime/site-inventory.js.map +1 -1
  139. package/src/lib/server/ai-admin-org.js +4 -25
  140. package/src/lib/server/ai-admin-org.js.map +1 -1
  141. package/src/lib/server/ai-admin-user.js +2 -19
  142. package/src/lib/server/ai-admin-user.js.map +1 -1
  143. package/src/lib/server/ai-assist-prompts.js +3 -3
  144. package/src/lib/server/ai-assist-prompts.js.map +1 -1
  145. package/src/lib/server/ai-insight-digest-route.js +8 -1
  146. package/src/lib/server/ai-insight-digest-route.js.map +1 -1
  147. package/src/lib/server/assist-chat.js +11 -10
  148. package/src/lib/server/assist-chat.js.map +1 -1
  149. package/src/lib/server/assist-view-context.js +23 -23
  150. package/src/lib/server/assist-view-context.js.map +1 -1
  151. package/src/lib/server.d.ts +1 -0
  152. package/src/lib/server.js.map +1 -1
  153. package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
  154. package/src/lib/testing/stand-in-automation-index.js +102 -0
  155. package/src/lib/testing/stand-in-automation-index.js.map +1 -0
  156. package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
  157. package/src/lib/testing/stand-in-list-send-time.js +77 -0
  158. package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
  159. package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
  160. package/src/lib/testing/stand-in-pipeline-index.js +59 -0
  161. package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
  162. package/src/lib/testing/stand-in-product-index.d.ts +20 -0
  163. package/src/lib/testing/stand-in-product-index.js +105 -0
  164. package/src/lib/testing/stand-in-product-index.js.map +1 -0
  165. package/src/lib/tools/ai-component-tool.js +2 -2
  166. package/src/lib/tools/ai-component-tool.js.map +1 -1
  167. package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
  168. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  169. package/src/lib/tools/ai-seo-tool.d.ts +6 -18
  170. package/src/lib/tools/ai-seo-tool.js +7 -38
  171. package/src/lib/tools/ai-seo-tool.js.map +1 -1
  172. package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
  173. package/src/lib/tools/ai-workflow-tool.js +1 -1
  174. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  175. package/src/lib/usage/ai-allotment-alerts.d.ts +3 -0
  176. package/src/lib/usage/ai-allotment-alerts.js +20 -8
  177. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  178. package/src/lib/usage/assist-free-taste.js +2 -1
  179. package/src/lib/usage/assist-free-taste.js.map +1 -1
  180. package/src/lib/usage/assist-signal-mining.js +1 -1
  181. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  182. package/src/lib/usage-axes.d.ts +39 -0
  183. package/src/lib/usage-axes.js +70 -0
  184. package/src/lib/usage-axes.js.map +1 -0
  185. package/src/lib/runtime/seo-page-facts.d.ts +0 -61
  186. package/src/lib/runtime/seo-page-facts.js +0 -150
  187. package/src/lib/runtime/seo-page-facts.js.map +0 -1
@@ -47,7 +47,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
47
47
  {
48
48
  key: 'besigner',
49
49
  match: /^\/[^/]+\/hosts\/[^/]+\/(screens|components|templates|layouts|emails)\/.*\/besigner$/,
50
- screen: 'The Besigner — the visual editor for a screen, component, template, layout or email.',
50
+ screen: 'The Besigner — the visual editor for a page, component, template, layout or email.',
51
51
  plain: [
52
52
  'Edit the page visually: pick an element on the canvas and change its text, styling and layout.',
53
53
  'Work is saved to a draft version; publishing is a separate, deliberate step.',
@@ -71,31 +71,31 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
71
71
  {
72
72
  key: 'host-screens',
73
73
  match: /^\/[^/]+\/hosts\/[^/]+\/screens(\/|$)/,
74
- screen: 'Screens — the list of pages on this site.',
74
+ screen: 'Pages — the list of pages on this site.',
75
75
  plain: [
76
- 'Every page a visitor can reach is a screen listed here.',
77
- 'Open a screen to see its versions, then open the Besigner to edit it.',
78
- 'Publishing a screen version is what puts a change on the live site.'
76
+ 'Every page a visitor can reach is listed here.',
77
+ 'Open a page to see its versions, then open the Besigner to edit it.',
78
+ 'Publishing a page version is what puts a change on the live site.'
79
79
  ],
80
80
  technical: [
81
81
  'Route: /[orgSlug]/hosts/[host]/screens; detail is .../screens/[screenId]/versions/[versionId]/view.',
82
82
  '[host] in the path is the site (host) document id, not the domain name.'
83
83
  ],
84
84
  actions: [
85
- orgAction('open.host.screens', 'Open Screens', 'the list of pages on this site', '/[orgSlug]/hosts/[host]/screens')
85
+ orgAction('open.host.screens', 'Open Pages', 'the list of pages on this site', '/[orgSlug]/hosts/[host]/screens')
86
86
  ]
87
87
  },
88
88
  {
89
89
  key: 'host-theme',
90
90
  match: /^\/[^/]+\/hosts\/[^/]+\/theme(\/|$)/,
91
- screen: 'Theme — the site-wide colors, type and spacing every screen inherits.',
91
+ screen: 'Theme — the site-wide colors, type and spacing every page inherits.',
92
92
  plain: [
93
93
  'Change something here and it changes everywhere on the site at once.',
94
94
  'This is the place to fix a color or a font you keep re-setting on individual elements.'
95
95
  ],
96
96
  technical: [
97
97
  'Route: /[orgSlug]/hosts/[host]/theme.',
98
- 'Theme values resolve at render time, so screens pick them up without being republished individually.'
98
+ 'Theme values resolve at render time, so pages pick them up without being republished individually.'
99
99
  ],
100
100
  actions: []
101
101
  },
@@ -123,7 +123,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
123
123
  screen: 'Products — the commerce catalog for this site.',
124
124
  plain: [
125
125
  'Add and edit the things this site sells, including price and availability.',
126
- 'A product has to exist here before a screen can put it in front of a buyer.'
126
+ 'A product has to exist here before a page can put it in front of a buyer.'
127
127
  ],
128
128
  technical: [
129
129
  'Route: /[orgSlug]/hosts/[host]/products.',
@@ -141,7 +141,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
141
141
  ],
142
142
  technical: [
143
143
  'Route: /[orgSlug]/hosts/[host]/redirects.',
144
- 'Rules are evaluated by the tenant runtime on the request path, before the screen is resolved.'
144
+ 'Rules are evaluated by the tenant runtime on the request path, before the page is resolved.'
145
145
  ],
146
146
  actions: [
147
147
  orgAction('open.host.redirects', 'Open Redirects', 'the redirect rules for this site', '/[orgSlug]/hosts/[host]/redirects', [
@@ -155,7 +155,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
155
155
  match: /^\/[^/]+\/hosts\/[^/]+\/analytics(\/|$)/,
156
156
  screen: 'Analytics — traffic and visitor figures for this site.',
157
157
  plain: [
158
- 'See how many people visited, and which screens they landed on.',
158
+ 'See how many people visited, and which pages they landed on.',
159
159
  'Figures cover the live site only — drafts and previews are not counted.'
160
160
  ],
161
161
  technical: [
@@ -186,8 +186,8 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
186
186
  match: /^\/[^/]+\/hosts\/[^/]+\/data(\/|$)/,
187
187
  screen: 'Site data — the datasets this site reads and writes.',
188
188
  plain: [
189
- 'A dataset is a structured list — products, posts, submissions — that screens can display.',
190
- 'Define the fields first; screens bind to them afterwards.'
189
+ 'A dataset is a structured list — products, posts, submissions — that pages can display.',
190
+ 'Define the fields first; pages bind to them afterwards.'
191
191
  ],
192
192
  technical: [
193
193
  'Route: /[orgSlug]/hosts/[host]/data. The workspace-wide view is /[orgSlug]/data.'
@@ -199,7 +199,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
199
199
  match: /^\/[^/]+\/hosts\/[^/]+(\/|$)/,
200
200
  screen: 'Site dashboard — the home page for one site in this workspace.',
201
201
  plain: [
202
- 'Everything about one site hangs off this page: its screens, theme, data, commerce and settings.',
202
+ 'Everything about one site hangs off this dashboard: its pages, theme, data, commerce and settings.',
203
203
  'A workspace can hold several sites; this is one of them.'
204
204
  ],
205
205
  technical: [
@@ -212,7 +212,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
212
212
  match: /^\/[^/]+\/hosts(\/|$)/,
213
213
  screen: 'Sites — every site in this workspace.',
214
214
  plain: [
215
- 'Each row is a site with its own screens, theme and domain.',
215
+ 'Each row is a site with its own pages, theme and domain.',
216
216
  'Agencies typically run one site per client from here.'
217
217
  ],
218
218
  technical: [
@@ -264,7 +264,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
264
264
  plain: [
265
265
  'Structured lists defined once here can be used by any site in the workspace.',
266
266
  'If a list only belongs to one site, define it on that site’s own Data page instead.',
267
- 'Define the fields before building the screen that displays them — screens bind to fields that already exist.'
267
+ 'Define the fields before building the page that displays them — pages bind to fields that already exist.'
268
268
  ],
269
269
  technical: [
270
270
  'Route: /[orgSlug]/data. The per-site view is /[orgSlug]/hosts/[host]/data.',
@@ -281,7 +281,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
281
281
  plain: [
282
282
  'Upload once here and use the file on any site in the workspace.',
283
283
  'If the file belongs to one site only, upload it on that site’s own Media page instead.',
284
- 'Files are referenced by the screens that use them, so replacing a file changes it everywhere it appears.'
284
+ 'Files are referenced by the pages that use them, so replacing a file changes it everywhere it appears.'
285
285
  ],
286
286
  technical: [
287
287
  'Route: /[orgSlug]/media. The per-site library is /[orgSlug]/hosts/[host]/media.',
@@ -441,7 +441,7 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
441
441
  }) {
442
442
  const lines = [];
443
443
  if (view) {
444
- lines.push(`This screen: ${view.screen}`);
444
+ lines.push(`This console page: ${view.screen}`);
445
445
  if (view.plain.length) {
446
446
  lines.push('What the user can do here:');
447
447
  for (const line of view.plain)lines.push(`- ${line}`);
@@ -453,22 +453,22 @@ const isNavigateAction = (action)=>action.kind !== 'edit';
453
453
  const offered = offeredActions(view, rung);
454
454
  const navigations = offered.filter(isNavigateAction);
455
455
  if (navigations.length) {
456
- lines.push('Actions you may PROPOSE from this screen (ids are exact; propose at most one, and only when the user asked to get something done):');
456
+ lines.push('Actions you may PROPOSE from this console page (ids are exact; propose at most one, and only when the user asked to get something done):');
457
457
  for (const action of navigations){
458
458
  const params = action.params.length ? ` params: ${action.params.join(', ')}` : ' params: none';
459
459
  lines.push(`- id "${action.id}" — ${action.label}: opens ${action.outcome}.${params}`);
460
460
  }
461
461
  } else if (offered.length) {
462
- lines.push('This screen offers no page to open. Do not emit an action block here.');
462
+ lines.push('This console page offers nothing to open. Do not emit an action block here.');
463
463
  } else {
464
- lines.push('This screen offers no proposable actions. Do not emit an action block here.');
464
+ lines.push('This console page offers no proposable actions. Do not emit an action block here.');
465
465
  }
466
466
  for (const action of offered){
467
467
  if (action.kind !== 'edit') continue;
468
- lines.push(`Edits you may PROPOSE on this screen (id "${action.id}"): through the ${ASSIST_EDIT_TOOL_NAME} tool described below, never an action block.`);
468
+ lines.push(`Edits you may PROPOSE on this console page (id "${action.id}"): through the ${ASSIST_EDIT_TOOL_NAME} tool described below, never an action block.`);
469
469
  }
470
470
  } else {
471
- lines.push('This exact screen is not in the assistant’s screen index. Answer from the documentation and describe navigation in words rather than asserting what this page contains.', 'Do not emit an action block.');
471
+ lines.push('This exact console page is not in the assistant’s index of console pages. Answer from the documentation and describe navigation in words rather than asserting what this page contains.', 'Do not emit an action block.');
472
472
  }
473
473
  return lines.join('\n');
474
474
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/assist-view-context.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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n ASSIST_EDIT_ACTION_ID,\n ASSIST_EDIT_DOCUMENT_KINDS,\n ASSIST_EDIT_TOOL_NAME,\n type AssistEditDocumentKind,\n} from '../model/assist-edit'\n\n/**\n * Aglyn Assist level 2 — the GUIDE capability (AGL-1988, under AGL-1860).\n *\n * Level 1 answers from the docs. Level 2 answers about **the thing in front\n * of the user**: the console screen they are standing on. This module is the\n * whole of that knowledge, and three decisions shape it.\n *\n * ## 1. A curated descriptor, not a state dump\n *\n * The tempting design is to serialise the page — props, records, the loaded\n * list — into the prompt. That buys worse answers at higher cost: the model\n * gets rows when what it needs is *what this screen is for*, and every extra\n * token is billed per turn against a hard margin constraint.\n *\n * So the view block is assembled from a STATIC, in-repo table keyed by route\n * pattern, plus a fixed handful of scalars about the current org. Nothing is\n * read out of the customer's data to build it. That is also why the privacy\n * guard is cheap to state and cheap to prove: there is no path from a\n * Firestore document to this prompt except the two fields `safeOrgFacts`\n * names, and no path from client input except three sanitised strings.\n *\n * ## 2. One answer for both ends of the ICP range\n *\n * the requirement is a single feature that is \"easy for someone who\n * doesn't know code and even easier for someone who does\". Two modes would\n * be the obvious build and the wrong one — it makes the beginner choose a\n * label for themselves before they have a question, and it makes the\n * developer opt in to being taken seriously.\n *\n * Progressive disclosure instead: every view carries a `plain` layer (what\n * you can do here, in words a first-business owner reads) and a `technical`\n * layer (the route template, the ids in the path, the API or field names\n * behind it). The model leads with plain and appends the technical layer\n * under a fixed `Under the hood:` marker, which the panel renders collapsed.\n * Same answer, two depths, one message.\n *\n * ## 3. Actions are PROPOSALS, and a proposal cannot write\n *\n * \"Automate current view\" is level 2's edge and needs a hard boundary — the\n * AGL-1860 ladder puts acting in the besigner at level 3, after launch. The\n * boundary here is structural rather than behavioural, because a rule the\n * model is asked to follow is not a boundary:\n *\n * - An action descriptor has no field that could carry a write. There is no\n * method, no body, no endpoint — only a `route` template and the NAMES of\n * params. `assertInertActions()` proves that over the live table.\n * - The model never supplies a destination. It may name an `id` from the\n * closed set attached to the view it is already on, and supply values for\n * params that view declared. `resolveAssistProposal` builds the href from\n * the registry; an unknown id, a foreign id, or an undeclared param is\n * dropped rather than honoured.\n * - Confirming NAVIGATES. The destination form's own submit button, with\n * its own permission checks, stays the only thing that writes.\n *\n * `prefill` is deliberately `false` across the table today: no console page\n * reads `assist_*` search params yet, and a card that claims \"I filled that\n * in for you\" over a form that came up empty is worse than one that names the\n * values to type. The plumbing is here so switching a page on is a one-line\n * change, and `PREFILL_READY_ROUTES` is the allowlist that has to grow first.\n *\n * ## 4. The one action that edits, and where it opens (AGL-2906)\n *\n * Level 3 makes a single exception, on the besigner only: the `edit` action.\n * The server still writes nothing. The model proposes typed canvas operations\n * through a strict tool, the server validates them against the canvas the\n * request described, and the panel shows the change and applies it in the\n * author's own editor when they press Apply — an unsaved change, undone by\n * the editor's own undo. The rung is closed unless the org carries\n * `aiGenerative`, `release_ai_generative` is on for it and the caller holds\n * `ai.generate`. `offeredActions` hands an edit action only to a request that\n * cleared the rung, and `assertInertActions` fails any edit action offered\n * without it, or one that carries a destination the way a navigation does.\n */\n\n/**\n * One thing the assistant may PROPOSE from a given view, by navigating.\n *\n * Note what this interface cannot express. There is no `method`, no `body`,\n * no `endpoint`, no `submit` — a descriptor is a signpost, and the type is\n * the first line of the write boundary. Adding any of those fields would\n * break `assertInertActions()`, which is the point of writing it down.\n */\nexport interface AssistNavigateAction {\n /** `navigate`, or absent. */\n kind?: 'navigate'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm card. */\n label: string\n /** What the user will be looking at once they confirm. */\n outcome: string\n /**\n * Server-owned destination template (`/[orgSlug]/…`). Built here, never\n * supplied by the model — so the worst a bad completion can do is send the\n * user to a different page in their own console.\n */\n route: string\n /** Param names the model may supply values for. Anything else is dropped. */\n params: readonly string[]\n /**\n * Whether the destination actually reads `assist_*` search params. False\n * everywhere today — see the module header. When false the values are\n * shown on the card as \"use these\" rather than pushed into the URL.\n */\n prefill: boolean\n}\n\n/**\n * The besigner's edit action (AGL-2906). Its proposal is typed canvas\n * operations the panel applies on confirm — never a destination and never a\n * server write — so it carries none of a navigation's `route`, `params` or\n * `prefill`, and `assertInertActions` fails one that does.\n */\nexport interface AssistEditAction {\n kind: 'edit'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm button. */\n label: string\n /** What changes once the author confirms. */\n outcome: string\n /** The besigner documents the action opens on. */\n documents: readonly AssistEditDocumentKind[]\n}\n\nexport type AssistViewAction = AssistNavigateAction | AssistEditAction\n\n/** What a request cleared, which decides the actions a view offers it. */\nexport interface AssistActionRung {\n /**\n * The edit rung: `aiGenerative`, `release_ai_generative` and `ai.generate`,\n * on a versioned besigner route, with the canvas described.\n */\n edit: boolean\n}\n\n/** A console screen, described for the assistant rather than for a router. */\nexport interface AssistView {\n /** Registry key; also the analytics/debug handle. */\n key: string\n /** Route matcher. First match wins, so the table is ordered specific-first. */\n match: RegExp\n /** One line: what this screen is. */\n screen: string\n /** The beginner layer — what a person can do here, in plain words. */\n plain: readonly string[]\n /** The developer layer — route template, path ids, API surface. */\n technical: readonly string[]\n /** Proposals available from this view: navigations, and on the besigner the edit action. */\n actions: readonly AssistViewAction[]\n}\n\n/**\n * Destinations verified to read `assist_*` search params. EMPTY on purpose:\n * `assertInertActions()` fails any action with `prefill: true` whose route is\n * not listed, so turning prefill on requires wiring the page first rather\n * than remembering to.\n */\nexport const PREFILL_READY_ROUTES: readonly string[] = []\n\n/** Query-param prefix for proposed values. Namespaced so a page opting in\n * can never confuse an assistant suggestion with its own state. */\nexport const ASSIST_PREFILL_PREFIX = 'assist_'\n\nconst orgAction = (\n id: string,\n label: string,\n outcome: string,\n route: string,\n params: readonly string[] = [],\n): AssistNavigateAction => ({ id, label, outcome, route, params, prefill: false })\n\nconst isNavigateAction = (action: AssistViewAction): action is AssistNavigateAction =>\n action.kind !== 'edit'\n\n/**\n * The registry. Ordered specific-first — `describeView` takes the first\n * match, so a host sub-page must precede the host dashboard, and every\n * host route must precede the bare `/[orgSlug]` home.\n *\n * Content rule: every line here has to be TRUE of the shipped console. These\n * strings are asserted to the model as fact and it will repeat them, so a\n * guessed button label becomes a confidently wrong instruction. Where the\n * exact affordance is uncertain the line names the SCREEN and its purpose\n * and lets docs retrieval supply the steps — a vaguer sentence that is right\n * beats a specific one that is invented.\n */\nexport const ASSIST_VIEWS: readonly AssistView[] = [\n {\n key: 'besigner',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(screens|components|templates|layouts|emails)\\/.*\\/besigner$/,\n screen: 'The Besigner — the visual editor for a screen, component, template, layout or email.',\n plain: [\n 'Edit the page visually: pick an element on the canvas and change its text, styling and layout.',\n 'Work is saved to a draft version; publishing is a separate, deliberate step.',\n 'Preview shows the draft as a visitor would see it, without publishing.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/besigner (and the component, template, layout and email variants).',\n 'Editing writes to the version named in the path — versioning is the undo, so a save never overwrites what is live.',\n 'The preview route is the same path with /preview in place of /besigner.',\n ],\n actions: [\n {\n kind: 'edit',\n id: ASSIST_EDIT_ACTION_ID,\n label: 'Apply as draft',\n outcome: 'the proposed changes on the open canvas, unsaved until the document is saved',\n documents: ASSIST_EDIT_DOCUMENT_KINDS,\n },\n ],\n },\n {\n key: 'host-screens',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/screens(\\/|$)/,\n screen: 'Screens — the list of pages on this site.',\n plain: [\n 'Every page a visitor can reach is a screen listed here.',\n 'Open a screen to see its versions, then open the Besigner to edit it.',\n 'Publishing a screen version is what puts a change on the live site.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens; detail is .../screens/[screenId]/versions/[versionId]/view.',\n '[host] in the path is the site (host) document id, not the domain name.',\n ],\n actions: [\n orgAction(\n 'open.host.screens',\n 'Open Screens',\n 'the list of pages on this site',\n '/[orgSlug]/hosts/[host]/screens',\n ),\n ],\n },\n {\n key: 'host-theme',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/theme(\\/|$)/,\n screen: 'Theme — the site-wide colors, type and spacing every screen inherits.',\n plain: [\n 'Change something here and it changes everywhere on the site at once.',\n 'This is the place to fix a color or a font you keep re-setting on individual elements.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/theme.',\n 'Theme values resolve at render time, so screens pick them up without being republished individually.',\n ],\n actions: [],\n },\n {\n // The key is the analytics/debug handle and stays as it is; the section\n // it names is what was renamed. Both addresses match because the console\n // redirects the older one, so a client can report either.\n key: 'host-workflows',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(automation|workflows)(\\/|$)/,\n screen:\n 'Automation — workflows, actions and webhooks that run when something happens on the site.',\n plain: [\n 'A workflow is a trigger plus the actions that follow it, such as sending an email when a form is submitted.',\n 'Build the trigger first, then add the actions it should run.',\n 'Workflows, Actions and Webhooks are the three tabs of this section.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/automation, with ?tab=workflows, ?tab=actions or ?tab=webhooks.',\n 'Webhook actions let a workflow call an external endpoint; see the automation docs for the payload shape.',\n ],\n actions: [],\n },\n {\n key: 'host-products',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/products(\\/|$)/,\n screen: 'Products — the commerce catalog for this site.',\n plain: [\n 'Add and edit the things this site sells, including price and availability.',\n 'A product has to exist here before a screen can put it in front of a buyer.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/products.',\n 'Catalog data is site-scoped: products belong to [host], not to the workspace.',\n ],\n actions: [],\n },\n {\n key: 'host-redirects',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/redirects(\\/|$)/,\n screen: 'Redirects — rules that send one path on this site to another.',\n plain: [\n 'Use a redirect when a page has moved, so an old link still lands somewhere useful.',\n 'Each rule is a path to match and a destination to send it to.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/redirects.',\n 'Rules are evaluated by the tenant runtime on the request path, before the screen is resolved.',\n ],\n actions: [\n orgAction(\n 'open.host.redirects',\n 'Open Redirects',\n 'the redirect rules for this site',\n '/[orgSlug]/hosts/[host]/redirects',\n ['source', 'target'],\n ),\n ],\n },\n {\n key: 'host-analytics',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/analytics(\\/|$)/,\n screen: 'Analytics — traffic and visitor figures for this site.',\n plain: [\n 'See how many people visited, and which screens they landed on.',\n 'Figures cover the live site only — drafts and previews are not counted.',\n ],\n technical: ['Route: /[orgSlug]/hosts/[host]/analytics.'],\n actions: [],\n },\n {\n key: 'host-setup',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/setup(\\/|$)/,\n screen: 'Site setup — the site’s own settings, including its domain.',\n plain: [\n 'Connecting a custom domain starts here.',\n 'A domain change needs DNS records at whoever the domain is registered with; the console shows which ones.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/setup.',\n 'Owner/admin-only controls live on the sibling /admin page rather than here, so collaborators can use Setup safely.',\n ],\n actions: [\n orgAction(\n 'open.host.setup',\n 'Open site setup',\n 'this site’s settings, where a custom domain is connected',\n '/[orgSlug]/hosts/[host]/setup',\n ['domain'],\n ),\n ],\n },\n {\n key: 'host-data',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/data(\\/|$)/,\n screen: 'Site data — the datasets this site reads and writes.',\n plain: [\n 'A dataset is a structured list — products, posts, submissions — that screens can display.',\n 'Define the fields first; screens bind to them afterwards.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/data. The workspace-wide view is /[orgSlug]/data.',\n ],\n actions: [],\n },\n {\n key: 'host-dashboard',\n match: /^\\/[^/]+\\/hosts\\/[^/]+(\\/|$)/,\n screen: 'Site dashboard — the home page for one site in this workspace.',\n plain: [\n 'Everything about one site hangs off this page: its screens, theme, data, commerce and settings.',\n 'A workspace can hold several sites; this is one of them.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]. The [host] segment is the site document id.',\n ],\n actions: [],\n },\n {\n key: 'org-hosts',\n match: /^\\/[^/]+\\/hosts(\\/|$)/,\n screen: 'Sites — every site in this workspace.',\n plain: [\n 'Each row is a site with its own screens, theme and domain.',\n 'Agencies typically run one site per client from here.',\n ],\n technical: ['Route: /[orgSlug]/hosts.'],\n actions: [\n orgAction(\n 'open.org.hosts',\n 'Open Sites',\n 'every site in this workspace',\n '/[orgSlug]/hosts',\n ),\n ],\n },\n {\n key: 'org-billing',\n match: /^\\/[^/]+\\/billing(\\/|$)/,\n screen: 'Billing & plans — the workspace subscription, plan and invoices.',\n plain: [\n 'The current plan and what it includes, and where to change it.',\n 'Plan limits that stop you elsewhere in the console are set here.',\n ],\n technical: [\n 'Route: /[orgSlug]/billing.',\n 'Entitlements are resolved from the org plan; a limit reached elsewhere reports the entitlement it needs.',\n ],\n actions: [\n orgAction(\n 'open.billing',\n 'Open Billing & plans',\n 'the plan for this workspace and how to change it',\n '/[orgSlug]/billing',\n ),\n ],\n },\n {\n key: 'org-team',\n match: /^\\/[^/]+\\/team(\\/|$)/,\n screen: 'Team — the people in this workspace and what each may do.',\n plain: [\n 'Invite a teammate by email, and choose the role that decides what they can reach.',\n 'Removing someone here removes their access to every site in the workspace.',\n ],\n technical: [\n 'Route: /[orgSlug]/team; one member is /[orgSlug]/team/[uid].',\n 'Seat counts follow the plan, so an invite can be refused by the billing entitlement rather than by permissions.',\n ],\n actions: [\n orgAction(\n 'open.team',\n 'Open Team',\n 'the members of this workspace, where invites are sent',\n '/[orgSlug]/team',\n ['email', 'role'],\n ),\n ],\n },\n {\n key: 'org-data',\n match: /^\\/[^/]+\\/data(\\/|$)/,\n screen: 'Data — datasets shared across the whole workspace.',\n plain: [\n 'Structured lists defined once here can be used by any site in the workspace.',\n 'If a list only belongs to one site, define it on that site’s own Data page instead.',\n 'Define the fields before building the screen that displays them — screens bind to fields that already exist.',\n ],\n technical: [\n 'Route: /[orgSlug]/data. The per-site view is /[orgSlug]/hosts/[host]/data.',\n 'Workspace datasets are visible to every site in the workspace, so a field change here reaches all of them.',\n ],\n actions: [\n orgAction(\n 'open.org.data',\n 'Open Data',\n 'the datasets shared across this workspace',\n '/[orgSlug]/data',\n ),\n ],\n },\n {\n key: 'org-media',\n match: /^\\/[^/]+\\/media(\\/|$)/,\n screen: 'Media — images and files for this workspace.',\n plain: [\n 'Upload once here and use the file on any site in the workspace.',\n 'If the file belongs to one site only, upload it on that site’s own Media page instead.',\n 'Files are referenced by the screens that use them, so replacing a file changes it everywhere it appears.',\n ],\n technical: [\n 'Route: /[orgSlug]/media. The per-site library is /[orgSlug]/hosts/[host]/media.',\n 'Workspace media is shared across every site in the workspace; site media is scoped to that site.',\n ],\n actions: [\n orgAction(\n 'open.org.media',\n 'Open Media',\n 'the images and files shared across this workspace',\n '/[orgSlug]/media',\n ),\n ],\n },\n {\n key: 'org-plugins',\n match: /^\\/[^/]+\\/plugins(\\/|$)/,\n screen: 'Plugins — what is installed in this workspace.',\n plain: [\n 'A plugin adds a feature to the console, to your sites, or to both.',\n 'Open one to see its settings and the permissions it was granted.',\n ],\n technical: [\n 'Route: /[orgSlug]/plugins; one install is /[orgSlug]/plugins/[pluginRef].',\n 'Plugins run inside a sandbox; the permissions listed on the install page are the boundary.',\n ],\n actions: [],\n },\n {\n key: 'org-marketplace',\n match: /^\\/[^/]+\\/marketplace(\\/|$)/,\n screen: 'Marketplace — plugins and items available to install.',\n plain: ['Browse what can be added, and install into this workspace.'],\n technical: [\n 'Route: /[orgSlug]/marketplace; a listing is /[orgSlug]/marketplace/[listingId].',\n 'Publishing your own plugin is /[orgSlug]/marketplace/publish/plugin.',\n ],\n actions: [],\n },\n {\n key: 'org-support',\n match: /^\\/[^/]+\\/support(\\/|$)/,\n screen: 'Support — the channels for getting help.',\n plain: [\n 'Raise a ticket, or ask in the forum.',\n 'Which channels are available depends on the workspace plan.',\n ],\n technical: [\n 'Route: /[orgSlug]/support, which forwards to the channel the plan makes primary.',\n 'Channels are /[orgSlug]/support/tickets and /[orgSlug]/support/forum.',\n ],\n actions: [\n orgAction(\n 'open.support',\n 'Open Support',\n 'the support channels for this workspace',\n '/[orgSlug]/support',\n ),\n ],\n },\n {\n key: 'org-settings',\n match: /^\\/[^/]+\\/settings(\\/|$)/,\n screen: 'Workspace settings — the workspace’s own name, slug and options.',\n plain: ['Settings that apply to the whole workspace rather than to one site.'],\n technical: [\n 'Route: /[orgSlug]/settings.',\n 'The workspace slug is the [orgSlug] segment in every console URL, so changing it changes those links.',\n ],\n actions: [],\n },\n {\n key: 'org-home',\n match: /^\\/[^/]+\\/?$/,\n screen: 'Workspace home — the landing page for one workspace.',\n plain: [\n 'The starting point for a workspace: its sites, and the workspace-wide areas beside them.',\n ],\n technical: ['Route: /[orgSlug].'],\n actions: [],\n },\n]\n\n/** The view the user is standing on, or null where the route is unknown. */\nexport function describeView(route: string): AssistView | null {\n const path = sanitiseRoute(route)\n if (!path) return null\n return ASSIST_VIEWS.find((view) => view.match.test(path)) ?? null\n}\n\n/**\n * The actions a view offers a request: every navigation, and the edit action\n * only to a request that cleared the edit rung.\n */\nexport function offeredActions(\n view: AssistView | null,\n rung: AssistActionRung,\n): readonly AssistViewAction[] {\n if (!view) return []\n return view.actions.filter((action) => action.kind !== 'edit' || rung.edit)\n}\n\n/**\n * Client-supplied strings that end up inside a SYSTEM block have to be\n * treated as hostile, not merely untidy. `route` is whatever the panel says\n * the pathname is, and an attacker who can shape it can otherwise write\n * newlines, backticks and prose into the model's instructions — including a\n * forged action fence, which is the one construct in this feature the model\n * is told to treat as meaningful.\n *\n * So the allowed alphabet is exactly what a console path is made of. Anything\n * else is dropped rather than escaped: there is no legitimate route that\n * needs a backtick, and a sanitiser that tries to preserve intent is a\n * sanitiser with a bypass in it.\n */\nexport function sanitiseRoute(route: string): string {\n const cleaned = String(route ?? '')\n .replace(/[^A-Za-z0-9/_\\-.[\\]]/g, '')\n .slice(0, 200)\n return cleaned.startsWith('/') ? cleaned : ''\n}\n\n/**\n * Same reasoning as `sanitiseRoute`, for a Firestore-shaped document id.\n *\n * Rejects rather than truncates, which is the difference between refusing an\n * id and quietly pointing at a different one: `slice(0, 64)` on an over-long\n * value yields a well-formed id for some OTHER document, and this function's\n * output is substituted straight into a destination path.\n */\nexport function sanitiseId(value: string): string {\n const raw = String(value ?? '')\n return raw.length <= 64 && /^[A-Za-z0-9_-]+$/.test(raw) ? raw : ''\n}\n\n/**\n * The ONLY fields of the org document that may enter a prompt.\n *\n * An allowlist rather than a denylist, and stated as a literal rather than\n * derived, because the failure mode is silent: the org doc grows a field one\n * day — a Stripe customer id, an owner's email, a support note — and a\n * spread would carry it into a third party's API without anything failing.\n * Both fields named here are already on screen for any member who can open\n * the panel, so nothing is disclosed that the asker could not read anyway.\n *\n * The plan is the one the workspace resolves to, as the console's badges name\n * it (AGL-3034): a canceled subscription's stored plan grants nothing, and a\n * staff comp grants a plan the stored field may not name.\n */\nexport function safeOrgFacts(org: Record<string, unknown>): {\n name: string\n plan: string\n} {\n return {\n name: String(org?.['name'] ?? '').slice(0, 120),\n plan: resolveEffectivePlan(org as never).slice(0, 40),\n }\n}\n\n/**\n * The per-VIEW system block — the screen description, and nothing about who\n * is looking at it.\n *\n * The split from `viewFactsBlock` is the whole caching design, and it was\n * not the first shape this took. Folding the workspace name, plan, slug and\n * host id in here reads more natural and quietly destroys the cache: a\n * prefix carrying the tenant's name is unique to that tenant, so on a\n * multi-tenant console every org warms its own copy and the entry is usually\n * cold when it matters. Derived purely from the route, the same block serves\n * every workspace asking anything on that screen — which on a shared console\n * is the difference between a cache that pays and one that mostly writes.\n *\n * So this block is a pure function of the registry and the rung — two\n * variants a route at most, neither carrying a tenant byte — and everything\n * that varies per request lives after the breakpoint.\n */\nexport function viewScreenBlock(\n view: AssistView | null,\n rung: AssistActionRung = { edit: false },\n): string {\n const lines: string[] = []\n if (view) {\n lines.push(`This screen: ${view.screen}`)\n if (view.plain.length) {\n lines.push('What the user can do here:')\n for (const line of view.plain) lines.push(`- ${line}`)\n }\n if (view.technical.length) {\n lines.push('Technical detail for the \"Under the hood\" line:')\n for (const line of view.technical) lines.push(`- ${line}`)\n }\n const offered = offeredActions(view, rung)\n const navigations = offered.filter(isNavigateAction)\n if (navigations.length) {\n lines.push(\n 'Actions you may PROPOSE from this screen (ids are exact; propose at most one, and only when the user asked to get something done):',\n )\n for (const action of navigations) {\n const params = action.params.length\n ? ` params: ${action.params.join(', ')}`\n : ' params: none'\n lines.push(`- id \"${action.id}\" — ${action.label}: opens ${action.outcome}.${params}`)\n }\n } else if (offered.length) {\n lines.push('This screen offers no page to open. Do not emit an action block here.')\n } else {\n lines.push(\n 'This screen offers no proposable actions. Do not emit an action block here.',\n )\n }\n for (const action of offered) {\n if (action.kind !== 'edit') continue\n lines.push(\n `Edits you may PROPOSE on this screen (id \"${action.id}\"): through the ${ASSIST_EDIT_TOOL_NAME} tool described below, never an action block.`,\n )\n }\n } else {\n lines.push(\n 'This exact screen is not in the assistant’s screen index. Answer from the documentation and describe navigation in words rather than asserting what this page contains.',\n 'Do not emit an action block.',\n )\n }\n return lines.join('\\n')\n}\n\n/**\n * The per-REQUEST facts. Everything here varies by workspace or by page, so\n * it sits after the last cache breakpoint where it invalidates nothing.\n *\n * Only `safeOrgFacts`' two allowlisted fields and the three sanitised client\n * strings ever reach this — see the module header.\n */\nexport function viewFactsBlock(facts: {\n route: string\n hostId: string\n orgSlug: string\n name: string\n plan: string\n}): string {\n const lines: string[] = ['Where the user is right now:']\n lines.push(`- Console page path: ${facts.route}`)\n if (facts.orgSlug) lines.push(`- Workspace URL slug: ${facts.orgSlug}`)\n if (facts.hostId) lines.push(`- Selected site (host) id: ${facts.hostId}`)\n if (facts.name) lines.push(`- Workspace name: ${facts.name}`)\n if (facts.plan) lines.push(`- Workspace plan: ${facts.plan}`)\n return lines.join('\\n')\n}\n\n/* ------------------------------------------------------------------ *\n * The proposal channel\n * ------------------------------------------------------------------ */\n\n/** Fence the model wraps a proposal in. Kept out of the visible answer. */\nexport const ASSIST_ACTION_FENCE = '```aglyn:action'\n\n/**\n * How much of `raw` is safe to stream to the user.\n *\n * The proposal fence must never reach the panel as text — a user watching\n * raw JSON appear mid-answer reads it as the assistant breaking. Trimming\n * only at the end is not enough either, because by then the tokens have\n * already been emitted as deltas.\n *\n * So the visible prefix is recomputed on every delta, and the tail is held\n * back only while it could still turn out to be the start of a fence. That\n * matters for latency: holding a fixed window would delay every answer by\n * the fence's length, whereas this holds nothing back unless the text\n * genuinely ends in a backtick run.\n */\nexport function visibleAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n if (cut >= 0) return raw.slice(0, cut)\n for (let n = Math.min(ASSIST_ACTION_FENCE.length - 1, raw.length); n > 0; n--) {\n if (ASSIST_ACTION_FENCE.startsWith(raw.slice(raw.length - n))) {\n return raw.slice(0, raw.length - n)\n }\n }\n return raw\n}\n\n/**\n * The visible answer once the stream has ENDED — no holdback.\n *\n * `visibleAssistText` withholds a tail that might still become a fence,\n * which is right mid-stream and wrong at the end: an answer that genuinely\n * closes on a code fence would otherwise lose its last three characters\n * permanently, both on screen and in the stored exchange. Once there are no\n * more deltas the ambiguity is resolved, so the only cut left is a real one.\n */\nexport function finalAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n return cut >= 0 ? raw.slice(0, cut) : raw\n}\n\n/** The raw JSON body between the fences, if the model emitted one. */\nexport function extractAssistAction(raw: string): string | null {\n const start = raw.indexOf(ASSIST_ACTION_FENCE)\n if (start < 0) return null\n const bodyStart = start + ASSIST_ACTION_FENCE.length\n const end = raw.indexOf('```', bodyStart)\n const body = (end < 0 ? raw.slice(bodyStart) : raw.slice(bodyStart, end)).trim()\n return body || null\n}\n\n/** A proposal that survived validation — inert, and safe to show. */\nexport interface AssistProposal {\n id: string\n label: string\n outcome: string\n /** Fully-resolved console path. Server-built; the model never sees it. */\n href: string\n /** Values the model suggested, in the order the action declared them. */\n values: ReadonlyArray<{ name: string; value: string }>\n /** Whether `href` carries the values, or the card must ask the user to type them. */\n prefill: boolean\n}\n\nconst MAX_PARAM_VALUE_CHARS = 200\n\n/**\n * Turn a model-emitted action block into a proposal, or nothing.\n *\n * Everything here is a narrowing. The id must be one the CURRENT view\n * offers as a navigation — not merely a real id somewhere in the registry,\n * because \"propose the billing page from the besigner\" is exactly the kind\n * of plausible wandering that makes an assistant feel unsafe, and not the\n * edit action either, which is never a destination. Param names must be ones\n * that action declared. The href is composed from the registry template and\n * the caller's own server-resolved slug and host, so a completion cannot\n * steer the user anywhere the registry does not already point.\n *\n * Anything unexpected returns null. A dropped proposal costs the user a\n * button; an honoured bad one costs them trust.\n */\nexport function resolveAssistProposal(\n rawBlock: string | null,\n view: AssistView | null,\n scope: { orgSlug: string; hostId: string },\n): AssistProposal | null {\n if (!rawBlock || !view) return null\n const navigations = view.actions.filter(isNavigateAction)\n if (!navigations.length) return null\n\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(rawBlock) as Record<string, unknown>\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n\n const id = String(parsed['id'] ?? '').trim()\n const action = navigations.find((candidate) => candidate.id === id)\n if (!action) return null\n\n const supplied = (parsed['params'] ?? {}) as Record<string, unknown>\n const values: Array<{ name: string; value: string }> = []\n if (supplied && typeof supplied === 'object' && !Array.isArray(supplied)) {\n for (const name of action.params) {\n const value = String(supplied[name] ?? '')\n .replace(/[\\r\\n]+/g, ' ')\n .trim()\n .slice(0, MAX_PARAM_VALUE_CHARS)\n if (value) values.push({ name, value })\n }\n }\n\n const href = buildActionHref(action, scope, values)\n if (!href) return null\n\n return {\n id: action.id,\n label: action.label,\n outcome: action.outcome,\n href,\n values,\n prefill: action.prefill,\n }\n}\n\n/**\n * Compose the destination from the registry template and the server's own\n * scope values. Returns '' when the template needs a segment this page does\n * not have — a half-substituted path like `/acme/hosts//setup` is a 404 with\n * a confirm button on it, which is worse than no button.\n */\nfunction buildActionHref(\n action: AssistNavigateAction,\n scope: { orgSlug: string; hostId: string },\n values: ReadonlyArray<{ name: string; value: string }>,\n): string {\n const orgSlug = sanitiseId(scope.orgSlug)\n const hostId = sanitiseId(scope.hostId)\n let path = action.route\n if (path.includes('[orgSlug]')) {\n if (!orgSlug) return ''\n path = path.replace('[orgSlug]', orgSlug)\n }\n if (path.includes('[host]')) {\n if (!hostId) return ''\n path = path.replace('[host]', hostId)\n }\n // Nothing above can produce these, which is exactly why they are worth\n // asserting: this is the last line before a path reaches a router.\n if (!path.startsWith('/') || path.includes('..') || path.includes('//')) return ''\n if (/\\[|\\]/.test(path)) return ''\n\n if (!action.prefill || !values.length) return path\n const query = values\n .map(\n ({ name, value }) =>\n `${ASSIST_PREFILL_PREFIX}${encodeURIComponent(name)}=${encodeURIComponent(value)}`,\n )\n .join('&')\n return `${path}?${query}`\n}\n\n/**\n * The write boundary, asserted over the live table rather than described in\n * a comment. Called by the spec; exported so the check travels with the data\n * it checks rather than living in a test file someone can delete.\n *\n * Every action is inert UNLESS it is an edit action and the request cleared\n * the edit rung: a navigation carries no write-capable field and no ops, an\n * edit action carries no destination, and an edit action offered to a\n * request below the rung is a violation. `offer` is the function the doors\n * decide with; a spec passes a broken one to show this check can fail.\n *\n * Returns the list of violations so a failure names the offending action.\n */\nexport function assertInertActions(\n views: readonly AssistView[] = ASSIST_VIEWS,\n rung: AssistActionRung = { edit: false },\n offer: typeof offeredActions = offeredActions,\n): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n const forbidden = ['method', 'body', 'endpoint', 'submit', 'url', 'api', 'mutation']\n for (const view of views) {\n const offered = offer(view, rung)\n for (const action of view.actions) {\n const where = `${view.key}/${action.id}`\n if (seen.has(action.id)) problems.push(`${where}: duplicate action id`)\n seen.add(action.id)\n for (const key of Object.keys(action)) {\n if (forbidden.includes(key.toLowerCase())) {\n problems.push(`${where}: action carries a write-capable field \"${key}\"`)\n }\n }\n if (action.kind === 'edit') {\n if (!rung.edit && offered.includes(action)) {\n problems.push(\n `${where}: an edit action is offered to a request that did not clear the edit rung`,\n )\n }\n for (const key of ['route', 'params', 'prefill']) {\n if (key in action) {\n problems.push(\n `${where}: an edit action carries \"${key}\" — it applies ops on the open canvas and never navigates`,\n )\n }\n }\n const documents = action.documents ?? []\n if (\n !documents.length ||\n documents.some((kind) => !ASSIST_EDIT_DOCUMENT_KINDS.includes(kind))\n ) {\n problems.push(`${where}: an edit action names a document the edit rung does not open on`)\n }\n continue\n }\n if ('ops' in action) {\n problems.push(`${where}: only an edit action carries ops`)\n }\n if (!action.route.startsWith('/')) {\n problems.push(`${where}: route is not a root-relative console path`)\n }\n if (action.route.includes('/api/')) {\n problems.push(`${where}: route points at an API endpoint`)\n }\n if (action.prefill && !PREFILL_READY_ROUTES.includes(action.route)) {\n problems.push(\n `${where}: prefill:true but ${action.route} is not in PREFILL_READY_ROUTES — wire the page to read ${ASSIST_PREFILL_PREFIX}* params first`,\n )\n }\n }\n }\n return problems\n}\n"],"names":["resolveEffectivePlan","ASSIST_EDIT_ACTION_ID","ASSIST_EDIT_DOCUMENT_KINDS","ASSIST_EDIT_TOOL_NAME","PREFILL_READY_ROUTES","ASSIST_PREFILL_PREFIX","orgAction","id","label","outcome","route","params","prefill","isNavigateAction","action","kind","ASSIST_VIEWS","key","match","screen","plain","technical","actions","documents","describeView","path","sanitiseRoute","find","view","test","offeredActions","rung","filter","edit","cleaned","String","replace","slice","startsWith","sanitiseId","value","raw","length","safeOrgFacts","org","name","plan","viewScreenBlock","lines","push","line","offered","navigations","join","viewFactsBlock","facts","orgSlug","hostId","ASSIST_ACTION_FENCE","visibleAssistText","cut","indexOf","n","Math","min","finalAssistText","extractAssistAction","start","bodyStart","end","body","trim","MAX_PARAM_VALUE_CHARS","resolveAssistProposal","rawBlock","scope","parsed","JSON","parse","candidate","supplied","values","Array","isArray","href","buildActionHref","includes","query","map","encodeURIComponent","assertInertActions","views","offer","problems","seen","Set","forbidden","where","has","add","Object","keys","toLowerCase","some"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,QAAQ,2CAA0C;AAC/E,SACEC,qBAAqB,EACrBC,0BAA0B,EAC1BC,qBAAqB,QAEhB,0BAAsB;AA0J7B;;;;;CAKC,GACD,OAAO,MAAMC,uBAA0C,EAAE,CAAA;AAEzD;iEACiE,GACjE,OAAO,MAAMC,wBAAwB,UAAS;AAE9C,MAAMC,YAAY,CAChBC,IACAC,OACAC,SACAC,OACAC,SAA4B,EAAE,GACJ,CAAA;QAAEJ;QAAIC;QAAOC;QAASC;QAAOC;QAAQC,SAAS;IAAM,CAAA;AAEhF,MAAMC,mBAAmB,CAACC,SACxBA,OAAOC,IAAI,KAAK;AAElB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,eAAsC;IACjD;QACEC,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACP;gBACEP,MAAM;gBACNR,IAAIN;gBACJO,OAAO;gBACPC,SAAS;gBACTc,WAAWrB;YACb;SACD;IACH;IACA;QACEe,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,qBACA,gBACA,kCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACE,wEAAwE;QACxE,yEAAyE;QACzE,0DAA0D;QAC1DL,KAAK;QACLC,OAAO;QACPC,QACE;QACFC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,uBACA,kBACA,oCACA,qCACA;gBAAC;gBAAU;aAAS;SAEvB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA4C;QACxDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mBACA,mBACA,4DACA,iCACA;gBAAC;aAAS;SAEb;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA2B;QACvCC,SAAS;YACPhB,UACE,kBACA,cACA,gCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gBACA,wBACA,oDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,aACA,aACA,yDACA,mBACA;gBAAC;gBAAS;aAAO;SAEpB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,iBACA,aACA,6CACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,kBACA,cACA,qDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YAAC;SAA6D;QACrEC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gBACA,gBACA,2CACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YAAC;SAAsE;QAC9EC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;SACD;QACDC,WAAW;YAAC;SAAqB;QACjCC,SAAS,EAAE;IACb;CACD,CAAA;AAED,0EAA0E,GAC1E,OAAO,SAASE,aAAad,KAAa;QAGjCM;IAFP,MAAMS,OAAOC,cAAchB;IAC3B,IAAI,CAACe,MAAM,OAAO;IAClB,QAAOT,qBAAAA,aAAaW,IAAI,CAAC,CAACC,OAASA,KAAKV,KAAK,CAACW,IAAI,CAACJ,kBAA5CT,qBAAsD;AAC/D;AAEA;;;CAGC,GACD,OAAO,SAASc,eACdF,IAAuB,EACvBG,IAAsB;IAEtB,IAAI,CAACH,MAAM,OAAO,EAAE;IACpB,OAAOA,KAAKN,OAAO,CAACU,MAAM,CAAC,CAAClB,SAAWA,OAAOC,IAAI,KAAK,UAAUgB,KAAKE,IAAI;AAC5E;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASP,cAAchB,KAAa;IACzC,MAAMwB,UAAUC,OAAOzB,gBAAAA,QAAS,IAC7B0B,OAAO,CAAC,yBAAyB,IACjCC,KAAK,CAAC,GAAG;IACZ,OAAOH,QAAQI,UAAU,CAAC,OAAOJ,UAAU;AAC7C;AAEA;;;;;;;CAOC,GACD,OAAO,SAASK,WAAWC,KAAa;IACtC,MAAMC,MAAMN,OAAOK,gBAAAA,QAAS;IAC5B,OAAOC,IAAIC,MAAM,IAAI,MAAM,mBAAmBb,IAAI,CAACY,OAAOA,MAAM;AAClE;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASE,aAAaC,GAA4B;;IAIvD,OAAO;QACLC,MAAMV,eAAOS,uBAAAA,GAAK,CAAC,OAAO,mBAAI,IAAIP,KAAK,CAAC,GAAG;QAC3CS,MAAM9C,qBAAqB4C,KAAcP,KAAK,CAAC,GAAG;IACpD;AACF;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASU,gBACdnB,IAAuB,EACvBG,OAAyB;IAAEE,MAAM;AAAM,CAAC;IAExC,MAAMe,QAAkB,EAAE;IAC1B,IAAIpB,MAAM;QACRoB,MAAMC,IAAI,CAAC,CAAC,aAAa,EAAErB,KAAKT,MAAM,EAAE;QACxC,IAAIS,KAAKR,KAAK,CAACsB,MAAM,EAAE;YACrBM,MAAMC,IAAI,CAAC;YACX,KAAK,MAAMC,QAAQtB,KAAKR,KAAK,CAAE4B,MAAMC,IAAI,CAAC,CAAC,EAAE,EAAEC,MAAM;QACvD;QACA,IAAItB,KAAKP,SAAS,CAACqB,MAAM,EAAE;YACzBM,MAAMC,IAAI,CAAC;YACX,KAAK,MAAMC,QAAQtB,KAAKP,SAAS,CAAE2B,MAAMC,IAAI,CAAC,CAAC,EAAE,EAAEC,MAAM;QAC3D;QACA,MAAMC,UAAUrB,eAAeF,MAAMG;QACrC,MAAMqB,cAAcD,QAAQnB,MAAM,CAACnB;QACnC,IAAIuC,YAAYV,MAAM,EAAE;YACtBM,MAAMC,IAAI,CACR;YAEF,KAAK,MAAMnC,UAAUsC,YAAa;gBAChC,MAAMzC,SAASG,OAAOH,MAAM,CAAC+B,MAAM,GAC/B,CAAC,SAAS,EAAE5B,OAAOH,MAAM,CAAC0C,IAAI,CAAC,OAAO,GACtC;gBACJL,MAAMC,IAAI,CAAC,CAAC,MAAM,EAAEnC,OAAOP,EAAE,CAAC,IAAI,EAAEO,OAAON,KAAK,CAAC,QAAQ,EAAEM,OAAOL,OAAO,CAAC,CAAC,EAAEE,QAAQ;YACvF;QACF,OAAO,IAAIwC,QAAQT,MAAM,EAAE;YACzBM,MAAMC,IAAI,CAAC;QACb,OAAO;YACLD,MAAMC,IAAI,CACR;QAEJ;QACA,KAAK,MAAMnC,UAAUqC,QAAS;YAC5B,IAAIrC,OAAOC,IAAI,KAAK,QAAQ;YAC5BiC,MAAMC,IAAI,CACR,CAAC,0CAA0C,EAAEnC,OAAOP,EAAE,CAAC,gBAAgB,EAAEJ,sBAAsB,6CAA6C,CAAC;QAEjJ;IACF,OAAO;QACL6C,MAAMC,IAAI,CACR,2KACA;IAEJ;IACA,OAAOD,MAAMK,IAAI,CAAC;AACpB;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,KAM9B;IACC,MAAMP,QAAkB;QAAC;KAA+B;IACxDA,MAAMC,IAAI,CAAC,CAAC,qBAAqB,EAAEM,MAAM7C,KAAK,EAAE;IAChD,IAAI6C,MAAMC,OAAO,EAAER,MAAMC,IAAI,CAAC,CAAC,sBAAsB,EAAEM,MAAMC,OAAO,EAAE;IACtE,IAAID,MAAME,MAAM,EAAET,MAAMC,IAAI,CAAC,CAAC,2BAA2B,EAAEM,MAAME,MAAM,EAAE;IACzE,IAAIF,MAAMV,IAAI,EAAEG,MAAMC,IAAI,CAAC,CAAC,kBAAkB,EAAEM,MAAMV,IAAI,EAAE;IAC5D,IAAIU,MAAMT,IAAI,EAAEE,MAAMC,IAAI,CAAC,CAAC,kBAAkB,EAAEM,MAAMT,IAAI,EAAE;IAC5D,OAAOE,MAAMK,IAAI,CAAC;AACpB;AAEA;;sEAEsE,GAEtE,yEAAyE,GACzE,OAAO,MAAMK,sBAAsB,kBAAiB;AAEpD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,kBAAkBlB,GAAW;IAC3C,MAAMmB,MAAMnB,IAAIoB,OAAO,CAACH;IACxB,IAAIE,OAAO,GAAG,OAAOnB,IAAIJ,KAAK,CAAC,GAAGuB;IAClC,IAAK,IAAIE,IAAIC,KAAKC,GAAG,CAACN,oBAAoBhB,MAAM,GAAG,GAAGD,IAAIC,MAAM,GAAGoB,IAAI,GAAGA,IAAK;QAC7E,IAAIJ,oBAAoBpB,UAAU,CAACG,IAAIJ,KAAK,CAACI,IAAIC,MAAM,GAAGoB,KAAK;YAC7D,OAAOrB,IAAIJ,KAAK,CAAC,GAAGI,IAAIC,MAAM,GAAGoB;QACnC;IACF;IACA,OAAOrB;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASwB,gBAAgBxB,GAAW;IACzC,MAAMmB,MAAMnB,IAAIoB,OAAO,CAACH;IACxB,OAAOE,OAAO,IAAInB,IAAIJ,KAAK,CAAC,GAAGuB,OAAOnB;AACxC;AAEA,oEAAoE,GACpE,OAAO,SAASyB,oBAAoBzB,GAAW;IAC7C,MAAM0B,QAAQ1B,IAAIoB,OAAO,CAACH;IAC1B,IAAIS,QAAQ,GAAG,OAAO;IACtB,MAAMC,YAAYD,QAAQT,oBAAoBhB,MAAM;IACpD,MAAM2B,MAAM5B,IAAIoB,OAAO,CAAC,OAAOO;IAC/B,MAAME,OAAO,AAACD,CAAAA,MAAM,IAAI5B,IAAIJ,KAAK,CAAC+B,aAAa3B,IAAIJ,KAAK,CAAC+B,WAAWC,IAAG,EAAGE,IAAI;IAC9E,OAAOD,QAAQ;AACjB;AAeA,MAAME,wBAAwB;AAE9B;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,sBACdC,QAAuB,EACvB9C,IAAuB,EACvB+C,KAA0C;QAcxBC,YAIAA;IAhBlB,IAAI,CAACF,YAAY,CAAC9C,MAAM,OAAO;IAC/B,MAAMwB,cAAcxB,KAAKN,OAAO,CAACU,MAAM,CAACnB;IACxC,IAAI,CAACuC,YAAYV,MAAM,EAAE,OAAO;IAEhC,IAAIkC;IACJ,IAAI;QACFA,SAASC,KAAKC,KAAK,CAACJ;IACtB,EAAE,eAAM;QACN,OAAO;IACT;IACA,IAAI,CAACE,UAAU,OAAOA,WAAW,UAAU,OAAO;IAElD,MAAMrE,KAAK4B,QAAOyC,aAAAA,MAAM,CAAC,KAAK,YAAZA,aAAgB,IAAIL,IAAI;IAC1C,MAAMzD,SAASsC,YAAYzB,IAAI,CAAC,CAACoD,YAAcA,UAAUxE,EAAE,KAAKA;IAChE,IAAI,CAACO,QAAQ,OAAO;IAEpB,MAAMkE,YAAYJ,iBAAAA,MAAM,CAAC,SAAS,YAAhBA,iBAAoB,CAAC;IACvC,MAAMK,SAAiD,EAAE;IACzD,IAAID,YAAY,OAAOA,aAAa,YAAY,CAACE,MAAMC,OAAO,CAACH,WAAW;QACxE,KAAK,MAAMnC,QAAQ/B,OAAOH,MAAM,CAAE;gBACXqE;YAArB,MAAMxC,QAAQL,QAAO6C,iBAAAA,QAAQ,CAACnC,KAAK,YAAdmC,iBAAkB,IACpC5C,OAAO,CAAC,YAAY,KACpBmC,IAAI,GACJlC,KAAK,CAAC,GAAGmC;YACZ,IAAIhC,OAAOyC,OAAOhC,IAAI,CAAC;gBAAEJ;gBAAML;YAAM;QACvC;IACF;IAEA,MAAM4C,OAAOC,gBAAgBvE,QAAQ6D,OAAOM;IAC5C,IAAI,CAACG,MAAM,OAAO;IAElB,OAAO;QACL7E,IAAIO,OAAOP,EAAE;QACbC,OAAOM,OAAON,KAAK;QACnBC,SAASK,OAAOL,OAAO;QACvB2E;QACAH;QACArE,SAASE,OAAOF,OAAO;IACzB;AACF;AAEA;;;;;CAKC,GACD,SAASyE,gBACPvE,MAA4B,EAC5B6D,KAA0C,EAC1CM,MAAsD;IAEtD,MAAMzB,UAAUjB,WAAWoC,MAAMnB,OAAO;IACxC,MAAMC,SAASlB,WAAWoC,MAAMlB,MAAM;IACtC,IAAIhC,OAAOX,OAAOJ,KAAK;IACvB,IAAIe,KAAK6D,QAAQ,CAAC,cAAc;QAC9B,IAAI,CAAC9B,SAAS,OAAO;QACrB/B,OAAOA,KAAKW,OAAO,CAAC,aAAaoB;IACnC;IACA,IAAI/B,KAAK6D,QAAQ,CAAC,WAAW;QAC3B,IAAI,CAAC7B,QAAQ,OAAO;QACpBhC,OAAOA,KAAKW,OAAO,CAAC,UAAUqB;IAChC;IACA,uEAAuE;IACvE,mEAAmE;IACnE,IAAI,CAAChC,KAAKa,UAAU,CAAC,QAAQb,KAAK6D,QAAQ,CAAC,SAAS7D,KAAK6D,QAAQ,CAAC,OAAO,OAAO;IAChF,IAAI,QAAQzD,IAAI,CAACJ,OAAO,OAAO;IAE/B,IAAI,CAACX,OAAOF,OAAO,IAAI,CAACqE,OAAOvC,MAAM,EAAE,OAAOjB;IAC9C,MAAM8D,QAAQN,OACXO,GAAG,CACF,CAAC,EAAE3C,IAAI,EAAEL,KAAK,EAAE,GACd,GAAGnC,wBAAwBoF,mBAAmB5C,MAAM,CAAC,EAAE4C,mBAAmBjD,QAAQ,EAErFa,IAAI,CAAC;IACR,OAAO,GAAG5B,KAAK,CAAC,EAAE8D,OAAO;AAC3B;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASG,mBACdC,QAA+B3E,YAAY,EAC3Ce,OAAyB;IAAEE,MAAM;AAAM,CAAC,EACxC2D,QAA+B9D,cAAc;IAE7C,MAAM+D,WAAqB,EAAE;IAC7B,MAAMC,OAAO,IAAIC;IACjB,MAAMC,YAAY;QAAC;QAAU;QAAQ;QAAY;QAAU;QAAO;QAAO;KAAW;IACpF,KAAK,MAAMpE,QAAQ+D,MAAO;QACxB,MAAMxC,UAAUyC,MAAMhE,MAAMG;QAC5B,KAAK,MAAMjB,UAAUc,KAAKN,OAAO,CAAE;YACjC,MAAM2E,QAAQ,GAAGrE,KAAKX,GAAG,CAAC,CAAC,EAAEH,OAAOP,EAAE,EAAE;YACxC,IAAIuF,KAAKI,GAAG,CAACpF,OAAOP,EAAE,GAAGsF,SAAS5C,IAAI,CAAC,GAAGgD,MAAM,qBAAqB,CAAC;YACtEH,KAAKK,GAAG,CAACrF,OAAOP,EAAE;YAClB,KAAK,MAAMU,OAAOmF,OAAOC,IAAI,CAACvF,QAAS;gBACrC,IAAIkF,UAAUV,QAAQ,CAACrE,IAAIqF,WAAW,KAAK;oBACzCT,SAAS5C,IAAI,CAAC,GAAGgD,MAAM,wCAAwC,EAAEhF,IAAI,CAAC,CAAC;gBACzE;YACF;YACA,IAAIH,OAAOC,IAAI,KAAK,QAAQ;oBAaRD;gBAZlB,IAAI,CAACiB,KAAKE,IAAI,IAAIkB,QAAQmC,QAAQ,CAACxE,SAAS;oBAC1C+E,SAAS5C,IAAI,CACX,GAAGgD,MAAM,yEAAyE,CAAC;gBAEvF;gBACA,KAAK,MAAMhF,OAAO;oBAAC;oBAAS;oBAAU;iBAAU,CAAE;oBAChD,IAAIA,OAAOH,QAAQ;wBACjB+E,SAAS5C,IAAI,CACX,GAAGgD,MAAM,0BAA0B,EAAEhF,IAAI,yDAAyD,CAAC;oBAEvG;gBACF;gBACA,MAAMM,aAAYT,oBAAAA,OAAOS,SAAS,YAAhBT,oBAAoB,EAAE;gBACxC,IACE,CAACS,UAAUmB,MAAM,IACjBnB,UAAUgF,IAAI,CAAC,CAACxF,OAAS,CAACb,2BAA2BoF,QAAQ,CAACvE,QAC9D;oBACA8E,SAAS5C,IAAI,CAAC,GAAGgD,MAAM,gEAAgE,CAAC;gBAC1F;gBACA;YACF;YACA,IAAI,SAASnF,QAAQ;gBACnB+E,SAAS5C,IAAI,CAAC,GAAGgD,MAAM,iCAAiC,CAAC;YAC3D;YACA,IAAI,CAACnF,OAAOJ,KAAK,CAAC4B,UAAU,CAAC,MAAM;gBACjCuD,SAAS5C,IAAI,CAAC,GAAGgD,MAAM,2CAA2C,CAAC;YACrE;YACA,IAAInF,OAAOJ,KAAK,CAAC4E,QAAQ,CAAC,UAAU;gBAClCO,SAAS5C,IAAI,CAAC,GAAGgD,MAAM,iCAAiC,CAAC;YAC3D;YACA,IAAInF,OAAOF,OAAO,IAAI,CAACR,qBAAqBkF,QAAQ,CAACxE,OAAOJ,KAAK,GAAG;gBAClEmF,SAAS5C,IAAI,CACX,GAAGgD,MAAM,mBAAmB,EAAEnF,OAAOJ,KAAK,CAAC,wDAAwD,EAAEL,sBAAsB,cAAc,CAAC;YAE9I;QACF;IACF;IACA,OAAOwF;AACT"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/assist-view-context.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 { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n ASSIST_EDIT_ACTION_ID,\n ASSIST_EDIT_DOCUMENT_KINDS,\n ASSIST_EDIT_TOOL_NAME,\n type AssistEditDocumentKind,\n} from '../model/assist-edit'\n\n/**\n * Aglyn Assist level 2 — the GUIDE capability (AGL-1988, under AGL-1860).\n *\n * Level 1 answers from the docs. Level 2 answers about **the thing in front\n * of the user**: the console screen they are standing on. This module is the\n * whole of that knowledge, and three decisions shape it.\n *\n * ## 1. A curated descriptor, not a state dump\n *\n * The tempting design is to serialise the page — props, records, the loaded\n * list — into the prompt. That buys worse answers at higher cost: the model\n * gets rows when what it needs is *what this screen is for*, and every extra\n * token is billed per turn against a hard margin constraint.\n *\n * So the view block is assembled from a STATIC, in-repo table keyed by route\n * pattern, plus a fixed handful of scalars about the current org. Nothing is\n * read out of the customer's data to build it. That is also why the privacy\n * guard is cheap to state and cheap to prove: there is no path from a\n * Firestore document to this prompt except the two fields `safeOrgFacts`\n * names, and no path from client input except three sanitised strings.\n *\n * ## 2. One answer for both ends of the ICP range\n *\n * the requirement is a single feature that is \"easy for someone who\n * doesn't know code and even easier for someone who does\". Two modes would\n * be the obvious build and the wrong one — it makes the beginner choose a\n * label for themselves before they have a question, and it makes the\n * developer opt in to being taken seriously.\n *\n * Progressive disclosure instead: every view carries a `plain` layer (what\n * you can do here, in words a first-business owner reads) and a `technical`\n * layer (the route template, the ids in the path, the API or field names\n * behind it). The model leads with plain and appends the technical layer\n * under a fixed `Under the hood:` marker, which the panel renders collapsed.\n * Same answer, two depths, one message.\n *\n * ## 3. Actions are PROPOSALS, and a proposal cannot write\n *\n * \"Automate current view\" is level 2's edge and needs a hard boundary — the\n * AGL-1860 ladder puts acting in the besigner at level 3, after launch. The\n * boundary here is structural rather than behavioural, because a rule the\n * model is asked to follow is not a boundary:\n *\n * - An action descriptor has no field that could carry a write. There is no\n * method, no body, no endpoint — only a `route` template and the NAMES of\n * params. `assertInertActions()` proves that over the live table.\n * - The model never supplies a destination. It may name an `id` from the\n * closed set attached to the view it is already on, and supply values for\n * params that view declared. `resolveAssistProposal` builds the href from\n * the registry; an unknown id, a foreign id, or an undeclared param is\n * dropped rather than honoured.\n * - Confirming NAVIGATES. The destination form's own submit button, with\n * its own permission checks, stays the only thing that writes.\n *\n * `prefill` is deliberately `false` across the table today: no console page\n * reads `assist_*` search params yet, and a card that claims \"I filled that\n * in for you\" over a form that came up empty is worse than one that names the\n * values to type. The plumbing is here so switching a page on is a one-line\n * change, and `PREFILL_READY_ROUTES` is the allowlist that has to grow first.\n *\n * ## 4. The one action that edits, and where it opens (AGL-2906)\n *\n * Level 3 makes a single exception, on the besigner only: the `edit` action.\n * The server still writes nothing. The model proposes typed canvas operations\n * through a strict tool, the server validates them against the canvas the\n * request described, and the panel shows the change and applies it in the\n * author's own editor when they press Apply — an unsaved change, undone by\n * the editor's own undo. The rung is closed unless the org carries\n * `aiGenerative`, `release_ai_generative` is on for it and the caller holds\n * `ai.generate`. `offeredActions` hands an edit action only to a request that\n * cleared the rung, and `assertInertActions` fails any edit action offered\n * without it, or one that carries a destination the way a navigation does.\n */\n\n/**\n * One thing the assistant may PROPOSE from a given view, by navigating.\n *\n * Note what this interface cannot express. There is no `method`, no `body`,\n * no `endpoint`, no `submit` — a descriptor is a signpost, and the type is\n * the first line of the write boundary. Adding any of those fields would\n * break `assertInertActions()`, which is the point of writing it down.\n */\nexport interface AssistNavigateAction {\n /** `navigate`, or absent. */\n kind?: 'navigate'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm card. */\n label: string\n /** What the user will be looking at once they confirm. */\n outcome: string\n /**\n * Server-owned destination template (`/[orgSlug]/…`). Built here, never\n * supplied by the model — so the worst a bad completion can do is send the\n * user to a different page in their own console.\n */\n route: string\n /** Param names the model may supply values for. Anything else is dropped. */\n params: readonly string[]\n /**\n * Whether the destination actually reads `assist_*` search params. False\n * everywhere today — see the module header. When false the values are\n * shown on the card as \"use these\" rather than pushed into the URL.\n */\n prefill: boolean\n}\n\n/**\n * The besigner's edit action (AGL-2906). Its proposal is typed canvas\n * operations the panel applies on confirm — never a destination and never a\n * server write — so it carries none of a navigation's `route`, `params` or\n * `prefill`, and `assertInertActions` fails one that does.\n */\nexport interface AssistEditAction {\n kind: 'edit'\n /** Stable id; the ONLY thing the model gets to choose from. */\n id: string\n /** Beginner-facing label on the confirm button. */\n label: string\n /** What changes once the author confirms. */\n outcome: string\n /** The besigner documents the action opens on. */\n documents: readonly AssistEditDocumentKind[]\n}\n\nexport type AssistViewAction = AssistNavigateAction | AssistEditAction\n\n/** What a request cleared, which decides the actions a view offers it. */\nexport interface AssistActionRung {\n /**\n * The edit rung: `aiGenerative`, `release_ai_generative` and `ai.generate`,\n * on a versioned besigner route, with the canvas described.\n */\n edit: boolean\n}\n\n/** A console screen, described for the assistant rather than for a router. */\nexport interface AssistView {\n /** Registry key; also the analytics/debug handle. */\n key: string\n /** Route matcher. First match wins, so the table is ordered specific-first. */\n match: RegExp\n /** One line: what this screen is. */\n screen: string\n /** The beginner layer — what a person can do here, in plain words. */\n plain: readonly string[]\n /** The developer layer — route template, path ids, API surface. */\n technical: readonly string[]\n /** Proposals available from this view: navigations, and on the besigner the edit action. */\n actions: readonly AssistViewAction[]\n}\n\n/**\n * Destinations verified to read `assist_*` search params. EMPTY on purpose:\n * `assertInertActions()` fails any action with `prefill: true` whose route is\n * not listed, so turning prefill on requires wiring the page first rather\n * than remembering to.\n */\nexport const PREFILL_READY_ROUTES: readonly string[] = []\n\n/** Query-param prefix for proposed values. Namespaced so a page opting in\n * can never confuse an assistant suggestion with its own state. */\nexport const ASSIST_PREFILL_PREFIX = 'assist_'\n\nconst orgAction = (\n id: string,\n label: string,\n outcome: string,\n route: string,\n params: readonly string[] = [],\n): AssistNavigateAction => ({ id, label, outcome, route, params, prefill: false })\n\nconst isNavigateAction = (action: AssistViewAction): action is AssistNavigateAction =>\n action.kind !== 'edit'\n\n/**\n * The registry. Ordered specific-first — `describeView` takes the first\n * match, so a host sub-page must precede the host dashboard, and every\n * host route must precede the bare `/[orgSlug]` home.\n *\n * Content rule: every line here has to be TRUE of the shipped console. These\n * strings are asserted to the model as fact and it will repeat them, so a\n * guessed button label becomes a confidently wrong instruction. Where the\n * exact affordance is uncertain the line names the SCREEN and its purpose\n * and lets docs retrieval supply the steps — a vaguer sentence that is right\n * beats a specific one that is invented.\n */\nexport const ASSIST_VIEWS: readonly AssistView[] = [\n {\n key: 'besigner',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(screens|components|templates|layouts|emails)\\/.*\\/besigner$/,\n screen: 'The Besigner — the visual editor for a page, component, template, layout or email.',\n plain: [\n 'Edit the page visually: pick an element on the canvas and change its text, styling and layout.',\n 'Work is saved to a draft version; publishing is a separate, deliberate step.',\n 'Preview shows the draft as a visitor would see it, without publishing.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/besigner (and the component, template, layout and email variants).',\n 'Editing writes to the version named in the path — versioning is the undo, so a save never overwrites what is live.',\n 'The preview route is the same path with /preview in place of /besigner.',\n ],\n actions: [\n {\n kind: 'edit',\n id: ASSIST_EDIT_ACTION_ID,\n label: 'Apply as draft',\n outcome: 'the proposed changes on the open canvas, unsaved until the document is saved',\n documents: ASSIST_EDIT_DOCUMENT_KINDS,\n },\n ],\n },\n {\n key: 'host-screens',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/screens(\\/|$)/,\n screen: 'Pages — the list of pages on this site.',\n plain: [\n 'Every page a visitor can reach is listed here.',\n 'Open a page to see its versions, then open the Besigner to edit it.',\n 'Publishing a page version is what puts a change on the live site.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/screens; detail is .../screens/[screenId]/versions/[versionId]/view.',\n '[host] in the path is the site (host) document id, not the domain name.',\n ],\n actions: [\n orgAction(\n 'open.host.screens',\n 'Open Pages',\n 'the list of pages on this site',\n '/[orgSlug]/hosts/[host]/screens',\n ),\n ],\n },\n {\n key: 'host-theme',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/theme(\\/|$)/,\n screen: 'Theme — the site-wide colors, type and spacing every page inherits.',\n plain: [\n 'Change something here and it changes everywhere on the site at once.',\n 'This is the place to fix a color or a font you keep re-setting on individual elements.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/theme.',\n 'Theme values resolve at render time, so pages pick them up without being republished individually.',\n ],\n actions: [],\n },\n {\n // The key is the analytics/debug handle and stays as it is; the section\n // it names is what was renamed. Both addresses match because the console\n // redirects the older one, so a client can report either.\n key: 'host-workflows',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/(automation|workflows)(\\/|$)/,\n screen:\n 'Automation — workflows, actions and webhooks that run when something happens on the site.',\n plain: [\n 'A workflow is a trigger plus the actions that follow it, such as sending an email when a form is submitted.',\n 'Build the trigger first, then add the actions it should run.',\n 'Workflows, Actions and Webhooks are the three tabs of this section.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/automation, with ?tab=workflows, ?tab=actions or ?tab=webhooks.',\n 'Webhook actions let a workflow call an external endpoint; see the automation docs for the payload shape.',\n ],\n actions: [],\n },\n {\n key: 'host-products',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/products(\\/|$)/,\n screen: 'Products — the commerce catalog for this site.',\n plain: [\n 'Add and edit the things this site sells, including price and availability.',\n 'A product has to exist here before a page can put it in front of a buyer.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/products.',\n 'Catalog data is site-scoped: products belong to [host], not to the workspace.',\n ],\n actions: [],\n },\n {\n key: 'host-redirects',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/redirects(\\/|$)/,\n screen: 'Redirects — rules that send one path on this site to another.',\n plain: [\n 'Use a redirect when a page has moved, so an old link still lands somewhere useful.',\n 'Each rule is a path to match and a destination to send it to.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/redirects.',\n 'Rules are evaluated by the tenant runtime on the request path, before the page is resolved.',\n ],\n actions: [\n orgAction(\n 'open.host.redirects',\n 'Open Redirects',\n 'the redirect rules for this site',\n '/[orgSlug]/hosts/[host]/redirects',\n ['source', 'target'],\n ),\n ],\n },\n {\n key: 'host-analytics',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/analytics(\\/|$)/,\n screen: 'Analytics — traffic and visitor figures for this site.',\n plain: [\n 'See how many people visited, and which pages they landed on.',\n 'Figures cover the live site only — drafts and previews are not counted.',\n ],\n technical: ['Route: /[orgSlug]/hosts/[host]/analytics.'],\n actions: [],\n },\n {\n key: 'host-setup',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/setup(\\/|$)/,\n screen: 'Site setup — the site’s own settings, including its domain.',\n plain: [\n 'Connecting a custom domain starts here.',\n 'A domain change needs DNS records at whoever the domain is registered with; the console shows which ones.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/setup.',\n 'Owner/admin-only controls live on the sibling /admin page rather than here, so collaborators can use Setup safely.',\n ],\n actions: [\n orgAction(\n 'open.host.setup',\n 'Open site setup',\n 'this site’s settings, where a custom domain is connected',\n '/[orgSlug]/hosts/[host]/setup',\n ['domain'],\n ),\n ],\n },\n {\n key: 'host-data',\n match: /^\\/[^/]+\\/hosts\\/[^/]+\\/data(\\/|$)/,\n screen: 'Site data — the datasets this site reads and writes.',\n plain: [\n 'A dataset is a structured list — products, posts, submissions — that pages can display.',\n 'Define the fields first; pages bind to them afterwards.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]/data. The workspace-wide view is /[orgSlug]/data.',\n ],\n actions: [],\n },\n {\n key: 'host-dashboard',\n match: /^\\/[^/]+\\/hosts\\/[^/]+(\\/|$)/,\n screen: 'Site dashboard — the home page for one site in this workspace.',\n plain: [\n 'Everything about one site hangs off this dashboard: its pages, theme, data, commerce and settings.',\n 'A workspace can hold several sites; this is one of them.',\n ],\n technical: [\n 'Route: /[orgSlug]/hosts/[host]. The [host] segment is the site document id.',\n ],\n actions: [],\n },\n {\n key: 'org-hosts',\n match: /^\\/[^/]+\\/hosts(\\/|$)/,\n screen: 'Sites — every site in this workspace.',\n plain: [\n 'Each row is a site with its own pages, theme and domain.',\n 'Agencies typically run one site per client from here.',\n ],\n technical: ['Route: /[orgSlug]/hosts.'],\n actions: [\n orgAction(\n 'open.org.hosts',\n 'Open Sites',\n 'every site in this workspace',\n '/[orgSlug]/hosts',\n ),\n ],\n },\n {\n key: 'org-billing',\n match: /^\\/[^/]+\\/billing(\\/|$)/,\n screen: 'Billing & plans — the workspace subscription, plan and invoices.',\n plain: [\n 'The current plan and what it includes, and where to change it.',\n 'Plan limits that stop you elsewhere in the console are set here.',\n ],\n technical: [\n 'Route: /[orgSlug]/billing.',\n 'Entitlements are resolved from the org plan; a limit reached elsewhere reports the entitlement it needs.',\n ],\n actions: [\n orgAction(\n 'open.billing',\n 'Open Billing & plans',\n 'the plan for this workspace and how to change it',\n '/[orgSlug]/billing',\n ),\n ],\n },\n {\n key: 'org-team',\n match: /^\\/[^/]+\\/team(\\/|$)/,\n screen: 'Team — the people in this workspace and what each may do.',\n plain: [\n 'Invite a teammate by email, and choose the role that decides what they can reach.',\n 'Removing someone here removes their access to every site in the workspace.',\n ],\n technical: [\n 'Route: /[orgSlug]/team; one member is /[orgSlug]/team/[uid].',\n 'Seat counts follow the plan, so an invite can be refused by the billing entitlement rather than by permissions.',\n ],\n actions: [\n orgAction(\n 'open.team',\n 'Open Team',\n 'the members of this workspace, where invites are sent',\n '/[orgSlug]/team',\n ['email', 'role'],\n ),\n ],\n },\n {\n key: 'org-data',\n match: /^\\/[^/]+\\/data(\\/|$)/,\n screen: 'Data — datasets shared across the whole workspace.',\n plain: [\n 'Structured lists defined once here can be used by any site in the workspace.',\n 'If a list only belongs to one site, define it on that site’s own Data page instead.',\n 'Define the fields before building the page that displays them — pages bind to fields that already exist.',\n ],\n technical: [\n 'Route: /[orgSlug]/data. The per-site view is /[orgSlug]/hosts/[host]/data.',\n 'Workspace datasets are visible to every site in the workspace, so a field change here reaches all of them.',\n ],\n actions: [\n orgAction(\n 'open.org.data',\n 'Open Data',\n 'the datasets shared across this workspace',\n '/[orgSlug]/data',\n ),\n ],\n },\n {\n key: 'org-media',\n match: /^\\/[^/]+\\/media(\\/|$)/,\n screen: 'Media — images and files for this workspace.',\n plain: [\n 'Upload once here and use the file on any site in the workspace.',\n 'If the file belongs to one site only, upload it on that site’s own Media page instead.',\n 'Files are referenced by the pages that use them, so replacing a file changes it everywhere it appears.',\n ],\n technical: [\n 'Route: /[orgSlug]/media. The per-site library is /[orgSlug]/hosts/[host]/media.',\n 'Workspace media is shared across every site in the workspace; site media is scoped to that site.',\n ],\n actions: [\n orgAction(\n 'open.org.media',\n 'Open Media',\n 'the images and files shared across this workspace',\n '/[orgSlug]/media',\n ),\n ],\n },\n {\n key: 'org-plugins',\n match: /^\\/[^/]+\\/plugins(\\/|$)/,\n screen: 'Plugins — what is installed in this workspace.',\n plain: [\n 'A plugin adds a feature to the console, to your sites, or to both.',\n 'Open one to see its settings and the permissions it was granted.',\n ],\n technical: [\n 'Route: /[orgSlug]/plugins; one install is /[orgSlug]/plugins/[pluginRef].',\n 'Plugins run inside a sandbox; the permissions listed on the install page are the boundary.',\n ],\n actions: [],\n },\n {\n key: 'org-marketplace',\n match: /^\\/[^/]+\\/marketplace(\\/|$)/,\n screen: 'Marketplace — plugins and items available to install.',\n plain: ['Browse what can be added, and install into this workspace.'],\n technical: [\n 'Route: /[orgSlug]/marketplace; a listing is /[orgSlug]/marketplace/[listingId].',\n 'Publishing your own plugin is /[orgSlug]/marketplace/publish/plugin.',\n ],\n actions: [],\n },\n {\n key: 'org-support',\n match: /^\\/[^/]+\\/support(\\/|$)/,\n screen: 'Support — the channels for getting help.',\n plain: [\n 'Raise a ticket, or ask in the forum.',\n 'Which channels are available depends on the workspace plan.',\n ],\n technical: [\n 'Route: /[orgSlug]/support, which forwards to the channel the plan makes primary.',\n 'Channels are /[orgSlug]/support/tickets and /[orgSlug]/support/forum.',\n ],\n actions: [\n orgAction(\n 'open.support',\n 'Open Support',\n 'the support channels for this workspace',\n '/[orgSlug]/support',\n ),\n ],\n },\n {\n key: 'org-settings',\n match: /^\\/[^/]+\\/settings(\\/|$)/,\n screen: 'Workspace settings — the workspace’s own name, slug and options.',\n plain: ['Settings that apply to the whole workspace rather than to one site.'],\n technical: [\n 'Route: /[orgSlug]/settings.',\n 'The workspace slug is the [orgSlug] segment in every console URL, so changing it changes those links.',\n ],\n actions: [],\n },\n {\n key: 'org-home',\n match: /^\\/[^/]+\\/?$/,\n screen: 'Workspace home — the landing page for one workspace.',\n plain: [\n 'The starting point for a workspace: its sites, and the workspace-wide areas beside them.',\n ],\n technical: ['Route: /[orgSlug].'],\n actions: [],\n },\n]\n\n/** The view the user is standing on, or null where the route is unknown. */\nexport function describeView(route: string): AssistView | null {\n const path = sanitiseRoute(route)\n if (!path) return null\n return ASSIST_VIEWS.find((view) => view.match.test(path)) ?? null\n}\n\n/**\n * The actions a view offers a request: every navigation, and the edit action\n * only to a request that cleared the edit rung.\n */\nexport function offeredActions(\n view: AssistView | null,\n rung: AssistActionRung,\n): readonly AssistViewAction[] {\n if (!view) return []\n return view.actions.filter((action) => action.kind !== 'edit' || rung.edit)\n}\n\n/**\n * Client-supplied strings that end up inside a SYSTEM block have to be\n * treated as hostile, not merely untidy. `route` is whatever the panel says\n * the pathname is, and an attacker who can shape it can otherwise write\n * newlines, backticks and prose into the model's instructions — including a\n * forged action fence, which is the one construct in this feature the model\n * is told to treat as meaningful.\n *\n * So the allowed alphabet is exactly what a console path is made of. Anything\n * else is dropped rather than escaped: there is no legitimate route that\n * needs a backtick, and a sanitiser that tries to preserve intent is a\n * sanitiser with a bypass in it.\n */\nexport function sanitiseRoute(route: string): string {\n const cleaned = String(route ?? '')\n .replace(/[^A-Za-z0-9/_\\-.[\\]]/g, '')\n .slice(0, 200)\n return cleaned.startsWith('/') ? cleaned : ''\n}\n\n/**\n * Same reasoning as `sanitiseRoute`, for a Firestore-shaped document id.\n *\n * Rejects rather than truncates, which is the difference between refusing an\n * id and quietly pointing at a different one: `slice(0, 64)` on an over-long\n * value yields a well-formed id for some OTHER document, and this function's\n * output is substituted straight into a destination path.\n */\nexport function sanitiseId(value: string): string {\n const raw = String(value ?? '')\n return raw.length <= 64 && /^[A-Za-z0-9_-]+$/.test(raw) ? raw : ''\n}\n\n/**\n * The ONLY fields of the org document that may enter a prompt.\n *\n * An allowlist rather than a denylist, and stated as a literal rather than\n * derived, because the failure mode is silent: the org doc grows a field one\n * day — a Stripe customer id, an owner's email, a support note — and a\n * spread would carry it into a third party's API without anything failing.\n * Both fields named here are already on screen for any member who can open\n * the panel, so nothing is disclosed that the asker could not read anyway.\n *\n * The plan is the one the workspace resolves to, as the console's badges name\n * it (AGL-3034): a canceled subscription's stored plan grants nothing, and a\n * staff comp grants a plan the stored field may not name.\n */\nexport function safeOrgFacts(org: Record<string, unknown>): {\n name: string\n plan: string\n} {\n return {\n name: String(org?.['name'] ?? '').slice(0, 120),\n plan: resolveEffectivePlan(org as never).slice(0, 40),\n }\n}\n\n/**\n * The per-VIEW system block — the screen description, and nothing about who\n * is looking at it.\n *\n * The split from `viewFactsBlock` is the whole caching design, and it was\n * not the first shape this took. Folding the workspace name, plan, slug and\n * host id in here reads more natural and quietly destroys the cache: a\n * prefix carrying the tenant's name is unique to that tenant, so on a\n * multi-tenant console every org warms its own copy and the entry is usually\n * cold when it matters. Derived purely from the route, the same block serves\n * every workspace asking anything on that screen — which on a shared console\n * is the difference between a cache that pays and one that mostly writes.\n *\n * So this block is a pure function of the registry and the rung — two\n * variants a route at most, neither carrying a tenant byte — and everything\n * that varies per request lives after the breakpoint.\n */\nexport function viewScreenBlock(\n view: AssistView | null,\n rung: AssistActionRung = { edit: false },\n): string {\n const lines: string[] = []\n if (view) {\n lines.push(`This console page: ${view.screen}`)\n if (view.plain.length) {\n lines.push('What the user can do here:')\n for (const line of view.plain) lines.push(`- ${line}`)\n }\n if (view.technical.length) {\n lines.push('Technical detail for the \"Under the hood\" line:')\n for (const line of view.technical) lines.push(`- ${line}`)\n }\n const offered = offeredActions(view, rung)\n const navigations = offered.filter(isNavigateAction)\n if (navigations.length) {\n lines.push(\n 'Actions you may PROPOSE from this console page (ids are exact; propose at most one, and only when the user asked to get something done):',\n )\n for (const action of navigations) {\n const params = action.params.length\n ? ` params: ${action.params.join(', ')}`\n : ' params: none'\n lines.push(`- id \"${action.id}\" — ${action.label}: opens ${action.outcome}.${params}`)\n }\n } else if (offered.length) {\n lines.push('This console page offers nothing to open. Do not emit an action block here.')\n } else {\n lines.push(\n 'This console page offers no proposable actions. Do not emit an action block here.',\n )\n }\n for (const action of offered) {\n if (action.kind !== 'edit') continue\n lines.push(\n `Edits you may PROPOSE on this console page (id \"${action.id}\"): through the ${ASSIST_EDIT_TOOL_NAME} tool described below, never an action block.`,\n )\n }\n } else {\n lines.push(\n 'This exact console page is not in the assistant’s index of console pages. Answer from the documentation and describe navigation in words rather than asserting what this page contains.',\n 'Do not emit an action block.',\n )\n }\n return lines.join('\\n')\n}\n\n/**\n * The per-REQUEST facts. Everything here varies by workspace or by page, so\n * it sits after the last cache breakpoint where it invalidates nothing.\n *\n * Only `safeOrgFacts`' two allowlisted fields and the three sanitised client\n * strings ever reach this — see the module header.\n */\nexport function viewFactsBlock(facts: {\n route: string\n hostId: string\n orgSlug: string\n name: string\n plan: string\n}): string {\n const lines: string[] = ['Where the user is right now:']\n lines.push(`- Console page path: ${facts.route}`)\n if (facts.orgSlug) lines.push(`- Workspace URL slug: ${facts.orgSlug}`)\n if (facts.hostId) lines.push(`- Selected site (host) id: ${facts.hostId}`)\n if (facts.name) lines.push(`- Workspace name: ${facts.name}`)\n if (facts.plan) lines.push(`- Workspace plan: ${facts.plan}`)\n return lines.join('\\n')\n}\n\n/* ------------------------------------------------------------------ *\n * The proposal channel\n * ------------------------------------------------------------------ */\n\n/** Fence the model wraps a proposal in. Kept out of the visible answer. */\nexport const ASSIST_ACTION_FENCE = '```aglyn:action'\n\n/**\n * How much of `raw` is safe to stream to the user.\n *\n * The proposal fence must never reach the panel as text — a user watching\n * raw JSON appear mid-answer reads it as the assistant breaking. Trimming\n * only at the end is not enough either, because by then the tokens have\n * already been emitted as deltas.\n *\n * So the visible prefix is recomputed on every delta, and the tail is held\n * back only while it could still turn out to be the start of a fence. That\n * matters for latency: holding a fixed window would delay every answer by\n * the fence's length, whereas this holds nothing back unless the text\n * genuinely ends in a backtick run.\n */\nexport function visibleAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n if (cut >= 0) return raw.slice(0, cut)\n for (let n = Math.min(ASSIST_ACTION_FENCE.length - 1, raw.length); n > 0; n--) {\n if (ASSIST_ACTION_FENCE.startsWith(raw.slice(raw.length - n))) {\n return raw.slice(0, raw.length - n)\n }\n }\n return raw\n}\n\n/**\n * The visible answer once the stream has ENDED — no holdback.\n *\n * `visibleAssistText` withholds a tail that might still become a fence,\n * which is right mid-stream and wrong at the end: an answer that genuinely\n * closes on a code fence would otherwise lose its last three characters\n * permanently, both on screen and in the stored exchange. Once there are no\n * more deltas the ambiguity is resolved, so the only cut left is a real one.\n */\nexport function finalAssistText(raw: string): string {\n const cut = raw.indexOf(ASSIST_ACTION_FENCE)\n return cut >= 0 ? raw.slice(0, cut) : raw\n}\n\n/** The raw JSON body between the fences, if the model emitted one. */\nexport function extractAssistAction(raw: string): string | null {\n const start = raw.indexOf(ASSIST_ACTION_FENCE)\n if (start < 0) return null\n const bodyStart = start + ASSIST_ACTION_FENCE.length\n const end = raw.indexOf('```', bodyStart)\n const body = (end < 0 ? raw.slice(bodyStart) : raw.slice(bodyStart, end)).trim()\n return body || null\n}\n\n/** A proposal that survived validation — inert, and safe to show. */\nexport interface AssistProposal {\n id: string\n label: string\n outcome: string\n /** Fully-resolved console path. Server-built; the model never sees it. */\n href: string\n /** Values the model suggested, in the order the action declared them. */\n values: ReadonlyArray<{ name: string; value: string }>\n /** Whether `href` carries the values, or the card must ask the user to type them. */\n prefill: boolean\n}\n\nconst MAX_PARAM_VALUE_CHARS = 200\n\n/**\n * Turn a model-emitted action block into a proposal, or nothing.\n *\n * Everything here is a narrowing. The id must be one the CURRENT view\n * offers as a navigation — not merely a real id somewhere in the registry,\n * because \"propose the billing page from the besigner\" is exactly the kind\n * of plausible wandering that makes an assistant feel unsafe, and not the\n * edit action either, which is never a destination. Param names must be ones\n * that action declared. The href is composed from the registry template and\n * the caller's own server-resolved slug and host, so a completion cannot\n * steer the user anywhere the registry does not already point.\n *\n * Anything unexpected returns null. A dropped proposal costs the user a\n * button; an honoured bad one costs them trust.\n */\nexport function resolveAssistProposal(\n rawBlock: string | null,\n view: AssistView | null,\n scope: { orgSlug: string; hostId: string },\n): AssistProposal | null {\n if (!rawBlock || !view) return null\n const navigations = view.actions.filter(isNavigateAction)\n if (!navigations.length) return null\n\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(rawBlock) as Record<string, unknown>\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object') return null\n\n const id = String(parsed['id'] ?? '').trim()\n const action = navigations.find((candidate) => candidate.id === id)\n if (!action) return null\n\n const supplied = (parsed['params'] ?? {}) as Record<string, unknown>\n const values: Array<{ name: string; value: string }> = []\n if (supplied && typeof supplied === 'object' && !Array.isArray(supplied)) {\n for (const name of action.params) {\n const value = String(supplied[name] ?? '')\n .replace(/[\\r\\n]+/g, ' ')\n .trim()\n .slice(0, MAX_PARAM_VALUE_CHARS)\n if (value) values.push({ name, value })\n }\n }\n\n const href = buildActionHref(action, scope, values)\n if (!href) return null\n\n return {\n id: action.id,\n label: action.label,\n outcome: action.outcome,\n href,\n values,\n prefill: action.prefill,\n }\n}\n\n/**\n * Compose the destination from the registry template and the server's own\n * scope values. Returns '' when the template needs a segment this page does\n * not have — a half-substituted path like `/acme/hosts//setup` is a 404 with\n * a confirm button on it, which is worse than no button.\n */\nfunction buildActionHref(\n action: AssistNavigateAction,\n scope: { orgSlug: string; hostId: string },\n values: ReadonlyArray<{ name: string; value: string }>,\n): string {\n const orgSlug = sanitiseId(scope.orgSlug)\n const hostId = sanitiseId(scope.hostId)\n let path = action.route\n if (path.includes('[orgSlug]')) {\n if (!orgSlug) return ''\n path = path.replace('[orgSlug]', orgSlug)\n }\n if (path.includes('[host]')) {\n if (!hostId) return ''\n path = path.replace('[host]', hostId)\n }\n // Nothing above can produce these, which is exactly why they are worth\n // asserting: this is the last line before a path reaches a router.\n if (!path.startsWith('/') || path.includes('..') || path.includes('//')) return ''\n if (/\\[|\\]/.test(path)) return ''\n\n if (!action.prefill || !values.length) return path\n const query = values\n .map(\n ({ name, value }) =>\n `${ASSIST_PREFILL_PREFIX}${encodeURIComponent(name)}=${encodeURIComponent(value)}`,\n )\n .join('&')\n return `${path}?${query}`\n}\n\n/**\n * The write boundary, asserted over the live table rather than described in\n * a comment. Called by the spec; exported so the check travels with the data\n * it checks rather than living in a test file someone can delete.\n *\n * Every action is inert UNLESS it is an edit action and the request cleared\n * the edit rung: a navigation carries no write-capable field and no ops, an\n * edit action carries no destination, and an edit action offered to a\n * request below the rung is a violation. `offer` is the function the doors\n * decide with; a spec passes a broken one to show this check can fail.\n *\n * Returns the list of violations so a failure names the offending action.\n */\nexport function assertInertActions(\n views: readonly AssistView[] = ASSIST_VIEWS,\n rung: AssistActionRung = { edit: false },\n offer: typeof offeredActions = offeredActions,\n): string[] {\n const problems: string[] = []\n const seen = new Set<string>()\n const forbidden = ['method', 'body', 'endpoint', 'submit', 'url', 'api', 'mutation']\n for (const view of views) {\n const offered = offer(view, rung)\n for (const action of view.actions) {\n const where = `${view.key}/${action.id}`\n if (seen.has(action.id)) problems.push(`${where}: duplicate action id`)\n seen.add(action.id)\n for (const key of Object.keys(action)) {\n if (forbidden.includes(key.toLowerCase())) {\n problems.push(`${where}: action carries a write-capable field \"${key}\"`)\n }\n }\n if (action.kind === 'edit') {\n if (!rung.edit && offered.includes(action)) {\n problems.push(\n `${where}: an edit action is offered to a request that did not clear the edit rung`,\n )\n }\n for (const key of ['route', 'params', 'prefill']) {\n if (key in action) {\n problems.push(\n `${where}: an edit action carries \"${key}\" — it applies ops on the open canvas and never navigates`,\n )\n }\n }\n const documents = action.documents ?? []\n if (\n !documents.length ||\n documents.some((kind) => !ASSIST_EDIT_DOCUMENT_KINDS.includes(kind))\n ) {\n problems.push(`${where}: an edit action names a document the edit rung does not open on`)\n }\n continue\n }\n if ('ops' in action) {\n problems.push(`${where}: only an edit action carries ops`)\n }\n if (!action.route.startsWith('/')) {\n problems.push(`${where}: route is not a root-relative console path`)\n }\n if (action.route.includes('/api/')) {\n problems.push(`${where}: route points at an API endpoint`)\n }\n if (action.prefill && !PREFILL_READY_ROUTES.includes(action.route)) {\n problems.push(\n `${where}: prefill:true but ${action.route} is not in PREFILL_READY_ROUTES — wire the page to read ${ASSIST_PREFILL_PREFIX}* params first`,\n )\n }\n }\n }\n return problems\n}\n"],"names":["resolveEffectivePlan","ASSIST_EDIT_ACTION_ID","ASSIST_EDIT_DOCUMENT_KINDS","ASSIST_EDIT_TOOL_NAME","PREFILL_READY_ROUTES","ASSIST_PREFILL_PREFIX","orgAction","id","label","outcome","route","params","prefill","isNavigateAction","action","kind","ASSIST_VIEWS","key","match","screen","plain","technical","actions","documents","describeView","path","sanitiseRoute","find","view","test","offeredActions","rung","filter","edit","cleaned","String","replace","slice","startsWith","sanitiseId","value","raw","length","safeOrgFacts","org","name","plan","viewScreenBlock","lines","push","line","offered","navigations","join","viewFactsBlock","facts","orgSlug","hostId","ASSIST_ACTION_FENCE","visibleAssistText","cut","indexOf","n","Math","min","finalAssistText","extractAssistAction","start","bodyStart","end","body","trim","MAX_PARAM_VALUE_CHARS","resolveAssistProposal","rawBlock","scope","parsed","JSON","parse","candidate","supplied","values","Array","isArray","href","buildActionHref","includes","query","map","encodeURIComponent","assertInertActions","views","offer","problems","seen","Set","forbidden","where","has","add","Object","keys","toLowerCase","some"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,QAAQ,2CAA0C;AAC/E,SACEC,qBAAqB,EACrBC,0BAA0B,EAC1BC,qBAAqB,QAEhB,0BAAsB;AA0J7B;;;;;CAKC,GACD,OAAO,MAAMC,uBAA0C,EAAE,CAAA;AAEzD;iEACiE,GACjE,OAAO,MAAMC,wBAAwB,UAAS;AAE9C,MAAMC,YAAY,CAChBC,IACAC,OACAC,SACAC,OACAC,SAA4B,EAAE,GACJ,CAAA;QAAEJ;QAAIC;QAAOC;QAASC;QAAOC;QAAQC,SAAS;IAAM,CAAA;AAEhF,MAAMC,mBAAmB,CAACC,SACxBA,OAAOC,IAAI,KAAK;AAElB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,eAAsC;IACjD;QACEC,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;YACA;SACD;QACDC,SAAS;YACP;gBACEP,MAAM;gBACNR,IAAIN;gBACJO,OAAO;gBACPC,SAAS;gBACTc,WAAWrB;YACb;SACD;IACH;IACA;QACEe,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,qBACA,cACA,kCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACE,wEAAwE;QACxE,yEAAyE;QACzE,0DAA0D;QAC1DL,KAAK;QACLC,OAAO;QACPC,QACE;QACFC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,uBACA,kBACA,oCACA,qCACA;gBAAC;gBAAU;aAAS;SAEvB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA4C;QACxDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,mBACA,mBACA,4DACA,iCACA;gBAAC;aAAS;SAEb;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;SACD;QACDC,SAAS,EAAE;IACb;IACA;QACEL,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YAAC;SAA2B;QACvCC,SAAS;YACPhB,UACE,kBACA,cACA,gCACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,gBACA,wBACA,oDACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,aACA,aACA,yDACA,mBACA;gBAAC;gBAAS;aAAO;SAEpB;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;QACRC,OAAO;YACL;YACA;YACA;SACD;QACDC,WAAW;YACT;YACA;SACD;QACDC,SAAS;YACPhB,UACE,iBACA,aACA,6CACA;SAEH;IACH;IACA;QACEW,KAAK;QACLC,OAAO;QACPC,QAAQ;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"}
@@ -30,3 +30,4 @@ export * from './runtime/ai-runtime';
30
30
  * `AI_JOBS_BEAT_PATH` gives.
31
31
  */
32
32
  export declare function registerAiConsoleApi(): void;
33
+ export type { aiPlanEntitlements } from './plan-entitlements';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n}\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiWorkflowJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","resumeAiJob","aiGenerateComponent","applyAiSeoAudit","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/server.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 { registerPluginApiRoute } from '@aglyn/aglyn/server'\nimport { registerAiDeclarations } from './declarations'\nimport { AI_JOBS_BEAT_PATH } from './jobs/ai-jobs-beat'\nimport { registerAiJobPlan } from './jobs/ai-job-plan-step'\nimport { registerAiJobsPause } from './jobs/ai-jobs-pause'\nimport { registerAiComponentJob } from './jobs/ai-job-component-step'\nimport { registerAiCrmJob } from './jobs/ai-job-crm-step'\nimport { registerAiLayoutJob } from './jobs/ai-job-layout-step'\nimport { registerAiTemplateJob } from './jobs/ai-job-template-step'\nimport { registerAiExperimentJob } from './jobs/ai-job-experiment-step'\nimport { registerAiFormJob } from './jobs/ai-job-form-step'\nimport { registerAiPageJob } from './jobs/ai-job-page-step'\nimport { registerAiProductsJob } from './jobs/ai-job-products-step'\nimport { registerAiEmailJob } from './jobs/ai-job-email-step'\nimport { registerAiCampaignJob } from './jobs/ai-job-campaign-step'\nimport { registerAiSiteJob } from './jobs/ai-job-site-step'\nimport { registerAiWorkflowJob } from './jobs/ai-job-workflow-step'\nimport { registerAiInsightJob } from './jobs/ai-job-insight-step'\nimport { registerAiFigureReaders } from './insights/ai-figure-readers'\nimport { registerAiTextGenerator } from './server/plugin-text-generation'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { ensureFirstPartyAiProviders } from './providers/registry'\nimport { aiAssistHandler } from './server/ai-assist'\nimport { GET as aiCrmAnswer } from './server/ai-crm-answer'\nimport { POST as runAiJobsBeat } from './server/ai-jobs-beat-route'\nimport { POST as cancelAiJob } from './server/ai-jobs-cancel'\nimport { GET as aiJobEvents } from './server/ai-jobs-events-route'\nimport { POST as createAiSiteBatch } from './server/ai-jobs-batch'\nimport { GET as listAiJobs, POST as createAiJob } from './server/ai-jobs-route'\nimport { POST as resumeAiJob } from './server/ai-jobs-resume'\nimport { POST as aiGenerateComponent } from './server/ai-generate-component'\nimport { POST as applyAiSeoAudit } from './server/ai-seo-apply'\nimport { POST as assistChat } from './server/assist-chat'\nimport { POST as assistEditApplied } from './server/assist-edit-applied'\nimport { POST as assistFeedback } from './server/assist-feedback'\nimport { PATCH as aiHostPermissions } from './server/ai-host-permissions'\nimport { GET as aiAdminOrg } from './server/ai-admin-org'\nimport { GET as aiAdminOrgsSpend } from './server/ai-admin-orgs-spend'\nimport { GET as aiAdminSignals } from './server/ai-admin-signals'\nimport { GET as aiAdminUser } from './server/ai-admin-user'\nimport { POST as aiAdminOverage } from './server/ai-admin-overage'\nimport { GET as aiUsage } from './server/ai-usage'\nimport { GET as aiAllotments } from './server/ai-allotments'\nimport { GET as aiInsightAnswer } from './server/ai-insight-answer'\nimport { POST as runAiInsightsDigest } from './server/ai-insight-digest-route'\nimport { AI_INSIGHTS_DIGEST_PATH } from './insights/ai-insight-digest'\nimport { GET as aiModels } from './server/ai-models'\nimport { GET as billingCredits } from './server/billing-credits'\nimport { POST as billingOverage } from './server/billing-overage'\n\nexport * from './providers/contract'\nexport * from './providers/catalog'\nexport * from './providers/registry'\nexport * from './providers/routing'\nexport * from './runtime/ai-runtime'\n\n/**\n * The two first-party adapters, registered once per process. A marketplace\n * plugin registers its own against `AI_PROVIDER_CONTRACT` from its own\n * entry and never touches this.\n */\nconst registerFirstPartyProviders = ensureFirstPartyAiProviders\n\n/**\n * Every job kind this plugin runs beyond the three the machine registers\n * itself (AGL-3025). Each is a CALL, never an import made for what the\n * module does as it loads: this package declares only this file\n * effect-ful, so a bundler honoring `sideEffects` deletes an import whose\n * exports go unused — and a step registered that way ran in every spec and\n * in neither app. `registrations-survive-a-bundler.spec.ts` holds this.\n *\n * The console surface is the only caller (AGL-3026): its doors run a job's\n * first step inline, and its beat runs every step after it. The tenant app\n * loads no server surface of this plugin at all, so nothing that serves a\n * published site can reach a provider.\n */\nfunction registerAiJobKinds(): void {\n // The workspace AI pause the machine asks before it claims any step, so a\n // paused workspace's queued jobs spend nothing (AGL-3037).\n registerAiJobsPause()\n // The plan step every planned kind runs first (AGL-2935).\n registerAiJobPlan()\n // Components, layouts and templates (AGL-2908, AGL-2909).\n registerAiComponentJob()\n registerAiLayoutJob()\n registerAiTemplateJob()\n // Forms (AGL-2913).\n registerAiExperimentJob()\n registerAiFormJob()\n // Pages, with the least time one pass needs (AGL-2907).\n registerAiPageJob()\n // Email designs and campaigns, whose drafts the email and marketing\n // plugins write on the resource-draft seam (AGL-2912).\n registerAiEmailJob()\n registerAiCampaignJob()\n // Automations drafted from a description, and explained, whose drafts the\n // workflows plugin writes on the resource-draft seam (AGL-2919).\n registerAiWorkflowJob()\n // Product copy, and a store's products, categories and discounts from a\n // brief, as proposals the commerce plugin's surfaces apply (AGL-2916).\n registerAiProductsJob()\n // The site scaffold, which builds a whole site through the steps above\n // (AGL-2911).\n registerAiSiteJob()\n // Insights (AGL-2915): a question about a site's figures, answered from\n // tables the figure readers return, and the weekly digest. The readers for\n // the platform's own records — page views, forms, datasets — are this\n // plugin's; every other plugin registers its own from its console surface.\n registerAiFigureReaders(() => firebaseAdmin.app().firestore())\n registerAiInsightJob()\n // CRM by AI (AGL-2917): a record's summary and next step, an email draft\n // and an import's column matches, read through the CRM's facts readers.\n registerAiCrmJob()\n}\n\n/**\n * The console-side API (AGL-2939): every AI door, under the `ai` and\n * `assist` prefixes the plugin owns. The URLs the panel and the besigner\n * call are unchanged — `/api/assist/chat`, `/api/ai/assist`,\n * `/api/ai/jobs` — because the dispatcher serves them from the registry\n * exactly where the named routes used to; the two billing doors and the\n * per-member usage read (`ai/usage`) live under the plugin's own prefix.\n * The jobs beat is the one path outside them, for the reason\n * `AI_JOBS_BEAT_PATH` gives.\n */\nexport function registerAiConsoleApi(): void {\n registerAiDeclarations()\n registerFirstPartyProviders()\n registerAiJobKinds()\n // This plugin as the workspace's text generator on the core's seam\n // (AGL-3324): another plugin drafts a paragraph through it under the\n // generation doors' own rules, metered as a text job is.\n registerAiTextGenerator()\n registerPluginApiRoute('assist/chat', { web: assistChat })\n registerPluginApiRoute('assist/feedback', { web: assistFeedback })\n // The applied-edit record (AGL-2906): counts of a proposal the author\n // applied in their editor, written to the site's activity log.\n registerPluginApiRoute('assist/edit-applied', { web: assistEditApplied })\n // A collaborator's AI toggles on one site (AGL-2927), set from the site's\n // collaborators card through this plugin's column there.\n registerPluginApiRoute('ai/host-permissions', { web: aiHostPermissions })\n registerPluginApiRoute('ai/assist', aiAssistHandler)\n registerPluginApiRoute('ai/jobs', {\n web: (request) => (request.method === 'GET' ? listAiJobs(request) : createAiJob(request)),\n })\n // The agency batch (AGL-2911): one brief, one `site` job per named site,\n // under one batch id. Before the `:jobId` routes, which it is not one of.\n registerPluginApiRoute('ai/jobs/batch', { web: createAiSiteBatch })\n registerPluginApiRoute('ai/jobs/:jobId/cancel', {\n web: (request, context) =>\n cancelAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/resume', {\n web: (request, context) =>\n resumeAiJob(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // An insight job's answer (AGL-2915), which is kept off the job document\n // and served only to the member who asked, or for a digest to the site's\n // members.\n registerPluginApiRoute('ai/insights/:jobId', {\n web: (request, context) =>\n aiInsightAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // A CRM job's answer (AGL-2917), which is kept off the job document and\n // served only to a member the CRM still lets read the record.\n registerPluginApiRoute('ai/crm/:jobId', {\n web: (request, context) =>\n aiCrmAnswer(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n registerPluginApiRoute('ai/jobs/:jobId/events', {\n web: (request, context) =>\n aiJobEvents(request, { params: Promise.resolve({ jobId: String(context.params['jobId']) }) }),\n })\n // The jobs beat (AGL-3026): every step the doors leave queued, run every\n // minute by the console's scheduler on the cron secret, on the one surface\n // that holds the provider's key.\n registerPluginApiRoute(AI_JOBS_BEAT_PATH, { web: runAiJobsBeat })\n // The weekly insights (AGL-2915): Monday's digests made for the people who\n // asked for them, and delivered once the beat has written them. Beside the\n // beat under `/api/admin/`, where the console's scheduled sweeps live.\n registerPluginApiRoute(AI_INSIGHTS_DIGEST_PATH, { web: runAiInsightsDigest })\n // A site SEO audit's \"Apply all\" (AGL-2910): content fixes as new\n // unpublished versions, listing values staged for their SEO cards.\n registerPluginApiRoute('ai/seo/apply', { web: applyAiSeoAudit })\n // Save the selection as a reusable component, with AI (AGL-2908): the\n // second entry point of the component job, from the besigner's Attributes\n // panel. It proposes, and the person's Apply writes.\n registerPluginApiRoute('ai/generate/component', { web: aiGenerateComponent })\n registerPluginApiRoute('ai/billing/credits', { web: billingCredits })\n registerPluginApiRoute('ai/billing/overage', { web: billingOverage })\n registerPluginApiRoute('ai/usage', { web: aiUsage })\n // The allotments a manager sets, and the options the model switch lists\n // (AGL-2942). One handler answers the allotments' GET and POST.\n registerPluginApiRoute('ai/allotments', { web: aiAllotments })\n registerPluginApiRoute('ai/models', { web: aiModels })\n // The staff doors (AGL-2928, AGL-2930, AGL-2252): one org's AI in full,\n // one account's usage across its workspaces, and the fleet's Assist\n // signals behind the Assist signal staff page.\n registerPluginApiRoute('ai/admin/org', { web: aiAdminOrg })\n registerPluginApiRoute('ai/admin/orgs-spend', { web: aiAdminOrgsSpend })\n registerPluginApiRoute('ai/admin/user', { web: aiAdminUser })\n registerPluginApiRoute('ai/admin/signals', { web: aiAdminSignals })\n // One workspace's overage standing and the four acts staff have over it\n // (AGL-3011): the ceiling override, lifting a pause — the only way a\n // dispute pause comes off — and resetting the step. Every act audited.\n registerPluginApiRoute('ai/admin/overage', { web: aiAdminOverage })\n}\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { aiPlanEntitlements } from './plan-entitlements'\n"],"names":["registerPluginApiRoute","registerAiDeclarations","AI_JOBS_BEAT_PATH","registerAiJobPlan","registerAiJobsPause","registerAiComponentJob","registerAiCrmJob","registerAiLayoutJob","registerAiTemplateJob","registerAiExperimentJob","registerAiFormJob","registerAiPageJob","registerAiProductsJob","registerAiEmailJob","registerAiCampaignJob","registerAiSiteJob","registerAiWorkflowJob","registerAiInsightJob","registerAiFigureReaders","registerAiTextGenerator","firebaseAdmin","ensureFirstPartyAiProviders","aiAssistHandler","GET","aiCrmAnswer","POST","runAiJobsBeat","cancelAiJob","aiJobEvents","createAiSiteBatch","listAiJobs","createAiJob","resumeAiJob","aiGenerateComponent","applyAiSeoAudit","assistChat","assistEditApplied","assistFeedback","PATCH","aiHostPermissions","aiAdminOrg","aiAdminOrgsSpend","aiAdminSignals","aiAdminUser","aiAdminOverage","aiUsage","aiAllotments","aiInsightAnswer","runAiInsightsDigest","AI_INSIGHTS_DIGEST_PATH","aiModels","billingCredits","billingOverage","registerFirstPartyProviders","registerAiJobKinds","app","firestore","registerAiConsoleApi","web","request","method","context","params","Promise","resolve","jobId","String"],"mappings":"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"}
@@ -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 all three, reading through `firestore`. */
18
+ export declare function standInAutomationIndexes(firestore: FirebaseFirestore.Firestore): void;
19
+ /** Forgets all three, so the next spec starts with no plugin keeping automations. */
20
+ export declare function removeStandInAutomationIndexes(): void;