@aglyn/plugins-ai 1.0.0-beta.285 → 1.0.0-beta.287

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 (210) hide show
  1. package/package.json +17 -17
  2. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  3. package/src/lib/assets/site-styles/professional.webp +0 -0
  4. package/src/lib/assets/site-styles/store.webp +0 -0
  5. package/src/lib/assets/site-styles/trades.webp +0 -0
  6. package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
  7. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  8. package/src/lib/declarations.js +5 -2
  9. package/src/lib/declarations.js.map +1 -1
  10. package/src/lib/declarations.server.js +12 -0
  11. package/src/lib/declarations.server.js.map +1 -1
  12. package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
  13. package/src/lib/jobs/ai-build-capabilities.js +27 -2
  14. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  15. package/src/lib/jobs/ai-image-reads.d.ts +59 -0
  16. package/src/lib/jobs/ai-image-reads.js +138 -0
  17. package/src/lib/jobs/ai-image-reads.js.map +1 -0
  18. package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
  19. package/src/lib/jobs/ai-job-build-step.js +83 -12
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
  22. package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
  23. package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
  25. package/src/lib/jobs/ai-job-import-step.js +15 -11
  26. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
  28. package/src/lib/jobs/ai-job-insight-step.js +23 -5
  29. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-layout-language.js +2 -1
  31. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
  33. package/src/lib/jobs/ai-job-media-step.js +6 -8
  34. package/src/lib/jobs/ai-job-media-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
  36. package/src/lib/jobs/ai-job-navigation-step.js +7 -9
  37. package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
  38. package/src/lib/jobs/ai-job-page-language.js +7 -1
  39. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  40. package/src/lib/jobs/ai-job-page-sections.js +16 -3
  41. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  42. package/src/lib/jobs/ai-job-page-step.js +9 -1
  43. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
  45. package/src/lib/jobs/ai-job-plan-step.js +78 -13
  46. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  47. package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
  48. package/src/lib/jobs/ai-job-privacy-step.js +14 -15
  49. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-products-step.js +5 -3
  51. package/src/lib/jobs/ai-job-products-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
  53. package/src/lib/jobs/ai-job-settings-step.js +7 -8
  54. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
  56. package/src/lib/jobs/ai-job-site-content.js +67 -8
  57. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  58. package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
  59. package/src/lib/jobs/ai-job-site-datasets.js +298 -30
  60. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  61. package/src/lib/jobs/ai-job-site-step.js +6 -3
  62. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
  64. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
  65. package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
  66. package/src/lib/jobs/ai-job-tracking-step.js +7 -8
  67. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  68. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
  69. package/src/lib/jobs/ai-jobs-beat.js +18 -8
  70. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  71. package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
  72. package/src/lib/jobs/ai-jobs-notify.js +136 -3
  73. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  74. package/src/lib/jobs/ai-layout-stock-photos.d.ts +59 -4
  75. package/src/lib/jobs/ai-layout-stock-photos.js +897 -81
  76. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  77. package/src/lib/jobs/ai-product-image.d.ts +14 -0
  78. package/src/lib/jobs/ai-product-image.js +73 -21
  79. package/src/lib/jobs/ai-product-image.js.map +1 -1
  80. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
  81. package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
  82. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  83. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
  84. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
  85. package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
  86. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
  87. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
  88. package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
  89. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
  90. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
  91. package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
  92. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
  93. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
  94. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
  95. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
  96. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
  97. package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
  98. package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
  99. package/src/lib/layout-language/ai-layout-compiler.js +194 -18
  100. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  101. package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
  102. package/src/lib/layout-language/ai-layout-copy.js +383 -3
  103. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  104. package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
  105. package/src/lib/layout-language/ai-layout-design.js +31 -11
  106. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  107. package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
  108. package/src/lib/layout-language/ai-layout-listings.js +9 -1
  109. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  110. package/src/lib/layout-language/ai-layout-pictures.d.ts +30 -0
  111. package/src/lib/layout-language/ai-layout-pictures.js +131 -3
  112. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  113. package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
  114. package/src/lib/layout-language/ai-layout-storefront.js +100 -20
  115. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
  116. package/src/lib/model/ai-build-job.d.ts +7 -0
  117. package/src/lib/model/ai-build-job.js +36 -7
  118. package/src/lib/model/ai-build-job.js.map +1 -1
  119. package/src/lib/model/ai-build-plan.d.ts +28 -4
  120. package/src/lib/model/ai-build-plan.js +49 -9
  121. package/src/lib/model/ai-build-plan.js.map +1 -1
  122. package/src/lib/model/ai-build-progress.js +3 -1
  123. package/src/lib/model/ai-build-progress.js.map +1 -1
  124. package/src/lib/model/ai-job-notice.d.ts +8 -0
  125. package/src/lib/model/ai-job-notice.js +33 -0
  126. package/src/lib/model/ai-job-notice.js.map +1 -1
  127. package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
  128. package/src/lib/model/ai-job-plan-lines.js +6 -1
  129. package/src/lib/model/ai-job-plan-lines.js.map +1 -1
  130. package/src/lib/model/ai-jobs.types.d.ts +8 -0
  131. package/src/lib/model/ai-jobs.types.js.map +1 -1
  132. package/src/lib/model/ai-permissions.d.ts +13 -0
  133. package/src/lib/model/ai-permissions.js +29 -1
  134. package/src/lib/model/ai-permissions.js.map +1 -1
  135. package/src/lib/model/ai-products.d.ts +18 -0
  136. package/src/lib/model/ai-products.js +32 -0
  137. package/src/lib/model/ai-products.js.map +1 -1
  138. package/src/lib/model/ai-site-inspiration.d.ts +3 -2
  139. package/src/lib/model/ai-site-inspiration.js +3 -2
  140. package/src/lib/model/ai-site-inspiration.js.map +1 -1
  141. package/src/lib/plugin.js +4 -13
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.js +9 -2
  144. package/src/lib/providers/anthropic.js.map +1 -1
  145. package/src/lib/providers/catalog.js +14 -5
  146. package/src/lib/providers/catalog.js.map +1 -1
  147. package/src/lib/providers/contract.d.ts +24 -1
  148. package/src/lib/providers/contract.js.map +1 -1
  149. package/src/lib/providers/routing.js +5 -2
  150. package/src/lib/providers/routing.js.map +1 -1
  151. package/src/lib/runtime/ai-dataset-generation.d.ts +99 -1
  152. package/src/lib/runtime/ai-dataset-generation.js +251 -3
  153. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  154. package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
  155. package/src/lib/runtime/ai-dev-replay.js +4 -2
  156. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  157. package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
  158. package/src/lib/runtime/ai-doctrine-validators.js +343 -27
  159. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  160. package/src/lib/runtime/ai-doctrine.js +8 -2
  161. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  162. package/src/lib/runtime/ai-icon-library.d.ts +27 -1
  163. package/src/lib/runtime/ai-icon-library.js +30 -3
  164. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  165. package/src/lib/runtime/ai-products-generation.d.ts +11 -1
  166. package/src/lib/runtime/ai-products-generation.js +51 -4
  167. package/src/lib/runtime/ai-products-generation.js.map +1 -1
  168. package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
  169. package/src/lib/runtime/assist-answer-cache.js +5 -2
  170. package/src/lib/runtime/assist-answer-cache.js.map +1 -1
  171. package/src/lib/server/ai-allotments.js +7 -2
  172. package/src/lib/server/ai-allotments.js.map +1 -1
  173. package/src/lib/server/ai-experiment-versions.js +5 -5
  174. package/src/lib/server/ai-experiment-versions.js.map +1 -1
  175. package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
  176. package/src/lib/server/ai-host-permissions-roster.js +33 -0
  177. package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
  178. package/src/lib/server/ai-host-permissions.js +8 -6
  179. package/src/lib/server/ai-host-permissions.js.map +1 -1
  180. package/src/lib/server/ai-image-reads-route.d.ts +17 -0
  181. package/src/lib/server/ai-image-reads-route.js +106 -0
  182. package/src/lib/server/ai-image-reads-route.js.map +1 -0
  183. package/src/lib/server/ai-seo-apply.js +5 -5
  184. package/src/lib/server/ai-seo-apply.js.map +1 -1
  185. package/src/lib/server/ai-site-import-route.js +7 -4
  186. package/src/lib/server/ai-site-import-route.js.map +1 -1
  187. package/src/lib/server/assist-build.js +1 -0
  188. package/src/lib/server/assist-build.js.map +1 -1
  189. package/src/lib/server/assist-chat.d.ts +11 -7
  190. package/src/lib/server/assist-chat.js +185 -48
  191. package/src/lib/server/assist-chat.js.map +1 -1
  192. package/src/lib/server/assist-docs-query.d.ts +51 -0
  193. package/src/lib/server/assist-docs-query.js +57 -0
  194. package/src/lib/server/assist-docs-query.js.map +1 -0
  195. package/src/lib/server/assist-jobs-context.d.ts +102 -0
  196. package/src/lib/server/assist-jobs-context.js +227 -0
  197. package/src/lib/server/assist-jobs-context.js.map +1 -0
  198. package/src/lib/server/assist-view-context.d.ts +8 -4
  199. package/src/lib/server/assist-view-context.js +12 -7
  200. package/src/lib/server/assist-view-context.js.map +1 -1
  201. package/src/lib/server.js +12 -0
  202. package/src/lib/server.js.map +1 -1
  203. package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
  204. package/src/lib/tools/ai-dataset-tool.js +24 -7
  205. package/src/lib/tools/ai-dataset-tool.js.map +1 -1
  206. package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
  207. package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
  208. package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
  209. package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
  210. package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
@@ -29,7 +29,7 @@ import { permissionRefusal, authForPool, checkRateLimit, emailUnverifiedResponse
29
29
  import { recordUserAiRefusal } from "../usage/ai-usage-by-user.js";
30
30
  import { estimateAssistCostUsd, publicAssistQuota, recordAssistExchange, releaseAssistMessage, reserveAssistMessage } from "../usage/assist-usage.js";
31
31
  import { assistAnswerCacheKey, readAssistAnswerCache, writeAssistAnswerCache } from "../runtime/assist-answer-cache.js";
32
- import { docsGroundingBlock, retrieveDocsSections } from "@aglyn/aglyn/app-utils/docs-retrieval";
32
+ import { docsGroundingBlock, docsLookupNeedsEnglish, retrieveDocsSections } from "@aglyn/aglyn/app-utils/docs-retrieval";
33
33
  import { composeDocsLinksAnswer, deflectToDocs, questionStandsAlone, sectionLabel, sectionUrl } from "@aglyn/aglyn/app-utils/docs-deflection";
34
34
  import { ASSIST_EDIT_TOOL_NAME, assistEditDocumentOf } from "../model/assist-edit.js";
35
35
  import { ASSIST_EDIT_MAX_OUTPUT_TOKENS, assistEditTool, editCanvasBlock, editSelectionBlock, parseAssistEditContext, resolveAssistEdit } from "./assist-edit.js";
@@ -37,6 +37,8 @@ import { ASSIST_BUILD_TOOL_NAME } from "../model/assist-build.js";
37
37
  import { assistBuildDraftsBlock, assistDraftsForTurn, parseAssistBuildDrafts } from "../model/assist-follow-up.js";
38
38
  import { assistBuildBlock, assistBuildIntents, assistBuildIntentsBlock, assistBuildTool, resolveAssistBuild } from "./assist-build.js";
39
39
  import { describeView, extractAssistAction, finalAssistText, resolveAssistProposal, safeOrgFacts, sanitiseId, sanitiseRoute, viewFactsBlock, viewScreenBlock, visibleAssistText, withAssistOpenDraftAction } from "./assist-view-context.js";
40
+ import { assistAsksAboutItsWorkspace, assistJobsHere, assistWorkspaceBlock, readAssistOpenJobs } from "./assist-jobs-context.js";
41
+ import { ASSIST_DOCS_QUERY_INPUT_CHARS, ASSIST_DOCS_QUERY_MAX_TOKENS, ASSIST_DOCS_QUERY_SYSTEM, parseAssistDocsQuery } from "./assist-docs-query.js";
40
42
  import { invalidIdTokenResponse } from "@aglyn/tenant-data-admin/server/id-token-refusal";
41
43
  // By its own entry point rather than the barrel (AGL-2903), for the reason
42
44
  // `isRefusedIdToken` is: this route's spec replaces the barrel with a
@@ -115,7 +117,9 @@ import { checkAiClientIpRateLimit, freeAccountAgeRefusal } from "../runtime/ai-a
115
117
  * answers PLUS page-context awareness (level 2 — the current route/host is
116
118
  * injected so the assistant can walk the user through the view they are
117
119
  * on). Free orgs get level 1 only: docs-grounded answers and deep links;
118
- * any client-sent page context is deliberately dropped.
120
+ * any client-sent page context is deliberately dropped. Every tier is told
121
+ * its plan by name and, where AI jobs exist, the workspace's open ones
122
+ * (AGL-4075); a job on the site the page named links its page there.
119
123
  *
120
124
  * Streaming: the route re-emits the provider's stream as simplified
121
125
  * `data: {type:'delta',text}` events, then one `{type:'done', exchangeId,
@@ -132,18 +136,20 @@ import { checkAiClientIpRateLimit, freeAccountAgeRefusal } from "../runtime/ai-a
132
136
  * thinking plus answer together, so leaving it off the request would spend
133
137
  * output-priced thinking tokens on "how do I publish a screen", stall the
134
138
  * stream behind a silent think (display defaults to `omitted`, i.e. empty
135
- * thinking blocks), and truncate the answer inside the same 1024-token
136
- * ceiling. `effort: low` is the documented posture for a scoped chat
137
- * workload; both are asserted in the spec so a silent default change fails.
139
+ * thinking blocks), and truncate the answer inside the same 2,048-token
140
+ * ceiling (1,024 until AGL-4075). `effort: low` is the documented posture for
141
+ * a scoped chat workload; both are asserted in the spec so a silent default
142
+ * change fails.
138
143
  *
139
- * Prompt caching: the system array is ordered stable → volatile across FIVE
144
+ * Prompt caching: the system array is ordered stable → volatile across SIX
140
145
  * blocks, with breakpoints after the first two.
141
146
  *
142
147
  * 1. static prompt [breakpoint] identical everywhere
143
148
  * 2. screen description [breakpoint] identical per ROUTE, any tenant
144
149
  * 3. product brand the org's name for the product
145
- * 4. request facts workspace, plan, host, path
146
- * 5. docs retrieval follows the question
150
+ * 4. workspace its plan and open AI jobs (AGL-4075)
151
+ * 5. request facts workspace, host, path (paid)
152
+ * 6. docs retrieval follows the question
147
153
  *
148
154
  * On the edit and build rungs a SITE block (AGL-3661) follows the stable
149
155
  * blocks: the business profile, status and remembered preferences of the
@@ -270,6 +276,10 @@ Rules:
270
276
  - Be concise and task-focused: answer the question, give the steps, link the source. Skip preamble.
271
277
  - If the question is not about the product, say so briefly and point the user back to product topics.
272
278
  - If you do not know, say so and suggest contacting support from the Support page rather than guessing.
279
+ - A feature exists only when the documentation sections or the console page description given to you say so. When neither does, say you cannot confirm it exists rather than describing how it might work.
280
+ - Right-to-left layout is not supported yet: every site is laid out left to right, including in Arabic, Hebrew, Persian and Urdu. Never say otherwise.
281
+ - This chat takes text only. You cannot receive or open files, images or screenshots, and none can be attached here. When something needs a file, tell the user to upload it on the site's Media page.
282
+ - Answer in the language the user wrote in. The documentation is in English: link it as given, but write your answer in the user's language.
273
283
  - The product's interface calls a site's pages "pages", never "screens". Routes, tools, fields and ids still say "screen" (a screenId, /screens), but always say "page" to the user.
274
284
 
275
285
  Guiding from the current page:
@@ -607,15 +617,14 @@ ${AI_ACCEPTABLE_USE_BLOCK}`;
607
617
  const pathSlug = (_route_split_filter_ = route.split('/').filter(Boolean)[0]) != null ? _route_split_filter_ : '';
608
618
  const orgSlug = sanitiseId(String((_org_slug = org['slug']) != null ? _org_slug : '')) || (ORGLESS_PATH_SEGMENTS.has(pathSlug) ? '' : sanitiseId(pathSlug));
609
619
  const view = describeView(route);
610
- const { name, plan } = safeOrgFacts(org);
620
+ const { name } = safeOrgFacts(org);
611
621
  return {
612
622
  screen: viewScreenBlock(view, rung),
613
623
  facts: viewFactsBlock({
614
624
  route,
615
625
  hostId,
616
626
  orgSlug,
617
- name,
618
- plan
627
+ name
619
628
  }),
620
629
  view,
621
630
  scope: {
@@ -743,6 +752,70 @@ ${AI_ACCEPTABLE_USE_BLOCK}`;
743
752
  */ function upstreamFailureCopy(retryable) {
744
753
  return retryable ? 'The assistant is busy right now — try again in a moment.' : 'The assistant request failed — try again.';
745
754
  }
755
+ /** The docs a turn cites: each section's label and URL, and the paths the exchange records. */ function citedDocs(scored) {
756
+ return {
757
+ docs: scored.map(({ section })=>({
758
+ title: sectionLabel(section),
759
+ url: sectionUrl(section)
760
+ })),
761
+ docsPaths: scored.map(({ section })=>section.path + section.anchor)
762
+ };
763
+ }
764
+ /** Two calls' tokens as one turn's: the English lookup's and the answer's, on one model. */ function turnUsage(answer, lookup) {
765
+ var _answer_cacheWriteHourTokens, _lookup_cacheWriteHourTokens;
766
+ if (!lookup) return answer;
767
+ const hour = ((_answer_cacheWriteHourTokens = answer.cacheWriteHourTokens) != null ? _answer_cacheWriteHourTokens : 0) + ((_lookup_cacheWriteHourTokens = lookup.cacheWriteHourTokens) != null ? _lookup_cacheWriteHourTokens : 0);
768
+ return _extends({}, answer, {
769
+ inputTokens: answer.inputTokens + lookup.inputTokens,
770
+ outputTokens: answer.outputTokens + lookup.outputTokens,
771
+ cacheReadTokens: answer.cacheReadTokens + lookup.cacheReadTokens,
772
+ cacheWriteTokens: answer.cacheWriteTokens + lookup.cacheWriteTokens
773
+ }, hour ? {
774
+ cacheWriteHourTokens: hour
775
+ } : {});
776
+ }
777
+ /**
778
+ * The question as an English search query (AGL-4075), on the turn's own
779
+ * model, inside the turn the reservation admitted — see `assist-docs-query`.
780
+ * `null` when the lookup failed, and the docs are searched as asked; the
781
+ * usage comes back whatever the answer, because the tokens were spent.
782
+ */ async function englishDocsQuery(model, question) {
783
+ try {
784
+ const result = await runAiRequest(_extends({
785
+ model,
786
+ maxTokens: ASSIST_DOCS_QUERY_MAX_TOKENS,
787
+ stream: false
788
+ }, CHAT_ROUTE.thinking ? {
789
+ thinking: CHAT_ROUTE.thinking
790
+ } : {}, CHAT_ROUTE.effort ? {
791
+ effort: CHAT_ROUTE.effort
792
+ } : {}, {
793
+ system: [
794
+ {
795
+ text: ASSIST_DOCS_QUERY_SYSTEM
796
+ }
797
+ ],
798
+ messages: [
799
+ {
800
+ role: 'user',
801
+ content: question.slice(0, ASSIST_DOCS_QUERY_INPUT_CHARS)
802
+ }
803
+ ]
804
+ }));
805
+ return {
806
+ query: result.kind === 'completion' ? parseAssistDocsQuery(result.text) : '',
807
+ usage: result.usage
808
+ };
809
+ } catch (error) {
810
+ // The answer still comes; it is grounded in what the question's own
811
+ // words find, as before the lookup existed.
812
+ console.error('assist english docs lookup failed', {
813
+ model,
814
+ error
815
+ });
816
+ return null;
817
+ }
818
+ }
746
819
  async function handler(request) {
747
820
  var _request_headers_get;
748
821
  if (request.method !== 'POST') {
@@ -776,8 +849,8 @@ async function handler(request) {
776
849
  });
777
850
  }
778
851
  try {
779
- var _resolved_org, _org_slug, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
780
- var _body_context, _body_context1, _body_context2, _body_context3, _quota_allotment, _quota_allotment1;
852
+ var _resolved_org, _org_slug, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
853
+ var _body_context, _body_context1, _body_context2, _body_context3, _quota_allotment, _quota_allotment1, _body_context4, _body_context5;
781
854
  const app = firebaseAdmin.app();
782
855
  const decoded = await app.auth().verifyIdToken(idToken);
783
856
  if (!decoded.email_verified && !isImpersonationSession(decoded)) {
@@ -906,11 +979,18 @@ async function handler(request) {
906
979
  // documentation back to the user is the spend this issue exists to
907
980
  // remove.
908
981
  const scored = retrieveDocsSections(body.question);
909
- const docsPaths = scored.map(({ section })=>section.path + section.anchor);
910
- const docs = scored.map(({ section })=>({
911
- title: sectionLabel(section),
912
- url: sectionUrl(section)
913
- }));
982
+ const { docs, docsPaths } = citedDocs(scored);
983
+ // A question the docs' English cannot find by its own words (AGL-4075):
984
+ // looked up in English below, once a model is paying for the turn, and
985
+ // never answered with an English quote, since its answer is written in
986
+ // the asker's language.
987
+ const needsEnglish = docsLookupNeedsEnglish(body.question);
988
+ // Nor is a question about the workspace's own plan or AI jobs (AGL-4075):
989
+ // the docs describe every plan and every job, and only this turn's
990
+ // workspace block says which plan this is and where its jobs stand.
991
+ const asksAboutWorkspace = assistAsksAboutItsWorkspace(body.question, {
992
+ jobs: checkEntitlement(org, 'aiGenerative')
993
+ });
914
994
  // On the edit rung a docs quote cannot carry the proposal the author may
915
995
  // be asking for, so only a question that reads on its own — a how-to, not
916
996
  // an instruction and not a question about "this" element — is answered
@@ -918,10 +998,10 @@ async function handler(request) {
918
998
  // for a question that needs nothing around it, and the open canvas is
919
999
  // exactly what an edit request leans on. So is a follow-up on a draft
920
1000
  // this thread's build made (AGL-3616): "make the about page shorter".
921
- const deflection = (editRung || buildRung || body.drafts.length) && !questionStandsAlone(body.question) ? null : deflectToDocs(body.question, scored, body.history.length > 0);
1001
+ const deflection = needsEnglish || asksAboutWorkspace || (editRung || buildRung || body.drafts.length) && !questionStandsAlone(body.question) ? null : deflectToDocs(body.question, scored, body.history.length > 0);
922
1002
  if (deflection == null ? void 0 : deflection.answered) {
923
- var _ref6;
924
- var _body_context4, _body_context5;
1003
+ var _ref8;
1004
+ var _body_context6, _body_context7;
925
1005
  // Costs no provider tokens, so it takes NO reservation: a docs answer
926
1006
  // must not spend one of a free workspace's ten messages a day. The
927
1007
  // per-uid rate limiter above is what bounds this path, and it is the
@@ -934,8 +1014,8 @@ async function handler(request) {
934
1014
  answer: deflection.answer,
935
1015
  docs,
936
1016
  docsPaths: deflection.quoted.map((section)=>section.path + section.anchor),
937
- route: (_ref6 = (_body_context4 = body.context) == null ? void 0 : _body_context4.route) != null ? _ref6 : '',
938
- hostId: ((_body_context5 = body.context) == null ? void 0 : _body_context5.hostId) || null,
1017
+ route: (_ref8 = (_body_context6 = body.context) == null ? void 0 : _body_context6.route) != null ? _ref8 : '',
1018
+ hostId: ((_body_context7 = body.context) == null ? void 0 : _body_context7.hostId) || null,
939
1019
  tier: entitled ? 'entitled' : 'free',
940
1020
  rate
941
1021
  });
@@ -966,8 +1046,8 @@ async function handler(request) {
966
1046
  // panel does not print this string; it has its own plain-English line for
967
1047
  // a 501, because the person reading it is not the person who sets the var.
968
1048
  if (!aiProviderReady()) {
969
- var _ref7;
970
- var _body_context6, _body_context7;
1049
+ var _ref9;
1050
+ var _body_context8, _body_context9;
971
1051
  const closest = composeDocsLinksAnswer(scored.map(({ section })=>section));
972
1052
  if (!closest) {
973
1053
  return Response.json({
@@ -987,13 +1067,37 @@ async function handler(request) {
987
1067
  answer: closest,
988
1068
  docs,
989
1069
  docsPaths,
990
- route: (_ref7 = (_body_context6 = body.context) == null ? void 0 : _body_context6.route) != null ? _ref7 : '',
991
- hostId: ((_body_context7 = body.context) == null ? void 0 : _body_context7.hostId) || null,
1070
+ route: (_ref9 = (_body_context8 = body.context) == null ? void 0 : _body_context8.route) != null ? _ref9 : '',
1071
+ hostId: ((_body_context9 = body.context) == null ? void 0 : _body_context9.hostId) || null,
992
1072
  tier: entitled ? 'entitled' : 'free',
993
1073
  rate,
994
1074
  servedBy: 'docs-links'
995
1075
  });
996
1076
  }
1077
+ // ── The workspace's open AI jobs (AGL-4075) ───────────────────────────
1078
+ // Read for every plan where the jobs exist at all — the same rungs the
1079
+ // AI jobs list at the top of this panel climbs: `aiGenerative` and the
1080
+ // release flag (staff preview). One bounded read, before the answer
1081
+ // cache, which a turn told of a job must not use. A failure leaves the
1082
+ // jobs out of the turn, never the answer.
1083
+ const jobsShown = checkEntitlement(org, 'aiGenerative') && (staff || await isServerReleaseFlagOnForOrg('release_ai_generative', body.orgId));
1084
+ let openJobs = null;
1085
+ let jobsUnread = false;
1086
+ if (jobsShown) {
1087
+ try {
1088
+ openJobs = await readAssistOpenJobs(firestore, {
1089
+ orgId: body.orgId,
1090
+ uid: decoded.uid
1091
+ });
1092
+ } catch (error) {
1093
+ console.error('assist open jobs read failed', {
1094
+ orgId: body.orgId,
1095
+ error
1096
+ });
1097
+ jobsUnread = true;
1098
+ }
1099
+ }
1100
+ const plan = safeOrgFacts(org).plan;
997
1101
  // ── The answer cache (AGL-2486) ───────────────────────────────────────
998
1102
  // Only reached by an ESCALATION, and only on a FIRST turn: a turn with
999
1103
  // history is answered against a conversation this key cannot describe,
@@ -1022,17 +1126,23 @@ async function handler(request) {
1022
1126
  // rung (AGL-2906): that answer is composed against the canvas the request
1023
1127
  // described, which the key cannot describe either. Nor does a turn that
1024
1128
  // listed a build's drafts (AGL-3616): its answer may open one of them.
1025
- const cacheKey = body.history.length || body.model || editRung || buildRung || body.drafts.length ? '' : assistAnswerCacheKey({
1129
+ //
1130
+ // Nor does a turn told of an open AI job (AGL-4075), or one whose jobs
1131
+ // could not be read: its answer may be about that job, which has moved
1132
+ // on by the next time anyone asks. The plan's name, which every turn is
1133
+ // told, is in the key.
1134
+ const cacheKey = body.history.length || body.model || editRung || buildRung || body.drafts.length || jobsUnread || (openJobs == null ? void 0 : openJobs.length) ? '' : assistAnswerCacheKey({
1026
1135
  question: body.question,
1027
1136
  tier: entitled ? 'entitled' : 'free',
1028
1137
  route: (_ref = (_body_context2 = body.context) == null ? void 0 : _body_context2.route) != null ? _ref : '',
1029
1138
  model: assistModel(),
1030
- productName: resolveBrandingProfile(org).productName
1139
+ productName: resolveBrandingProfile(org).productName,
1140
+ plan
1031
1141
  });
1032
1142
  const cached = cacheKey ? await readAssistAnswerCache(firestore, body.orgId, cacheKey) : null;
1033
1143
  if (cached) {
1034
- var _cached_docs, _ref8;
1035
- var _body_context8, _body_context9;
1144
+ var _cached_docs, _ref10;
1145
+ var _body_context10, _body_context11;
1036
1146
  // Spent nothing, so it takes no reservation — the same rule the docs
1037
1147
  // path follows, for the same reason.
1038
1148
  return docsOnlyResponse({
@@ -1043,8 +1153,8 @@ async function handler(request) {
1043
1153
  answer: cached.answer,
1044
1154
  docs: (_cached_docs = cached.docs) != null ? _cached_docs : [],
1045
1155
  docsPaths,
1046
- route: (_ref8 = (_body_context8 = body.context) == null ? void 0 : _body_context8.route) != null ? _ref8 : '',
1047
- hostId: ((_body_context9 = body.context) == null ? void 0 : _body_context9.hostId) || null,
1156
+ route: (_ref10 = (_body_context10 = body.context) == null ? void 0 : _body_context10.route) != null ? _ref10 : '',
1157
+ hostId: ((_body_context11 = body.context) == null ? void 0 : _body_context11.hostId) || null,
1048
1158
  tier: entitled ? 'entitled' : 'free',
1049
1159
  rate,
1050
1160
  servedBy: 'cache'
@@ -1087,7 +1197,7 @@ async function handler(request) {
1087
1197
  // month counts it beside the org counter the reservation moved.
1088
1198
  recordUserAiRefusal(firestore, body.orgId, decoded.uid, quota);
1089
1199
  if (!quota.allowed) {
1090
- var _ref9;
1200
+ var _ref11;
1091
1201
  // Five refusals, not two. The spend ceiling (AGL-2264) is armed by
1092
1202
  // default at $40, and it must not borrow the message cap's words:
1093
1203
  // "reached its limit for the month" invites the user to count their
@@ -1139,10 +1249,10 @@ async function handler(request) {
1139
1249
  }
1140
1250
  const ownControl = assistOwnControlRefusalText(org, refusedBy);
1141
1251
  return Response.json({
1142
- error: (_ref9 = ownControl != null ? ownControl : // The Free taste's own precautions (AGL-2925): a clock or an
1252
+ error: (_ref11 = ownControl != null ? ownControl : // The Free taste's own precautions (AGL-2925): a clock or an
1143
1253
  // upgrade, in one sentence, from the same helper the other
1144
1254
  // doors read.
1145
- assistFreeTasteRefusalText(refusedBy)) != null ? _ref9 : refusedBy === 'budget' || refusedBy === 'band' ? quota.budgetUsd === null ? 'This workspace reached its assistant spending limit for the month' : 'This workspace used its assistant credits for the month' : entitled ? 'This workspace reached its assistant limit for the month' : `Free workspaces get ${quota.limit} assistant messages a day — upgrade to Pro for more`,
1255
+ assistFreeTasteRefusalText(refusedBy)) != null ? _ref11 : refusedBy === 'budget' || refusedBy === 'band' ? quota.budgetUsd === null ? 'This workspace reached its assistant spending limit for the month' : 'This workspace used its assistant credits for the month' : entitled ? 'This workspace reached its assistant limit for the month' : `Free workspaces get ${quota.limit} assistant messages a day — upgrade to Pro for more`,
1146
1256
  reason: 'quota',
1147
1257
  // CREDITS, never the reservation itself: it carries `costUsd`,
1148
1258
  // `costLimitUsd` and `budgetUsd`, all three of which are our
@@ -1163,7 +1273,24 @@ async function handler(request) {
1163
1273
  orgModels: (_ref2 = (_quota_allotment1 = quota.allotment) == null ? void 0 : _quota_allotment1.orgModels) != null ? _ref2 : null
1164
1274
  });
1165
1275
  const model = (_ref3 = choice == null ? void 0 : choice.model) != null ? _ref3 : assistModel();
1166
- const docsBlock = docsGroundingBlock(scored);
1276
+ // The docs lookup in English (AGL-4075), for a question its own words
1277
+ // cannot find: one small completion on this turn's model, metered with
1278
+ // the answer. What it finds replaces what the words found, when it
1279
+ // finds anything.
1280
+ const lookup = needsEnglish ? await englishDocsQuery(model, body.question) : null;
1281
+ const englishFound = (lookup == null ? void 0 : lookup.query) ? retrieveDocsSections(lookup.query) : [];
1282
+ const grounding = englishFound.length ? englishFound : scored;
1283
+ const cited = englishFound.length ? citedDocs(englishFound) : {
1284
+ docs,
1285
+ docsPaths
1286
+ };
1287
+ const docsBlock = docsGroundingBlock(grounding);
1288
+ // The plan by name for every workspace, and its open AI jobs (AGL-4075).
1289
+ const workspaceBlock = assistWorkspaceBlock({
1290
+ plan,
1291
+ jobs: openJobs,
1292
+ here: assistJobsHere(sanitiseRoute((_ref4 = (_body_context4 = body.context) == null ? void 0 : _body_context4.route) != null ? _ref4 : ''), (_ref5 = (_body_context5 = body.context) == null ? void 0 : _body_context5.hostId) != null ? _ref5 : '', ORGLESS_PATH_SEGMENTS)
1293
+ });
1167
1294
  // Level 2 is the paid rung. A free workspace gets docs grounding and
1168
1295
  // deep links; the view block, and with it the proposal channel, is not
1169
1296
  // assembled at all — not assembled-then-withheld, so there is no path
@@ -1185,7 +1312,7 @@ async function handler(request) {
1185
1312
  // that may change the site: the edit and build rungs, whose host was
1186
1313
  // checked for `ai.generate` above. Free and paid read the same block. A
1187
1314
  // site breakpoint when the stable blocks leave one, volatile otherwise.
1188
- const siteHostId = (_ref4 = (_ref5 = buildRung == null ? void 0 : buildRung.hostId) != null ? _ref5 : editRung == null ? void 0 : editRung.target.hostId) != null ? _ref4 : null;
1315
+ const siteHostId = (_ref6 = (_ref7 = buildRung == null ? void 0 : buildRung.hostId) != null ? _ref7 : editRung == null ? void 0 : editRung.target.hostId) != null ? _ref6 : null;
1189
1316
  const siteContext = siteHostId ? await readAiSiteContext(firestore, {
1190
1317
  orgId: body.orgId,
1191
1318
  hostId: siteHostId
@@ -1249,6 +1376,14 @@ async function handler(request) {
1249
1376
  text: assistBrandBlock(org),
1250
1377
  volatile: true
1251
1378
  },
1379
+ // The workspace's plan and open AI jobs (AGL-4075): per org and per
1380
+ // moment, so after every breakpoint, for every plan.
1381
+ ...workspaceBlock ? [
1382
+ {
1383
+ text: workspaceBlock,
1384
+ volatile: true
1385
+ }
1386
+ ] : [],
1252
1387
  ...guide ? [
1253
1388
  {
1254
1389
  text: guide.facts,
@@ -1356,7 +1491,7 @@ async function handler(request) {
1356
1491
  cacheWriteTokens: 0
1357
1492
  };
1358
1493
  try {
1359
- var _ref, _ref1, _ref2, _ref3;
1494
+ var _ref, _ref1, _ref2, _ref3, _ref4;
1360
1495
  var _guide_view, _describeView, _body_context;
1361
1496
  {
1362
1497
  var _iteratorAbruptCompletion = false, _didIteratorError = false, _iteratorError, _return;
@@ -1499,20 +1634,22 @@ async function handler(request) {
1499
1634
  }
1500
1635
  // The data loop + meters — recorded even for a partial answer:
1501
1636
  // tokens were spent either way and the miner wants the question.
1637
+ // The English lookup's tokens are this turn's too (AGL-4075).
1638
+ const spent = turnUsage(usage, (_ref3 = lookup == null ? void 0 : lookup.usage) != null ? _ref3 : null);
1502
1639
  let exchangeId = null;
1503
1640
  try {
1504
- var _ref4;
1641
+ var _ref5;
1505
1642
  var _body_context1, _body_context2;
1506
1643
  exchangeId = await recordAssistExchange(firestore, body.orgId, _extends({
1507
1644
  uid: decoded.uid,
1508
1645
  question: body.question,
1509
1646
  answer: answer.slice(0, MAX_STORED_ANSWER_CHARS),
1510
- route: (_ref4 = (_body_context1 = body.context) == null ? void 0 : _body_context1.route) != null ? _ref4 : '',
1647
+ route: (_ref5 = (_body_context1 = body.context) == null ? void 0 : _body_context1.route) != null ? _ref5 : '',
1511
1648
  hostId: ((_body_context2 = body.context) == null ? void 0 : _body_context2.hostId) || null,
1512
1649
  model,
1513
1650
  tier,
1514
- usage,
1515
- docsPaths,
1651
+ usage: spent,
1652
+ docsPaths: cited.docsPaths,
1516
1653
  stopReason,
1517
1654
  // The account this turn drew on, as the reservation decided
1518
1655
  // it (AGL-2925) — so a Free turn lands on the owner's
@@ -1543,14 +1680,14 @@ async function handler(request) {
1543
1680
  if (cacheKey && answer && !proposal && !build && stopReason !== 'refusal' && stopReason !== 'max_tokens' && model === assistModel()) {
1544
1681
  await writeAssistAnswerCache(firestore, body.orgId, cacheKey, {
1545
1682
  answer,
1546
- docs
1683
+ docs: cited.docs
1547
1684
  });
1548
1685
  }
1549
1686
  emit({
1550
1687
  type: 'done',
1551
1688
  exchangeId,
1552
- usage,
1553
- docs,
1689
+ usage: spent,
1690
+ docs: cited.docs,
1554
1691
  // Inert data. The panel renders it as a card the user must
1555
1692
  // confirm, and confirming navigates — see the write boundary in
1556
1693
  // `assist-view-context.ts`.
@@ -1574,10 +1711,10 @@ async function handler(request) {
1574
1711
  // The usage strip's envelope (AGL-2942): this turn's credits
1575
1712
  // added to the pool and the asker's month the reservation read.
1576
1713
  meter: aiUsageMeter(quota, {
1577
- lastCredits: assistCreditsFromUsd(estimateAssistCostUsd(usage, model)),
1714
+ lastCredits: assistCreditsFromUsd(estimateAssistCostUsd(spent, model)),
1578
1715
  model: {
1579
1716
  id: model,
1580
- auto: (_ref3 = choice == null ? void 0 : choice.auto) != null ? _ref3 : true
1717
+ auto: (_ref4 = choice == null ? void 0 : choice.auto) != null ? _ref4 : true
1581
1718
  }
1582
1719
  })
1583
1720
  });