@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.
- package/package.json +17 -17
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/components/ai-theme-proposal-card.component.js +12 -3
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/declarations.js +5 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +3 -1
- package/src/lib/jobs/ai-build-capabilities.js +27 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +59 -0
- package/src/lib/jobs/ai-image-reads.js +138 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +29 -0
- package/src/lib/jobs/ai-job-build-step.js +83 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-duplicate-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-duplicate-step.js +6 -6
- package/src/lib/jobs/ai-job-duplicate-step.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-import-step.js +15 -11
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.d.ts +10 -0
- package/src/lib/jobs/ai-job-insight-step.js +23 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +2 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-media-step.js +6 -8
- package/src/lib/jobs/ai-job-media-step.js.map +1 -1
- package/src/lib/jobs/ai-job-navigation-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-navigation-step.js +7 -9
- package/src/lib/jobs/ai-job-navigation-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +7 -1
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +16 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +9 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +30 -8
- package/src/lib/jobs/ai-job-plan-step.js +78 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-privacy-step.d.ts +4 -2
- package/src/lib/jobs/ai-job-privacy-step.js +14 -15
- package/src/lib/jobs/ai-job-privacy-step.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.js +5 -3
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-settings-step.js +7 -8
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +34 -1
- package/src/lib/jobs/ai-job-site-content.js +67 -8
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +53 -3
- package/src/lib/jobs/ai-job-site-datasets.js +298 -30
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.js +3 -3
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-tracking-step.js +7 -8
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -7
- package/src/lib/jobs/ai-jobs-beat.js +18 -8
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +66 -1
- package/src/lib/jobs/ai-jobs-notify.js +136 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +59 -4
- package/src/lib/jobs/ai-layout-stock-photos.js +897 -81
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-product-image.d.ts +14 -0
- package/src/lib/jobs/ai-product-image.js +73 -21
- package/src/lib/jobs/ai-product-image.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +9 -1
- package/src/lib/jobs/ai-theme-brand-inputs.js +28 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.d.ts +175 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js +601 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-datasets.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.d.ts +46 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js +2862 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.d.ts +74 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js +888 -0
- package/src/lib/jobs/fixtures/ai-site-agl4065-plans.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.d.ts +29 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js +81 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-judge.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.d.ts +87 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js +225 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl4065-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +11 -0
- package/src/lib/layout-language/ai-layout-compiler.js +194 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-copy.js +383 -3
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +19 -0
- package/src/lib/layout-language/ai-layout-design.js +31 -11
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +7 -1
- package/src/lib/layout-language/ai-layout-listings.js +9 -1
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +30 -0
- package/src/lib/layout-language/ai-layout-pictures.js +131 -3
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +18 -4
- package/src/lib/layout-language/ai-layout-storefront.js +100 -20
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +7 -0
- package/src/lib/model/ai-build-job.js +36 -7
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +28 -4
- package/src/lib/model/ai-build-plan.js +49 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +3 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +8 -0
- package/src/lib/model/ai-job-notice.js +33 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +2 -1
- package/src/lib/model/ai-job-plan-lines.js +6 -1
- package/src/lib/model/ai-job-plan-lines.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +8 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-permissions.d.ts +13 -0
- package/src/lib/model/ai-permissions.js +29 -1
- package/src/lib/model/ai-permissions.js.map +1 -1
- package/src/lib/model/ai-products.d.ts +18 -0
- package/src/lib/model/ai-products.js +32 -0
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/model/ai-site-inspiration.d.ts +3 -2
- package/src/lib/model/ai-site-inspiration.js +3 -2
- package/src/lib/model/ai-site-inspiration.js.map +1 -1
- package/src/lib/plugin.js +4 -13
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.js +9 -2
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.js +14 -5
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +24 -1
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.js +5 -2
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +99 -1
- package/src/lib/runtime/ai-dataset-generation.js +251 -3
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.d.ts +1 -0
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +47 -3
- package/src/lib/runtime/ai-doctrine-validators.js +343 -27
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +8 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +27 -1
- package/src/lib/runtime/ai-icon-library.js +30 -3
- package/src/lib/runtime/ai-icon-library.js.map +1 -1
- package/src/lib/runtime/ai-products-generation.d.ts +11 -1
- package/src/lib/runtime/ai-products-generation.js +51 -4
- package/src/lib/runtime/ai-products-generation.js.map +1 -1
- package/src/lib/runtime/assist-answer-cache.d.ts +2 -0
- package/src/lib/runtime/assist-answer-cache.js +5 -2
- package/src/lib/runtime/assist-answer-cache.js.map +1 -1
- package/src/lib/server/ai-allotments.js +7 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-experiment-versions.js +5 -5
- package/src/lib/server/ai-experiment-versions.js.map +1 -1
- package/src/lib/server/ai-host-permissions-roster.d.ts +25 -0
- package/src/lib/server/ai-host-permissions-roster.js +33 -0
- package/src/lib/server/ai-host-permissions-roster.js.map +1 -0
- package/src/lib/server/ai-host-permissions.js +8 -6
- package/src/lib/server/ai-host-permissions.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.d.ts +17 -0
- package/src/lib/server/ai-image-reads-route.js +106 -0
- package/src/lib/server/ai-image-reads-route.js.map +1 -0
- package/src/lib/server/ai-seo-apply.js +5 -5
- package/src/lib/server/ai-seo-apply.js.map +1 -1
- package/src/lib/server/ai-site-import-route.js +7 -4
- package/src/lib/server/ai-site-import-route.js.map +1 -1
- package/src/lib/server/assist-build.js +1 -0
- package/src/lib/server/assist-build.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +11 -7
- package/src/lib/server/assist-chat.js +185 -48
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-docs-query.d.ts +51 -0
- package/src/lib/server/assist-docs-query.js +57 -0
- package/src/lib/server/assist-docs-query.js.map +1 -0
- package/src/lib/server/assist-jobs-context.d.ts +102 -0
- package/src/lib/server/assist-jobs-context.js +227 -0
- package/src/lib/server/assist-jobs-context.js.map +1 -0
- package/src/lib/server/assist-view-context.d.ts +8 -4
- package/src/lib/server/assist-view-context.js +12 -7
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +12 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-dataset-tool.d.ts +11 -1
- package/src/lib/tools/ai-dataset-tool.js +24 -7
- package/src/lib/tools/ai-dataset-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +5 -6
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +0 -32
- package/src/lib/components/ai-collaborator-permissions-column.component.js +0 -132
- package/src/lib/components/ai-collaborator-permissions-column.component.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-media-step.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { MEDIA_ALT_MAX_LENGTH } from '@aglyn/aglyn/app-utils/media-alt'\nimport { embeddedMetadataIsCurrent } from '@aglyn/aglyn/app-utils/media-embedded-fields'\nimport {\n MEDIA_FOLDER_MAX_DEPTH,\n MEDIA_FOLDER_NAME_MAX_LENGTH,\n newMediaFolderDoc,\n normalizeFolderName,\n} from '@aglyn/aglyn/app-utils/media-folders'\nimport {\n MEDIA_TAG_MAX_COUNT,\n MEDIA_TAG_MAX_LENGTH,\n mediaFilterKeys,\n mediaNameWords,\n normalizeMediaTags,\n} from '@aglyn/aglyn/app-utils/media-metadata'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `media` step (AGL-3752): organizing files already in a site's own media\n * library — the folder each is filed in, its tags, its alt text and its name —\n * as a first-class build operation, so \"put the kiln photos in a Studio\n * folder\", \"tag these portfolio\" or \"write the missing alt text\" is one item\n * on Assist's plan card.\n *\n * NO MODEL. The planner wrote the values as the item's arguments; this holds\n * each to the rule the media library's own controls hold it to and writes it,\n * so it costs no credits.\n *\n * ── Only the library's own fields ────────────────────────────────────────\n *\n * Exactly what the Details drawer and the grid's bulk actions write on a\n * file's document: `fileName` (200), `alt` (`MEDIA_ALT_MAX_LENGTH`), `tags`\n * (`normalizeMediaTags`: lower case, 20 of 40 characters), and `folderId`\n * with the legacy `folder` name beside it, with the library's filter and\n * search keys re-derived from the document as the write leaves it\n * (`mediaFilterKeys`). A new folder is the New folder button's: a\n * `mediaFolders` document made by `newMediaFolderDoc`, its name held by\n * `normalizeFolderName`, unique among its siblings without case, at most\n * `MEDIA_FOLDER_MAX_DEPTH` deep. Filing a file writes its folder as the\n * drawer's save does; the stored object takes the folder's path the next time\n * that folder is moved or renamed, as after a drawer save. Its address never\n * changes, so no page that uses it breaks.\n *\n * NEVER: uploading, copying, replacing or deleting a file, its bytes or the\n * details inside them, its sharing or privacy, renaming or deleting a folder,\n * or anything in the organization's shared library (every site of the\n * workspace uses it). Those are not arguments.\n *\n * ── Alt text ─────────────────────────────────────────────────────────────\n *\n * Alt text describes ONE image: `alt` is for one image the request says what\n * it shows. `altFromDetails` writes a missing alt from what the library\n * already holds about the image — its description, or the caption, headline\n * or title inside the file — and names the images that hold none. Never the\n * file name, which read aloud is worse than silence (`inheritedMediaAlt`).\n * The library's alt is what a placement takes when the image is placed;\n * placements made before keep their own.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The rules' own for a site library (`canWriteHostContent`,\n * `hostWritesFrozen`): a member with the admin, editor or author role, while\n * no lockdown freezes the site, its org or the platform. Asked at the door,\n * before the write, and the role again inside the transaction that writes.\n *\n * ── Idempotent ───────────────────────────────────────────────────────────\n *\n * One transaction reads the files and folders as they are stored and writes\n * only what differs; a folder of the asked name is found before one is made.\n * A pass asked again writes nothing.\n */\n\nexport const AI_MEDIA_KIND = 'media' as const\n\n/** ASSUMED: the site, the org, up to 20 file lookups and one transaction. No model. */\nexport const AI_JOB_MEDIA_STEP_MINIMUM_MS = 8_000\n\nexport const AI_MEDIA_NO_SITE_COPY = 'Open the site whose media library should change.'\nexport const AI_MEDIA_ROLE_COPY = 'Organizing the media library needs the admin, editor or author role on this site.'\nexport const AI_MEDIA_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_MEDIA_NOTHING_COPY = 'The request named no file in the media library and no change this could make to it.'\n\n/** Where a person finds what the op changed. */\nexport const AI_MEDIA_WHERE = 'the site’s Media library'\n\n/** The output's fixed id: one site's library, whatever changed. */\nexport const AI_MEDIA_OUTPUT_ID = 'library'\n\n/** The library's own limits, each from the control that edits the field. */\nexport const AI_MEDIA_LIMITS = {\n /** Files one item may name or select. */\n files: 20,\n /** The Details drawer's file name. */\n fileName: 200,\n alt: MEDIA_ALT_MAX_LENGTH,\n tags: MEDIA_TAG_MAX_COUNT,\n tag: MEDIA_TAG_MAX_LENGTH,\n /** A folder path: five names of 60 and their separators. */\n folder: MEDIA_FOLDER_MAX_DEPTH * (MEDIA_FOLDER_NAME_MAX_LENGTH + 3),\n} as const\n\n/** The item's arguments, as the planner fills them in. */\nexport const AI_MEDIA_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n files: {\n type: 'array',\n description: 'Files already in this site’s media library, each by its id or exact file name as the request gives it.',\n items: { type: 'string', maxLength: 200 },\n maxItems: AI_MEDIA_LIMITS.files,\n },\n missingAlt: {\n type: 'boolean',\n description: 'True to also take the site library’s images that have no alt text yet (up to 20), for altFromDetails.',\n },\n folder: {\n type: 'string',\n description: 'The folder to file them in, as a path such as Portfolio / 2026; a folder not there yet is made.',\n maxLength: AI_MEDIA_LIMITS.folder,\n },\n addTags: {\n type: 'array',\n description: 'Tags to add, each a word or two.',\n items: { type: 'string', maxLength: AI_MEDIA_LIMITS.tag },\n maxItems: AI_MEDIA_LIMITS.tags,\n },\n removeTags: {\n type: 'array',\n description: 'Tags to take off.',\n items: { type: 'string', maxLength: AI_MEDIA_LIMITS.tag },\n maxItems: AI_MEDIA_LIMITS.tags,\n },\n rename: {\n type: 'string',\n description: 'A new file name, for exactly one file. Its address does not change.',\n maxLength: AI_MEDIA_LIMITS.fileName,\n },\n alt: {\n type: 'string',\n description:\n 'Alt text for exactly one image, saying what it shows in one plain sentence, only from what the request says about it. Never its file name.',\n maxLength: AI_MEDIA_LIMITS.alt,\n },\n altFromDetails: {\n type: 'boolean',\n description: 'True to write each image’s missing alt text from its own description or caption already in the library.',\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks, each value read and held to the library's rule. */\nexport interface AiMediaRequest {\n files: string[]\n missingAlt: boolean\n /** The folder path's names, root first. */\n folder?: string[]\n addTags: string[]\n removeTags: string[]\n rename?: string\n alt?: string\n altFromDetails: boolean\n}\n\nexport interface AiMediaRead {\n request: AiMediaRequest\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\nconst list = (value: unknown): string[] =>\n (Array.isArray(value) ? value : typeof value === 'string' ? [value] : []).map(text).filter(Boolean)\n\n/** A folder path's names, root first, or a sentence saying why it cannot be one. */\nexport function aiMediaFolderPathOf(value: string): string[] | { error: string } {\n const names = value.split('/').map((part) => part.trim()).filter(Boolean)\n if (!names.length) return { error: 'The folder has no name.' }\n if (names.length > MEDIA_FOLDER_MAX_DEPTH) return { error: `Folders can nest at most ${MEDIA_FOLDER_MAX_DEPTH} levels.` }\n const normalized: string[] = []\n for (const name of names) {\n const one = normalizeFolderName(name)\n if (!one) return { error: `A folder name is at most ${MEDIA_FOLDER_NAME_MAX_LENGTH} characters.` }\n normalized.push(one)\n }\n return normalized\n}\n\n/** Words that name no image: alt text made of them alone describes nothing. */\nconst EMPTY_ALT = /^(an? |the )?(image|photo|photograph|picture|pic|graphic|img|icon|file|logo|banner|thumbnail)s?( of)?\\.?$/i\n\n/** Why this alt text describes nothing, or `null`. */\nexport function aiMediaAltProblem(alt: string, fileName: string): string | null {\n if (EMPTY_ALT.test(alt.trim())) return `“${alt}” does not say what the image shows.`\n const folded = (value: string) => mediaNameWords(value).toLowerCase().replace(/\\s+/g, ' ')\n const base = fileName.replace(/\\.[A-Za-z0-9]{2,5}$/, '')\n if (folded(alt) && (folded(alt) === folded(fileName) || folded(alt) === folded(base))) {\n return 'Alt text that repeats the file name does not say what the image shows.'\n }\n return null\n}\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiMediaRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiMediaRead {\n const problems: string[] = []\n const files = [...new Set(list(inputs?.['files']).map((one) => one.slice(0, 200)))]\n if (files.length > AI_MEDIA_LIMITS.files) problems.push(`One change takes at most ${AI_MEDIA_LIMITS.files} files; the rest were left.`)\n const request: AiMediaRequest = {\n files: files.slice(0, AI_MEDIA_LIMITS.files),\n missingAlt: inputs?.['missingAlt'] === true,\n addTags: normalizeMediaTags(list(inputs?.['addTags'])),\n removeTags: normalizeMediaTags(list(inputs?.['removeTags'])),\n altFromDetails: inputs?.['altFromDetails'] === true,\n }\n const folder = text(inputs?.['folder'])\n if (folder) {\n const path = aiMediaFolderPathOf(folder)\n if (Array.isArray(path)) request.folder = path\n else problems.push(path.error)\n }\n const rename = text(inputs?.['rename'])\n if (rename) {\n if (rename.length > AI_MEDIA_LIMITS.fileName) problems.push(`A file name is at most ${AI_MEDIA_LIMITS.fileName} characters.`)\n else if (request.files.length !== 1 || request.missingAlt) problems.push('A new name is for exactly one file.')\n else request.rename = rename\n }\n const alt = text(inputs?.['alt'])\n if (alt) {\n if (alt.length > AI_MEDIA_LIMITS.alt) problems.push(`Alt text is at most ${AI_MEDIA_LIMITS.alt} characters.`)\n else if (request.files.length !== 1 || request.missingAlt) problems.push('Alt text describes one image; name exactly one.')\n else request.alt = alt\n }\n return { request, problems }\n}\n\n/** Whether a read request names files and a change to make to them. */\nexport function aiMediaRequestEmpty(request: AiMediaRequest): boolean {\n const selects = request.files.length > 0 || request.missingAlt\n const changes =\n request.folder !== undefined ||\n request.addTags.length > 0 ||\n request.removeTags.length > 0 ||\n request.rename !== undefined ||\n request.alt !== undefined ||\n request.altFromDetails\n return !selects || !changes\n}\n\n// ── Files and folders ─────────────────────────────────────────────────────\n\n/** A site library row, as the op reads it. */\nexport interface AiMediaRow {\n id: string\n /** The document as stored, which the filter keys are derived from. */\n data: Record<string, unknown>\n}\n\n/** A media folder, as the op reads it. */\nexport interface AiMediaFolderRow {\n id: string\n name: string\n parentId: string | null\n}\n\nconst fileNameOf = (row: AiMediaRow) => String(row.data['fileName'] ?? '') || row.id\nconst isImage = (row: AiMediaRow) => String(row.data['contentType'] ?? '').startsWith('image/')\n\n/**\n * The file a name or id names among the rows read from the site's library:\n * never a deleted one. Its id wins over a file name; two files of one name\n * are refused in words.\n */\nexport function aiMediaFileOf(source: string, rows: readonly AiMediaRow[]): AiMediaRow | { error: string } {\n const live = rows.filter((row) => row.data['deletedAt'] == null)\n const byId = live.find((row) => row.id === source)\n if (byId) return byId\n const named = live.filter((row) => row.data['fileName'] === source)\n if (named.length > 1) return { error: `The media library has more than one file called “${source}”; name it by its id.` }\n return named[0] ?? { error: `This site’s media library has no file “${source}”.` }\n}\n\n/**\n * Alt text written from what the library already holds about an image: its\n * own description, else the caption, headline or title read from inside the\n * file (only while that record still describes the file's bytes). `null`\n * when it holds none that says what the image shows.\n */\nexport function aiMediaAltFromDetails(row: AiMediaRow): string | null {\n const candidates: unknown[] = [row.data['description']]\n const embedded = row.data['embeddedMetadata']\n if (embeddedMetadataIsCurrent(embedded, row.data['contentSha256'])) {\n const byKey = new Map(embedded.fields.map((field) => [field.key, field.value]))\n candidates.push(byKey.get('description'), byKey.get('headline'), byKey.get('title'))\n }\n for (const candidate of candidates) {\n const value = text(candidate)\n if (!value) continue\n const alt = value.length > AI_MEDIA_LIMITS.alt ? `${value.slice(0, AI_MEDIA_LIMITS.alt - 1).replace(/\\s+\\S*$/, '')}…` : value\n if (!aiMediaAltProblem(alt, fileNameOf(row))) return alt\n }\n return null\n}\n\n/** A folder to make: its name, and its parent's id or its index among those made before it. */\nexport interface AiMediaFolderCreate {\n name: string\n parent: { id: string | null } | { made: number }\n}\n\n/**\n * Where a folder path lands: the existing folders it walks, matched without\n * case among siblings as the library's sibling rule matches them, and the\n * folders left to make below the last one found.\n */\nexport function aiMediaFolderPlan(\n path: readonly string[],\n folders: readonly AiMediaFolderRow[],\n): { existingId: string | null; create: AiMediaFolderCreate[] } {\n let parentId: string | null = null\n let index = 0\n for (; index < path.length; index += 1) {\n const name = path[index].toLowerCase()\n const found = folders.find((folder) => folder.parentId === parentId && folder.name.trim().toLowerCase() === name)\n if (!found) break\n parentId = found.id\n }\n const create: AiMediaFolderCreate[] = path.slice(index).map((name, offset) => ({\n name,\n parent: offset === 0 ? { id: parentId } : { made: offset - 1 },\n }))\n return { existingId: parentId, create }\n}\n\n/** One file's change: the fields to write and the sentences that say it. */\nexport interface AiMediaFileChange {\n update: Record<string, unknown>\n said: string[]\n notDone: string[]\n}\n\n/**\n * What the request changes on one file, compared with what is stored: only\n * the fields that differ, with the filter keys re-derived from the document\n * as the change leaves it, exactly as the Details drawer derives them.\n */\nexport function aiMediaFileChange(\n row: AiMediaRow,\n request: AiMediaRequest,\n folder: { id: string; name: string } | null,\n): AiMediaFileChange {\n const name = fileNameOf(row)\n const update: Record<string, unknown> = {}\n const said: string[] = []\n const notDone: string[] = []\n if (request.rename && request.rename !== row.data['fileName']) {\n update['fileName'] = request.rename\n said.push(`“${name}” is now called “${request.rename}”.`)\n }\n const tags = Array.isArray(row.data['tags']) ? (row.data['tags'] as unknown[]).map(String) : []\n if (request.addTags.length || request.removeTags.length) {\n const removed = new Set(request.removeTags)\n const next = normalizeMediaTags([...tags, ...request.addTags]).filter((tag) => !removed.has(tag))\n const dropped = request.addTags.filter((tag) => !removed.has(tag) && !next.includes(tag))\n if (dropped.length) notDone.push(`“${name}” holds ${AI_MEDIA_LIMITS.tags} tags at most, so ${dropped.join(', ')} was not added.`)\n if (JSON.stringify(next) !== JSON.stringify(tags)) {\n update['tags'] = next\n said.push(`“${name}” is tagged ${next.length ? next.join(', ') : 'with nothing'}.`)\n }\n }\n if (folder && (row.data['folderId'] ?? null) !== folder.id) {\n update['folderId'] = folder.id\n // The legacy name beside it, as the drawer keeps it until every reader is on folderId.\n update['folder'] = folder.name\n said.push(`“${name}” is in ${folder.name}.`)\n }\n const storedAlt = text(row.data['alt'])\n let alt: string | null = null\n if (request.alt) {\n if (!isImage(row)) notDone.push(`“${name}” is not an image, so it takes no alt text.`)\n else {\n const problem = aiMediaAltProblem(request.alt, name)\n if (problem) notDone.push(problem)\n else alt = request.alt\n }\n } else if (request.altFromDetails && isImage(row) && !storedAlt) {\n alt = aiMediaAltFromDetails(row)\n if (!alt) notDone.push(`“${name}” has no description or caption in the library to write its alt text from; say what it shows.`)\n }\n if (alt && alt !== storedAlt) {\n update['alt'] = alt\n said.push(`“${name}” has alt text.`)\n }\n if (update['fileName'] !== undefined || update['alt'] !== undefined) {\n Object.assign(update, mediaFilterKeys({ ...row.data, ...update }))\n }\n return { update, said, notDone }\n}\n\nexport type AiMediaWrite =\n | { ok: true; done: string[]; notDone: string[]; changedFiles: number; madeFolders: string[]; altChanged: boolean }\n | { ok: false; error: string }\n\nconst ID_SHAPE = /^[A-Za-z0-9_-]{1,128}$/\n\n/**\n * The library change, written in one transaction that reads the site, its\n * folders and the named files as they are stored NOW: the member still holds\n * a write role, a folder of the asked name is used before one is made, and\n * only fields that differ are written.\n */\nexport async function writeAiMediaChanges(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiMediaRequest; now: Date },\n): Promise<AiMediaWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const mediaRef = hostRef.collection('media')\n const foldersRef = hostRef.collection('mediaFolders')\n const { request } = input\n return firestore.runTransaction(async (tx): Promise<AiMediaWrite> => {\n // Every read first: a transaction writes nothing until its reads are done.\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_MEDIA_ROLE_COPY }\n const notDone: string[] = []\n const rows = new Map<string, AiMediaRow>()\n for (const source of request.files) {\n const read: AiMediaRow[] = []\n if (ID_SHAPE.test(source)) {\n const byId = await tx.get(mediaRef.doc(source))\n if (byId.exists) read.push({ id: byId.id, data: byId.data() ?? {} })\n }\n const byName = await tx.get(mediaRef.where('fileName', '==', source).limit(5))\n for (const doc of byName.docs) if (!read.some((row) => row.id === doc.id)) read.push({ id: doc.id, data: doc.data() })\n const found = aiMediaFileOf(source, read)\n if ('error' in found) notDone.push(found.error)\n else rows.set(found.id, found)\n }\n if (request.missingAlt) {\n // The library's own \"Alt text is missing\" filter: `hasAlt`, which every writer derives.\n const missing = await tx.get(mediaRef.where('hasAlt', '==', false).limit(AI_MEDIA_LIMITS.files * 2))\n for (const doc of missing.docs) {\n const row = { id: doc.id, data: doc.data() }\n if (rows.size >= AI_MEDIA_LIMITS.files) break\n if (row.data['deletedAt'] == null && isImage(row)) rows.set(row.id, row)\n }\n if (!missing.docs.length && !request.files.length) notDone.push('Every image in this site’s media library has alt text.')\n }\n const folders: AiMediaFolderRow[] = []\n if (request.folder && rows.size) {\n const stored = await tx.get(foldersRef.limit(500))\n for (const doc of stored.docs) {\n folders.push({ id: doc.id, name: String(doc.get('name') ?? ''), parentId: (doc.get('parentId') as string | null) ?? null })\n }\n }\n\n let folder: { id: string; name: string } | null = null\n const madeFolders: string[] = []\n if (request.folder && rows.size) {\n const plan = aiMediaFolderPlan(request.folder, folders)\n const made: string[] = []\n for (const one of plan.create) {\n const ref = foldersRef.doc(createResourceUid())\n const parentId = 'id' in one.parent ? one.parent.id : made[one.parent.made]\n // The New folder button's document: a site library's folder stores no scope.\n tx.set(ref, newMediaFolderDoc({ name: one.name, parentId, createdAt: FieldValue.serverTimestamp(), visibleTo: null }))\n made.push(ref.id)\n madeFolders.push(one.name)\n }\n const id = made[made.length - 1] ?? plan.existingId\n if (id) folder = { id, name: request.folder[request.folder.length - 1] }\n }\n\n const done: string[] = madeFolders.map((name) => `Made the folder ${name}.`)\n let changedFiles = 0\n let altChanged = false\n for (const row of rows.values()) {\n const change = aiMediaFileChange(row, request, folder)\n notDone.push(...change.notDone)\n if (!Object.keys(change.update).length) continue\n tx.update(mediaRef.doc(row.id), { ...change.update, updatedAt: input.now })\n done.push(...change.said)\n changedFiles += 1\n if (change.update['alt'] !== undefined) altChanged = true\n }\n return { ok: true, done, notDone, changedFiles, madeFolders, altChanged }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not organize this site's library now, or `null`. */\nexport async function aiMediaAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_MEDIA_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_MEDIA_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, and what was not done. */\nexport function aiMediaOutputNote(done: readonly string[], notDone: readonly string[], altChanged: boolean): string {\n // The build's own wording for an item with no draft state (AGL-3745).\n const did = done.length ? `${done.join(' ')} Live now, in ${AI_MEDIA_WHERE}.` : 'Nothing needed changing.'\n const placed = altChanged ? ' Images placed from now on take the new alt text; ones already on a page keep their own.' : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${placed}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobMediaStepDeps {\n write?: typeof writeAiMediaChanges\n lockdown?: typeof getLockdownVerdict\n}\n\nexport function createAiJobMediaStep(deps: AiJobMediaStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiMediaChanges\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_MEDIA_NO_SITE_COPY)\n const read = aiMediaRequestOf(job.inputs)\n if (aiMediaRequestEmpty(read.request)) return unspent([AI_MEDIA_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiMediaAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, now })\n if (written.ok === false) return unspent(written.error)\n const notDone = [...read.problems, ...written.notDone]\n if (!written.done.length && notDone.length) return unspent(notDone.join(' '))\n const output: AiJobOutput = {\n resource: 'media',\n id: AI_MEDIA_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Media library',\n note: aiMediaOutputNote(written.done, notDone, written.altChanged),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobMediaStep = createAiJobMediaStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiMediaJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A library change is admitted on a site of the job's own org, for a member\n * who may write its library now, where the request names files and a change\n * this can make. Refused in those words before anything is written.\n */\nexport function createAiMediaJobAdmission(deps: AiMediaJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_MEDIA_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiMediaAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiMediaRequestOf(context.inputs as Record<string, unknown>)\n return aiMediaRequestEmpty(read.request) ? { status: 400, error: [AI_MEDIA_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiMediaJobAdmission = createAiMediaJobAdmission()\n\n/** Registers the media library step and its door (AGL-3752). */\nexport function registerAiMediaJob(): void {\n registerAiJobStep(AI_MEDIA_KIND, runAiJobMediaStep, { minimumMs: AI_JOB_MEDIA_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_MEDIA_KIND, aiMediaJobAdmission)\n}\n"],"names":["createResourceUid","MEDIA_ALT_MAX_LENGTH","embeddedMetadataIsCurrent","MEDIA_FOLDER_MAX_DEPTH","MEDIA_FOLDER_NAME_MAX_LENGTH","newMediaFolderDoc","normalizeFolderName","MEDIA_TAG_MAX_COUNT","MEDIA_TAG_MAX_LENGTH","mediaFilterKeys","mediaNameWords","normalizeMediaTags","hostRoleCanWrite","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_MEDIA_KIND","AI_JOB_MEDIA_STEP_MINIMUM_MS","AI_MEDIA_NO_SITE_COPY","AI_MEDIA_ROLE_COPY","AI_MEDIA_LOCKED_COPY","AI_MEDIA_NOTHING_COPY","AI_MEDIA_WHERE","AI_MEDIA_OUTPUT_ID","AI_MEDIA_LIMITS","files","fileName","alt","tags","tag","folder","AI_MEDIA_ARGS_SCHEMA","type","properties","description","items","maxLength","maxItems","missingAlt","addTags","removeTags","rename","altFromDetails","additionalProperties","text","value","replace","trim","list","Array","isArray","map","filter","Boolean","aiMediaFolderPathOf","names","split","part","length","error","normalized","name","one","push","EMPTY_ALT","aiMediaAltProblem","test","folded","toLowerCase","base","aiMediaRequestOf","inputs","problems","Set","slice","request","path","aiMediaRequestEmpty","selects","changes","undefined","fileNameOf","row","String","data","id","isImage","startsWith","aiMediaFileOf","source","rows","named","live","byId","find","aiMediaAltFromDetails","candidates","embedded","byKey","Map","fields","field","key","get","candidate","aiMediaFolderPlan","folders","parentId","index","found","create","offset","parent","made","existingId","aiMediaFileChange","update","said","notDone","removed","next","has","dropped","includes","join","JSON","stringify","storedAlt","problem","Object","assign","ID_SHAPE","writeAiMediaChanges","firestore","input","hostRef","collection","doc","hostId","mediaRef","foldersRef","runTransaction","tx","host","exists","ok","roles","uid","read","byName","where","limit","docs","some","set","missing","size","stored","madeFolders","plan","ref","createdAt","serverTimestamp","visibleTo","done","changedFiles","altChanged","values","change","keys","updatedAt","now","aiMediaAccessRefusal","deps","locked","lockdown","staff","org","intent","aiMediaOutputNote","did","placed","not","createAiJobMediaStep","write","job","modelFor","model","unspent","failure","orgData","orgId","refused","createdBy","review","written","output","resource","hostSubdomain","label","note","outputs","runAiJobMediaStep","createAiMediaJobAdmission","ownerOf","context","status","owner","aiMediaJobAdmission","registerAiMediaJob","minimumMs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-media-step.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { holdsSiteKey } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { orgPermissionLabel } from '@aglyn/aglyn/app-utils/org-permissions'\nimport { MEDIA_ALT_MAX_LENGTH } from '@aglyn/aglyn/app-utils/media-alt'\nimport { embeddedMetadataIsCurrent } from '@aglyn/aglyn/app-utils/media-embedded-fields'\nimport {\n MEDIA_FOLDER_MAX_DEPTH,\n MEDIA_FOLDER_NAME_MAX_LENGTH,\n newMediaFolderDoc,\n normalizeFolderName,\n} from '@aglyn/aglyn/app-utils/media-folders'\nimport {\n MEDIA_TAG_MAX_COUNT,\n MEDIA_TAG_MAX_LENGTH,\n mediaFilterKeys,\n mediaNameWords,\n normalizeMediaTags,\n} from '@aglyn/aglyn/app-utils/media-metadata'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `media` step (AGL-3752): organizing files already in a site's own media\n * library — the folder each is filed in, its tags, its alt text and its name —\n * as a first-class build operation, so \"put the kiln photos in a Studio\n * folder\", \"tag these portfolio\" or \"write the missing alt text\" is one item\n * on Assist's plan card.\n *\n * NO MODEL. The planner wrote the values as the item's arguments; this holds\n * each to the rule the media library's own controls hold it to and writes it,\n * so it costs no credits.\n *\n * ── Only the library's own fields ────────────────────────────────────────\n *\n * Exactly what the Details drawer and the grid's bulk actions write on a\n * file's document: `fileName` (200), `alt` (`MEDIA_ALT_MAX_LENGTH`), `tags`\n * (`normalizeMediaTags`: lower case, 20 of 40 characters), and `folderId`\n * with the legacy `folder` name beside it, with the library's filter and\n * search keys re-derived from the document as the write leaves it\n * (`mediaFilterKeys`). A new folder is the New folder button's: a\n * `mediaFolders` document made by `newMediaFolderDoc`, its name held by\n * `normalizeFolderName`, unique among its siblings without case, at most\n * `MEDIA_FOLDER_MAX_DEPTH` deep. Filing a file writes its folder as the\n * drawer's save does; the stored object takes the folder's path the next time\n * that folder is moved or renamed, as after a drawer save. Its address never\n * changes, so no page that uses it breaks.\n *\n * NEVER: uploading, copying, replacing or deleting a file, its bytes or the\n * details inside them, its sharing or privacy, renaming or deleting a folder,\n * or anything in the organization's shared library (every site of the\n * workspace uses it). Those are not arguments.\n *\n * ── Alt text ─────────────────────────────────────────────────────────────\n *\n * Alt text describes ONE image: `alt` is for one image the request says what\n * it shows. `altFromDetails` writes a missing alt from what the library\n * already holds about the image — its description, or the caption, headline\n * or title inside the file — and names the images that hold none. Never the\n * file name, which read aloud is worse than silence (`inheritedMediaAlt`).\n * The library's alt is what a placement takes when the image is placed;\n * placements made before keep their own.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The rules' own for a site library (`canWriteHostContent`,\n * `hostWritesFrozen`): a member with the admin, editor or author role, while\n * no lockdown freezes the site, its org or the platform. Asked at the door,\n * before the write, and the role again inside the transaction that writes.\n *\n * ── Idempotent ───────────────────────────────────────────────────────────\n *\n * One transaction reads the files and folders as they are stored and writes\n * only what differs; a folder of the asked name is found before one is made.\n * A pass asked again writes nothing.\n */\n\nexport const AI_MEDIA_KIND = 'media' as const\n\n/** ASSUMED: the site, the org, up to 20 file lookups and one transaction. No model. */\nexport const AI_JOB_MEDIA_STEP_MINIMUM_MS = 8_000\n\nexport const AI_MEDIA_NO_SITE_COPY = 'Open the site whose media library should change.'\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const AI_MEDIA_ROLE_COPY = `Organizing the media library needs the \"${orgPermissionLabel('site.edit')}\" permission on this site — ask an organization admin.`\nexport const AI_MEDIA_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_MEDIA_NOTHING_COPY = 'The request named no file in the media library and no change this could make to it.'\n\n/** Where a person finds what the op changed. */\nexport const AI_MEDIA_WHERE = 'the site’s Media library'\n\n/** The output's fixed id: one site's library, whatever changed. */\nexport const AI_MEDIA_OUTPUT_ID = 'library'\n\n/** The library's own limits, each from the control that edits the field. */\nexport const AI_MEDIA_LIMITS = {\n /** Files one item may name or select. */\n files: 20,\n /** The Details drawer's file name. */\n fileName: 200,\n alt: MEDIA_ALT_MAX_LENGTH,\n tags: MEDIA_TAG_MAX_COUNT,\n tag: MEDIA_TAG_MAX_LENGTH,\n /** A folder path: five names of 60 and their separators. */\n folder: MEDIA_FOLDER_MAX_DEPTH * (MEDIA_FOLDER_NAME_MAX_LENGTH + 3),\n} as const\n\n/** The item's arguments, as the planner fills them in. */\nexport const AI_MEDIA_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n files: {\n type: 'array',\n description: 'Files already in this site’s media library, each by its id or exact file name as the request gives it.',\n items: { type: 'string', maxLength: 200 },\n maxItems: AI_MEDIA_LIMITS.files,\n },\n missingAlt: {\n type: 'boolean',\n description: 'True to also take the site library’s images that have no alt text yet (up to 20), for altFromDetails.',\n },\n folder: {\n type: 'string',\n description: 'The folder to file them in, as a path such as Portfolio / 2026; a folder not there yet is made.',\n maxLength: AI_MEDIA_LIMITS.folder,\n },\n addTags: {\n type: 'array',\n description: 'Tags to add, each a word or two.',\n items: { type: 'string', maxLength: AI_MEDIA_LIMITS.tag },\n maxItems: AI_MEDIA_LIMITS.tags,\n },\n removeTags: {\n type: 'array',\n description: 'Tags to take off.',\n items: { type: 'string', maxLength: AI_MEDIA_LIMITS.tag },\n maxItems: AI_MEDIA_LIMITS.tags,\n },\n rename: {\n type: 'string',\n description: 'A new file name, for exactly one file. Its address does not change.',\n maxLength: AI_MEDIA_LIMITS.fileName,\n },\n alt: {\n type: 'string',\n description:\n 'Alt text for exactly one image, saying what it shows in one plain sentence, only from what the request says about it. Never its file name.',\n maxLength: AI_MEDIA_LIMITS.alt,\n },\n altFromDetails: {\n type: 'boolean',\n description: 'True to write each image’s missing alt text from its own description or caption already in the library.',\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks, each value read and held to the library's rule. */\nexport interface AiMediaRequest {\n files: string[]\n missingAlt: boolean\n /** The folder path's names, root first. */\n folder?: string[]\n addTags: string[]\n removeTags: string[]\n rename?: string\n alt?: string\n altFromDetails: boolean\n}\n\nexport interface AiMediaRead {\n request: AiMediaRequest\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\nconst list = (value: unknown): string[] =>\n (Array.isArray(value) ? value : typeof value === 'string' ? [value] : []).map(text).filter(Boolean)\n\n/** A folder path's names, root first, or a sentence saying why it cannot be one. */\nexport function aiMediaFolderPathOf(value: string): string[] | { error: string } {\n const names = value.split('/').map((part) => part.trim()).filter(Boolean)\n if (!names.length) return { error: 'The folder has no name.' }\n if (names.length > MEDIA_FOLDER_MAX_DEPTH) return { error: `Folders can nest at most ${MEDIA_FOLDER_MAX_DEPTH} levels.` }\n const normalized: string[] = []\n for (const name of names) {\n const one = normalizeFolderName(name)\n if (!one) return { error: `A folder name is at most ${MEDIA_FOLDER_NAME_MAX_LENGTH} characters.` }\n normalized.push(one)\n }\n return normalized\n}\n\n/** Words that name no image: alt text made of them alone describes nothing. */\nconst EMPTY_ALT = /^(an? |the )?(image|photo|photograph|picture|pic|graphic|img|icon|file|logo|banner|thumbnail)s?( of)?\\.?$/i\n\n/** Why this alt text describes nothing, or `null`. */\nexport function aiMediaAltProblem(alt: string, fileName: string): string | null {\n if (EMPTY_ALT.test(alt.trim())) return `“${alt}” does not say what the image shows.`\n const folded = (value: string) => mediaNameWords(value).toLowerCase().replace(/\\s+/g, ' ')\n const base = fileName.replace(/\\.[A-Za-z0-9]{2,5}$/, '')\n if (folded(alt) && (folded(alt) === folded(fileName) || folded(alt) === folded(base))) {\n return 'Alt text that repeats the file name does not say what the image shows.'\n }\n return null\n}\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiMediaRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiMediaRead {\n const problems: string[] = []\n const files = [...new Set(list(inputs?.['files']).map((one) => one.slice(0, 200)))]\n if (files.length > AI_MEDIA_LIMITS.files) problems.push(`One change takes at most ${AI_MEDIA_LIMITS.files} files; the rest were left.`)\n const request: AiMediaRequest = {\n files: files.slice(0, AI_MEDIA_LIMITS.files),\n missingAlt: inputs?.['missingAlt'] === true,\n addTags: normalizeMediaTags(list(inputs?.['addTags'])),\n removeTags: normalizeMediaTags(list(inputs?.['removeTags'])),\n altFromDetails: inputs?.['altFromDetails'] === true,\n }\n const folder = text(inputs?.['folder'])\n if (folder) {\n const path = aiMediaFolderPathOf(folder)\n if (Array.isArray(path)) request.folder = path\n else problems.push(path.error)\n }\n const rename = text(inputs?.['rename'])\n if (rename) {\n if (rename.length > AI_MEDIA_LIMITS.fileName) problems.push(`A file name is at most ${AI_MEDIA_LIMITS.fileName} characters.`)\n else if (request.files.length !== 1 || request.missingAlt) problems.push('A new name is for exactly one file.')\n else request.rename = rename\n }\n const alt = text(inputs?.['alt'])\n if (alt) {\n if (alt.length > AI_MEDIA_LIMITS.alt) problems.push(`Alt text is at most ${AI_MEDIA_LIMITS.alt} characters.`)\n else if (request.files.length !== 1 || request.missingAlt) problems.push('Alt text describes one image; name exactly one.')\n else request.alt = alt\n }\n return { request, problems }\n}\n\n/** Whether a read request names files and a change to make to them. */\nexport function aiMediaRequestEmpty(request: AiMediaRequest): boolean {\n const selects = request.files.length > 0 || request.missingAlt\n const changes =\n request.folder !== undefined ||\n request.addTags.length > 0 ||\n request.removeTags.length > 0 ||\n request.rename !== undefined ||\n request.alt !== undefined ||\n request.altFromDetails\n return !selects || !changes\n}\n\n// ── Files and folders ─────────────────────────────────────────────────────\n\n/** A site library row, as the op reads it. */\nexport interface AiMediaRow {\n id: string\n /** The document as stored, which the filter keys are derived from. */\n data: Record<string, unknown>\n}\n\n/** A media folder, as the op reads it. */\nexport interface AiMediaFolderRow {\n id: string\n name: string\n parentId: string | null\n}\n\nconst fileNameOf = (row: AiMediaRow) => String(row.data['fileName'] ?? '') || row.id\nconst isImage = (row: AiMediaRow) => String(row.data['contentType'] ?? '').startsWith('image/')\n\n/**\n * The file a name or id names among the rows read from the site's library:\n * never a deleted one. Its id wins over a file name; two files of one name\n * are refused in words.\n */\nexport function aiMediaFileOf(source: string, rows: readonly AiMediaRow[]): AiMediaRow | { error: string } {\n const live = rows.filter((row) => row.data['deletedAt'] == null)\n const byId = live.find((row) => row.id === source)\n if (byId) return byId\n const named = live.filter((row) => row.data['fileName'] === source)\n if (named.length > 1) return { error: `The media library has more than one file called “${source}”; name it by its id.` }\n return named[0] ?? { error: `This site’s media library has no file “${source}”.` }\n}\n\n/**\n * Alt text written from what the library already holds about an image: its\n * own description, else the caption, headline or title read from inside the\n * file (only while that record still describes the file's bytes). `null`\n * when it holds none that says what the image shows.\n */\nexport function aiMediaAltFromDetails(row: AiMediaRow): string | null {\n const candidates: unknown[] = [row.data['description']]\n const embedded = row.data['embeddedMetadata']\n if (embeddedMetadataIsCurrent(embedded, row.data['contentSha256'])) {\n const byKey = new Map(embedded.fields.map((field) => [field.key, field.value]))\n candidates.push(byKey.get('description'), byKey.get('headline'), byKey.get('title'))\n }\n for (const candidate of candidates) {\n const value = text(candidate)\n if (!value) continue\n const alt = value.length > AI_MEDIA_LIMITS.alt ? `${value.slice(0, AI_MEDIA_LIMITS.alt - 1).replace(/\\s+\\S*$/, '')}…` : value\n if (!aiMediaAltProblem(alt, fileNameOf(row))) return alt\n }\n return null\n}\n\n/** A folder to make: its name, and its parent's id or its index among those made before it. */\nexport interface AiMediaFolderCreate {\n name: string\n parent: { id: string | null } | { made: number }\n}\n\n/**\n * Where a folder path lands: the existing folders it walks, matched without\n * case among siblings as the library's sibling rule matches them, and the\n * folders left to make below the last one found.\n */\nexport function aiMediaFolderPlan(\n path: readonly string[],\n folders: readonly AiMediaFolderRow[],\n): { existingId: string | null; create: AiMediaFolderCreate[] } {\n let parentId: string | null = null\n let index = 0\n for (; index < path.length; index += 1) {\n const name = path[index].toLowerCase()\n const found = folders.find((folder) => folder.parentId === parentId && folder.name.trim().toLowerCase() === name)\n if (!found) break\n parentId = found.id\n }\n const create: AiMediaFolderCreate[] = path.slice(index).map((name, offset) => ({\n name,\n parent: offset === 0 ? { id: parentId } : { made: offset - 1 },\n }))\n return { existingId: parentId, create }\n}\n\n/** One file's change: the fields to write and the sentences that say it. */\nexport interface AiMediaFileChange {\n update: Record<string, unknown>\n said: string[]\n notDone: string[]\n}\n\n/**\n * What the request changes on one file, compared with what is stored: only\n * the fields that differ, with the filter keys re-derived from the document\n * as the change leaves it, exactly as the Details drawer derives them.\n */\nexport function aiMediaFileChange(\n row: AiMediaRow,\n request: AiMediaRequest,\n folder: { id: string; name: string } | null,\n): AiMediaFileChange {\n const name = fileNameOf(row)\n const update: Record<string, unknown> = {}\n const said: string[] = []\n const notDone: string[] = []\n if (request.rename && request.rename !== row.data['fileName']) {\n update['fileName'] = request.rename\n said.push(`“${name}” is now called “${request.rename}”.`)\n }\n const tags = Array.isArray(row.data['tags']) ? (row.data['tags'] as unknown[]).map(String) : []\n if (request.addTags.length || request.removeTags.length) {\n const removed = new Set(request.removeTags)\n const next = normalizeMediaTags([...tags, ...request.addTags]).filter((tag) => !removed.has(tag))\n const dropped = request.addTags.filter((tag) => !removed.has(tag) && !next.includes(tag))\n if (dropped.length) notDone.push(`“${name}” holds ${AI_MEDIA_LIMITS.tags} tags at most, so ${dropped.join(', ')} was not added.`)\n if (JSON.stringify(next) !== JSON.stringify(tags)) {\n update['tags'] = next\n said.push(`“${name}” is tagged ${next.length ? next.join(', ') : 'with nothing'}.`)\n }\n }\n if (folder && (row.data['folderId'] ?? null) !== folder.id) {\n update['folderId'] = folder.id\n // The legacy name beside it, as the drawer keeps it until every reader is on folderId.\n update['folder'] = folder.name\n said.push(`“${name}” is in ${folder.name}.`)\n }\n const storedAlt = text(row.data['alt'])\n let alt: string | null = null\n if (request.alt) {\n if (!isImage(row)) notDone.push(`“${name}” is not an image, so it takes no alt text.`)\n else {\n const problem = aiMediaAltProblem(request.alt, name)\n if (problem) notDone.push(problem)\n else alt = request.alt\n }\n } else if (request.altFromDetails && isImage(row) && !storedAlt) {\n alt = aiMediaAltFromDetails(row)\n if (!alt) notDone.push(`“${name}” has no description or caption in the library to write its alt text from; say what it shows.`)\n }\n if (alt && alt !== storedAlt) {\n update['alt'] = alt\n said.push(`“${name}” has alt text.`)\n }\n if (update['fileName'] !== undefined || update['alt'] !== undefined) {\n Object.assign(update, mediaFilterKeys({ ...row.data, ...update }))\n }\n return { update, said, notDone }\n}\n\nexport type AiMediaWrite =\n | { ok: true; done: string[]; notDone: string[]; changedFiles: number; madeFolders: string[]; altChanged: boolean }\n | { ok: false; error: string }\n\nconst ID_SHAPE = /^[A-Za-z0-9_-]{1,128}$/\n\n/**\n * The library change, written in one transaction that reads the site, its\n * folders and the named files as they are stored NOW: the member still holds\n * a write role, a folder of the asked name is used before one is made, and\n * only fields that differ are written.\n */\nexport async function writeAiMediaChanges(\n firestore: Firestore,\n input: { hostId: string; uid: string; request: AiMediaRequest; now: Date },\n): Promise<AiMediaWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const mediaRef = hostRef.collection('media')\n const foldersRef = hostRef.collection('mediaFolders')\n const { request } = input\n return firestore.runTransaction(async (tx): Promise<AiMediaWrite> => {\n // Every read first: a transaction writes nothing until its reads are done.\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n if (!holdsSiteKey(host, input.uid, 'site.edit')) return { ok: false, error: AI_MEDIA_ROLE_COPY }\n const notDone: string[] = []\n const rows = new Map<string, AiMediaRow>()\n for (const source of request.files) {\n const read: AiMediaRow[] = []\n if (ID_SHAPE.test(source)) {\n const byId = await tx.get(mediaRef.doc(source))\n if (byId.exists) read.push({ id: byId.id, data: byId.data() ?? {} })\n }\n const byName = await tx.get(mediaRef.where('fileName', '==', source).limit(5))\n for (const doc of byName.docs) if (!read.some((row) => row.id === doc.id)) read.push({ id: doc.id, data: doc.data() })\n const found = aiMediaFileOf(source, read)\n if ('error' in found) notDone.push(found.error)\n else rows.set(found.id, found)\n }\n if (request.missingAlt) {\n // The library's own \"Alt text is missing\" filter: `hasAlt`, which every writer derives.\n const missing = await tx.get(mediaRef.where('hasAlt', '==', false).limit(AI_MEDIA_LIMITS.files * 2))\n for (const doc of missing.docs) {\n const row = { id: doc.id, data: doc.data() }\n if (rows.size >= AI_MEDIA_LIMITS.files) break\n if (row.data['deletedAt'] == null && isImage(row)) rows.set(row.id, row)\n }\n if (!missing.docs.length && !request.files.length) notDone.push('Every image in this site’s media library has alt text.')\n }\n const folders: AiMediaFolderRow[] = []\n if (request.folder && rows.size) {\n const stored = await tx.get(foldersRef.limit(500))\n for (const doc of stored.docs) {\n folders.push({ id: doc.id, name: String(doc.get('name') ?? ''), parentId: (doc.get('parentId') as string | null) ?? null })\n }\n }\n\n let folder: { id: string; name: string } | null = null\n const madeFolders: string[] = []\n if (request.folder && rows.size) {\n const plan = aiMediaFolderPlan(request.folder, folders)\n const made: string[] = []\n for (const one of plan.create) {\n const ref = foldersRef.doc(createResourceUid())\n const parentId = 'id' in one.parent ? one.parent.id : made[one.parent.made]\n // The New folder button's document: a site library's folder stores no scope.\n tx.set(ref, newMediaFolderDoc({ name: one.name, parentId, createdAt: FieldValue.serverTimestamp(), visibleTo: null }))\n made.push(ref.id)\n madeFolders.push(one.name)\n }\n const id = made[made.length - 1] ?? plan.existingId\n if (id) folder = { id, name: request.folder[request.folder.length - 1] }\n }\n\n const done: string[] = madeFolders.map((name) => `Made the folder ${name}.`)\n let changedFiles = 0\n let altChanged = false\n for (const row of rows.values()) {\n const change = aiMediaFileChange(row, request, folder)\n notDone.push(...change.notDone)\n if (!Object.keys(change.update).length) continue\n tx.update(mediaRef.doc(row.id), { ...change.update, updatedAt: input.now })\n done.push(...change.said)\n changedFiles += 1\n if (change.update['alt'] !== undefined) altChanged = true\n }\n return { ok: true, done, notDone, changedFiles, madeFolders, altChanged }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not organize this site's library now, or `null`. */\nexport async function aiMediaAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n if (!holdsSiteKey(input.host, input.uid, 'site.edit')) return AI_MEDIA_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_MEDIA_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, and what was not done. */\nexport function aiMediaOutputNote(done: readonly string[], notDone: readonly string[], altChanged: boolean): string {\n // The build's own wording for an item with no draft state (AGL-3745).\n const did = done.length ? `${done.join(' ')} Live now, in ${AI_MEDIA_WHERE}.` : 'Nothing needed changing.'\n const placed = altChanged ? ' Images placed from now on take the new alt text; ones already on a page keep their own.' : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${placed}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobMediaStepDeps {\n write?: typeof writeAiMediaChanges\n lockdown?: typeof getLockdownVerdict\n}\n\nexport function createAiJobMediaStep(deps: AiJobMediaStepDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiMediaChanges\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_MEDIA_NO_SITE_COPY)\n const read = aiMediaRequestOf(job.inputs)\n if (aiMediaRequestEmpty(read.request)) return unspent([AI_MEDIA_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiMediaAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, now })\n if (written.ok === false) return unspent(written.error)\n const notDone = [...read.problems, ...written.notDone]\n if (!written.done.length && notDone.length) return unspent(notDone.join(' '))\n const output: AiJobOutput = {\n resource: 'media',\n id: AI_MEDIA_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Media library',\n note: aiMediaOutputNote(written.done, notDone, written.altChanged),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobMediaStep = createAiJobMediaStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiMediaJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A library change is admitted on a site of the job's own org, for a member\n * who may write its library now, where the request names files and a change\n * this can make. Refused in those words before anything is written.\n */\nexport function createAiMediaJobAdmission(deps: AiMediaJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_MEDIA_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiMediaAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiMediaRequestOf(context.inputs as Record<string, unknown>)\n return aiMediaRequestEmpty(read.request) ? { status: 400, error: [AI_MEDIA_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiMediaJobAdmission = createAiMediaJobAdmission()\n\n/** Registers the media library step and its door (AGL-3752). */\nexport function registerAiMediaJob(): void {\n registerAiJobStep(AI_MEDIA_KIND, runAiJobMediaStep, { minimumMs: AI_JOB_MEDIA_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_MEDIA_KIND, aiMediaJobAdmission)\n}\n"],"names":["createResourceUid","holdsSiteKey","orgPermissionLabel","MEDIA_ALT_MAX_LENGTH","embeddedMetadataIsCurrent","MEDIA_FOLDER_MAX_DEPTH","MEDIA_FOLDER_NAME_MAX_LENGTH","newMediaFolderDoc","normalizeFolderName","MEDIA_TAG_MAX_COUNT","MEDIA_TAG_MAX_LENGTH","mediaFilterKeys","mediaNameWords","normalizeMediaTags","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_MEDIA_KIND","AI_JOB_MEDIA_STEP_MINIMUM_MS","AI_MEDIA_NO_SITE_COPY","AI_MEDIA_ROLE_COPY","AI_MEDIA_LOCKED_COPY","AI_MEDIA_NOTHING_COPY","AI_MEDIA_WHERE","AI_MEDIA_OUTPUT_ID","AI_MEDIA_LIMITS","files","fileName","alt","tags","tag","folder","AI_MEDIA_ARGS_SCHEMA","type","properties","description","items","maxLength","maxItems","missingAlt","addTags","removeTags","rename","altFromDetails","additionalProperties","text","value","replace","trim","list","Array","isArray","map","filter","Boolean","aiMediaFolderPathOf","names","split","part","length","error","normalized","name","one","push","EMPTY_ALT","aiMediaAltProblem","test","folded","toLowerCase","base","aiMediaRequestOf","inputs","problems","Set","slice","request","path","aiMediaRequestEmpty","selects","changes","undefined","fileNameOf","row","String","data","id","isImage","startsWith","aiMediaFileOf","source","rows","named","live","byId","find","aiMediaAltFromDetails","candidates","embedded","byKey","Map","fields","field","key","get","candidate","aiMediaFolderPlan","folders","parentId","index","found","create","offset","parent","made","existingId","aiMediaFileChange","update","said","notDone","removed","next","has","dropped","includes","join","JSON","stringify","storedAlt","problem","Object","assign","ID_SHAPE","writeAiMediaChanges","firestore","input","hostRef","collection","doc","hostId","mediaRef","foldersRef","runTransaction","tx","host","exists","ok","uid","read","byName","where","limit","docs","some","set","missing","size","stored","madeFolders","plan","ref","createdAt","serverTimestamp","visibleTo","done","changedFiles","altChanged","values","change","keys","updatedAt","now","aiMediaAccessRefusal","deps","locked","lockdown","staff","org","intent","aiMediaOutputNote","did","placed","not","createAiJobMediaStep","write","job","modelFor","model","unspent","failure","orgData","orgId","refused","createdBy","review","written","output","resource","hostSubdomain","label","note","outputs","runAiJobMediaStep","createAiMediaJobAdmission","ownerOf","context","status","owner","aiMediaJobAdmission","registerAiMediaJob","minimumMs"],"mappings":"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"}
|
|
@@ -64,7 +64,8 @@ export declare const AI_NAVIGATION_KIND: "navigation";
|
|
|
64
64
|
*/
|
|
65
65
|
export declare const AI_JOB_NAVIGATION_STEP_MINIMUM_MS = 5000;
|
|
66
66
|
export declare const AI_NAVIGATION_NO_SITE_COPY = "Open the site whose menu should change.";
|
|
67
|
-
|
|
67
|
+
/** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */
|
|
68
|
+
export declare const AI_NAVIGATION_ROLE_COPY: string;
|
|
68
69
|
export declare const AI_NAVIGATION_LOCKED_COPY = "This site is not accepting changes right now.";
|
|
69
70
|
export declare const AI_NAVIGATION_NO_LAYOUT_COPY = "This site has no layout with a header or footer to change. Make one in Layouts first.";
|
|
70
71
|
export declare const AI_NAVIGATION_NOTHING_COPY = "The request named no menu change this could make.";
|
|
@@ -14,8 +14,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
18
|
-
import {
|
|
17
|
+
*/ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
18
|
+
import { holdsSiteKey } from "@aglyn/aglyn/app-utils/host-permissions";
|
|
19
|
+
import { orgPermissionLabel } from "@aglyn/aglyn/app-utils/org-permissions";
|
|
19
20
|
import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
|
|
20
21
|
import { getLockdownVerdict } from "@aglyn/tenant-data-admin/server/lockdown";
|
|
21
22
|
import { resolveOrgIdForHost } from "@aglyn/tenant-data-admin/server/organizations";
|
|
@@ -69,7 +70,7 @@ import { registerAiJobStep } from "./ai-jobs.js";
|
|
|
69
70
|
* fits an inline door.
|
|
70
71
|
*/ export const AI_JOB_NAVIGATION_STEP_MINIMUM_MS = 5000;
|
|
71
72
|
export const AI_NAVIGATION_NO_SITE_COPY = 'Open the site whose menu should change.';
|
|
72
|
-
export const AI_NAVIGATION_ROLE_COPY =
|
|
73
|
+
/** Names "Edit site content" (`site.edit`, AGL-4047), never a role. */ export const AI_NAVIGATION_ROLE_COPY = `Changing the site’s menus needs the "${orgPermissionLabel('site.edit')}" permission on this site — ask an organization admin.`;
|
|
73
74
|
export const AI_NAVIGATION_LOCKED_COPY = 'This site is not accepting changes right now.';
|
|
74
75
|
export const AI_NAVIGATION_NO_LAYOUT_COPY = 'This site has no layout with a header or footer to change. Make one in Layouts first.';
|
|
75
76
|
export const AI_NAVIGATION_NOTHING_COPY = 'The request named no menu change this could make.';
|
|
@@ -150,9 +151,8 @@ function buildMadeOf(inputs) {
|
|
|
150
151
|
return aiBuildStoreLayoutOf(null, context.pages);
|
|
151
152
|
}
|
|
152
153
|
/** Why this member may not change this site's menus now, or `null`. */ export async function aiNavigationAccessRefusal(input, deps = {}) {
|
|
153
|
-
var
|
|
154
|
-
|
|
155
|
-
if (!hostRoleCanWrite(roles[input.uid])) return AI_NAVIGATION_ROLE_COPY;
|
|
154
|
+
var _deps_lockdown;
|
|
155
|
+
if (!holdsSiteKey(input.host, input.uid, 'site.edit')) return AI_NAVIGATION_ROLE_COPY;
|
|
156
156
|
const locked = await ((_deps_lockdown = deps.lockdown) != null ? _deps_lockdown : getLockdownVerdict)({
|
|
157
157
|
staff: false,
|
|
158
158
|
uid: input.uid,
|
|
@@ -178,7 +178,6 @@ function buildMadeOf(inputs) {
|
|
|
178
178
|
const sourceRef = docRef.collection('versions').doc(input.sourceVersionId);
|
|
179
179
|
const newRef = docRef.collection('versions').doc(input.versionId);
|
|
180
180
|
return firestore.runTransaction(async (tx)=>{
|
|
181
|
-
var _host_get;
|
|
182
181
|
const [host, doc, source, already] = await Promise.all([
|
|
183
182
|
tx.get(hostRef),
|
|
184
183
|
tx.get(docRef),
|
|
@@ -199,8 +198,7 @@ function buildMadeOf(inputs) {
|
|
|
199
198
|
ok: false,
|
|
200
199
|
error: AI_EDIT_VERSION_GONE_COPY
|
|
201
200
|
};
|
|
202
|
-
|
|
203
|
-
if (!hostRoleCanWrite(roles[input.uid])) return {
|
|
201
|
+
if (!holdsSiteKey(host, input.uid, 'site.edit')) return {
|
|
204
202
|
ok: false,
|
|
205
203
|
error: AI_NAVIGATION_ROLE_COPY
|
|
206
204
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-navigation-step.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 { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport type { Firestore } from 'firebase-admin/firestore'\nimport { AI_EDIT_GONE_COPY, AI_EDIT_NEEDS_VERSIONING_COPY, AI_EDIT_NO_NODES_COPY, AI_EDIT_VERSION_GONE_COPY } from '../model/ai-edit-job'\nimport { isAiPlanNewRef } from '../model/ai-build-plan'\nimport { AI_BUILD_MADE_INPUT, type AiBuildMade } from '../model/ai-build-job'\nimport { aiBuildStoreLayoutOf } from '../model/ai-build-store-pages'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n aiLayoutWithMenuChanges,\n aiMenuChangesEmpty,\n aiMenuChangesOf,\n aiMenusOf,\n type AiMenuChanges,\n type AiMenuName,\n type AiMenuTarget,\n} from '../model/ai-layout-menus'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiEditPlacement, aiEditVersionId, aiJobEditModel, readAiEditTarget } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `navigation` step (AGL-3728): a change to a site's header or footer\n * menu — add, take out, rename, relink or reorder its items — as a\n * first-class build operation, so Assist's one plan card can say \"add About\n * and Pricing to the menu, and take Blog out\" and have it done.\n *\n * NO MODEL. The planner already wrote the change as the item's arguments, in\n * words (`aiMenuChangesOf`); this applies it to the layout's own Besigner\n * elements in code (`aiLayoutWithMenuChanges`), so it costs no credits and\n * cannot invent a link: every destination is one of the site's pages, a page\n * this build made, a path on the site or an https address.\n *\n * ── Where it lands ───────────────────────────────────────────────────────\n *\n * As the `edit` step's change to a layout does: a NEW version beside the\n * layout's current one, which visitors keep seeing until a person publishes\n * the new one. A layout is always served, so without version history there is\n * nowhere for it to land and it is refused as a plan limit, charged nothing.\n * The version is named by the job's id, so a pass asked again finds what it\n * wrote.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The editor's own rule for a layout's versions (`canWriteHostContent` and\n * `hostWritesFrozen` in the Firestore rules): a member with a write role on\n * the site (admin, editor or author), while no lockdown freezes the site, its\n * org or the platform. Asked at the door, again before the write, and the\n * role once more inside the transaction that writes.\n *\n * ── Audit ────────────────────────────────────────────────────────────────\n *\n * The layout it wrote is the job's output, which the job machine records as\n * an `ai.job.output` activity row on the site, as every other operation's is.\n */\n\nexport const AI_NAVIGATION_KIND = 'navigation' as const\n\n/**\n * ASSUMED: the least time the step needs — the site's pages, the layout and\n * its version, the org, and the one transaction that writes. No model, so it\n * fits an inline door.\n */\nexport const AI_JOB_NAVIGATION_STEP_MINIMUM_MS = 5_000\n\nexport const AI_NAVIGATION_NO_SITE_COPY = 'Open the site whose menu should change.'\nexport const AI_NAVIGATION_ROLE_COPY = 'Changing the site’s menus needs the admin, editor or author role on this site.'\nexport const AI_NAVIGATION_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_NAVIGATION_NO_LAYOUT_COPY = 'This site has no layout with a header or footer to change. Make one in Layouts first.'\nexport const AI_NAVIGATION_NOTHING_COPY = 'The request named no menu change this could make.'\n\n/** The name the new version carries in the layout's versions. */\nexport const AI_NAVIGATION_VERSION_NAME = 'Menu changes'\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** A path on the site, as a menu item may link it. */\nconst SITE_PATH = /^\\/(?:[A-Za-z0-9._~!$&'()*+,;=:@%-]+(?:\\/[A-Za-z0-9._~!$&'()*+,;=:@%-]+)*)?\\/?(?:[?#][^\\s]*)?$/\nconst SCREEN_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nfunction buildMadeOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiBuildMade {\n const raw = (inputs?.[AI_BUILD_MADE_INPUT] ?? {}) as Partial<Record<'pages' | 'layouts', unknown>>\n const rows = (value: unknown, name: 'title' | 'name') =>\n (Array.isArray(value) ? value : []).flatMap((entry) => {\n const row = (entry ?? {}) as Record<string, unknown>\n return text(row[name]) && SCREEN_ID.test(text(row['id'])) ? [{ [name]: text(row[name]), id: text(row['id']) }] : []\n })\n return { pages: rows(raw.pages, 'title') as AiBuildMade['pages'], layouts: rows(raw.layouts, 'name') as AiBuildMade['layouts'] }\n}\n\n/** The site's pages a menu may link, by id, with the layout each renders inside. */\nexport interface AiNavigationSitePage {\n id: string\n title: string\n slug: string\n template: boolean\n layoutId: string | null\n}\n\n/** The site's pages: not deleted, not an email design. */\nexport async function readAiNavigationSitePages(firestore: Firestore, hostId: string): Promise<AiNavigationSitePage[]> {\n const snapshot = await firestore.collection('hosts').doc(hostId).collection('screens').select('displayName', 'slug', 'kind', 'deletedAt', 'layoutId').get()\n return snapshot.docs.flatMap((doc) => {\n const kind = doc.get('kind')\n if (doc.get('deletedAt') || (kind && kind !== 'template')) return []\n const layoutId = doc.get('layoutId')\n return [\n {\n id: doc.id,\n slug: String(doc.get('slug') ?? ''),\n title: String(doc.get('displayName') ?? ''),\n template: kind === 'template',\n layoutId: typeof layoutId === 'string' && layoutId ? layoutId : null,\n },\n ]\n })\n}\n\n/**\n * What a destination in an item's words links (AGL-3728): a page this build\n * made (`new:<title>`), one of the site's pages by id (`page:<id>` or the bare\n * id), a path on the site, or an https address; `null` for anything else.\n */\nexport function aiNavigationTarget(\n destination: string,\n context: { pages: readonly Pick<AiNavigationSitePage, 'id' | 'template'>[]; made: AiBuildMade },\n): AiMenuTarget | null {\n const value = destination.trim()\n const pageIds = new Set(context.pages.filter((page) => !page.template).map((page) => page.id))\n if (isAiPlanNewRef(value as string | null)) {\n const title = value.slice(value.indexOf(':') + 1).trim().toLowerCase()\n const made = context.made.pages.find((page) => page.title.trim().toLowerCase() === title)\n // A page this build was to make and did not is no destination.\n return made && pageIds.has(made.id) ? { screenId: made.id } : null\n }\n const id = value.replace(/^page:/i, '')\n if (pageIds.has(id)) return { screenId: id }\n if (value.startsWith('/') && !value.startsWith('//') && SITE_PATH.test(value)) return { href: value }\n if (/^https:\\/\\/[^\\s/?#]+\\.[^\\s/?#]+(?:[/?#]\\S*)?$/i.test(value)) return { href: value }\n return null\n}\n\n/** The layout an item changes: the one it names, else the one the site's pages render inside. */\nexport function aiNavigationLayoutOf(\n named: string,\n context: { pages: readonly AiNavigationSitePage[]; made: AiBuildMade },\n): string | null {\n if (isAiPlanNewRef(named as string | null)) {\n const name = named.slice(named.indexOf(':') + 1).trim().toLowerCase()\n return context.made.layouts.find((layout) => layout.name.trim().toLowerCase() === name)?.id ?? null\n }\n if (SCREEN_ID.test(named)) return named\n return aiBuildStoreLayoutOf(null, context.pages)\n}\n\n/** Why this member may not change this site's menus now, or `null`. */\nexport async function aiNavigationAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_NAVIGATION_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_NAVIGATION_LOCKED_COPY : null\n}\n\n/** The new version's output note: what was done, what was not, and that it waits to be published. */\nexport function aiNavigationOutputNote(done: readonly string[], notDone: readonly string[]): string {\n const did = done.length ? `${done.join(' ')} ` : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}Saved as a new version of the layout beside the one visitors see: open it to review it, and publish it when it is ready.${not}`.slice(0, 1_000)\n}\n\nexport type AiNavigationWrite =\n | { ok: true; versionId: string; done: string[]; notDone: string[] }\n | { ok: false; error: string }\n\n/**\n * The change written, in one transaction that reads the layout as it is\n * stored NOW: it still exists, its version is still there, the member still\n * holds a write role, and the plan still keeps version history. The change is\n * applied again to that version's nodes, so a save made since is kept.\n */\nexport async function writeAiNavigation(\n firestore: Firestore,\n input: {\n hostId: string\n layoutId: string\n sourceVersionId: string\n versionId: string\n menus: readonly AiMenuName[]\n changes: AiMenuChanges\n uid: string\n aiJobId: string\n versioning: boolean\n now: Date\n },\n): Promise<AiNavigationWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const docRef = hostRef.collection('layouts').doc(input.layoutId)\n const sourceRef = docRef.collection('versions').doc(input.sourceVersionId)\n const newRef = docRef.collection('versions').doc(input.versionId)\n return firestore.runTransaction(async (tx): Promise<AiNavigationWrite> => {\n const [host, doc, source, already] = await Promise.all([tx.get(hostRef), tx.get(docRef), tx.get(sourceRef), tx.get(newRef)])\n if (already.exists) return { ok: true, versionId: input.versionId, done: [], notDone: [] }\n if (!doc.exists || doc.get('deletedAt') != null) return { ok: false, error: AI_EDIT_GONE_COPY }\n if (!source.exists) return { ok: false, error: AI_EDIT_VERSION_GONE_COPY }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_NAVIGATION_ROLE_COPY }\n const pointer = text(doc.get('versionId')) || null\n const placement = aiEditPlacement({\n versioning: input.versioning,\n served: true,\n sourceIsPointer: pointer === input.sourceVersionId,\n sourceScheduled: false,\n })\n if (placement === 'refused') return { ok: false, error: AI_EDIT_NEEDS_VERSIONING_COPY }\n const nodes = decodeStoredNodes<Record<string, unknown>>(source.get('nodes'))\n if (!nodes) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n const applied = aiLayoutWithMenuChanges(nodes, input.menus, input.changes)\n if (!applied.nodes) return { ok: false, error: applied.notDone.join(' ') || AI_NAVIGATION_NOTHING_COPY }\n const packed = encodeStoredNodes(applied.nodes as never)\n if (!packed) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n // A copy of the version it started from — everything the editor stored —\n // with the changed menus, as the versions route copies one.\n const copy = { ...(source.data() as Record<string, unknown>) }\n delete copy['aiEditJobId']\n tx.create(newRef, {\n ...copy,\n nodes: Buffer.from(packed),\n displayName: AI_NAVIGATION_VERSION_NAME,\n createdAt: input.now,\n updatedAt: input.now,\n createdBy: input.uid,\n aiJobId: input.aiJobId,\n })\n return { ok: true, versionId: input.versionId, done: applied.done, notDone: applied.notDone }\n })\n}\n\nexport interface AiJobNavigationStepDeps {\n readTarget?: typeof readAiEditTarget\n readPages?: typeof readAiNavigationSitePages\n write?: typeof writeAiNavigation\n lockdown?: typeof getLockdownVerdict\n}\n\n/** What the step decided before writing: the layout, its version, the change and what was read away. */\ninterface AiNavigationPlan {\n layoutId: string\n menus: AiMenuName[]\n changes: AiMenuChanges\n problems: string[]\n}\n\nasync function navigationPlanOf(\n firestore: Firestore,\n job: Pick<AiJob, 'hostId' | 'inputs'>,\n readPages: typeof readAiNavigationSitePages,\n): Promise<AiNavigationPlan | string> {\n if (!job.hostId) return AI_NAVIGATION_NO_SITE_COPY\n const inputs = job.inputs ?? {}\n const pages = await readPages(firestore, job.hostId)\n const made = buildMadeOf(inputs)\n const layoutId = aiNavigationLayoutOf(text(inputs['layout']), { pages, made })\n if (!layoutId) return AI_NAVIGATION_NO_LAYOUT_COPY\n const read = aiMenuChangesOf(inputs, (destination) => aiNavigationTarget(destination, { pages, made }))\n if (aiMenuChangesEmpty(read.changes)) return [AI_NAVIGATION_NOTHING_COPY, ...read.problems].join(' ')\n return { layoutId, menus: aiMenusOf(inputs['menu']), changes: read.changes, problems: read.problems }\n}\n\nexport function createAiJobNavigationStep(deps: AiJobNavigationStepDeps = {}): AiJobStepRunner {\n const readTarget = deps.readTarget ?? readAiEditTarget\n const readPages = deps.readPages ?? readAiNavigationSitePages\n const write = deps.write ?? writeAiNavigation\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_NAVIGATION_NO_SITE_COPY)\n const plan = await navigationPlanOf(firestore, job, readPages)\n if (typeof plan === 'string') return unspent(plan)\n const versionId = aiEditVersionId(job)\n const read = await readTarget(firestore, {\n orgId: job.orgId,\n hostId,\n inputs: { target: plan.layoutId, targetKind: 'layout', versionId: null },\n jobId: versionId,\n })\n if ('error' in read) return unspent(read.error)\n const output = (done: readonly string[], notDone: readonly string[]): AiJobOutput => ({\n resource: 'layout',\n id: read.id,\n versionId,\n hostId,\n hostSubdomain: read.hostSubdomain,\n label: read.name,\n note: aiNavigationOutputNote(done, notDone),\n })\n if (read.written) return aiUnspentOutcome(model, { outputs: [output([], [])] })\n const orgData = org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiNavigationAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const versioning = checkEntitlement(orgData as never, 'versioning')\n const placement = aiEditPlacement({\n versioning,\n served: true,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n if (placement === 'refused') return aiUnspentOutcome(model, { review: aiLimitReview(AI_EDIT_NEEDS_VERSIONING_COPY) })\n const written = await write(firestore, {\n hostId,\n layoutId: read.id,\n sourceVersionId: read.sourceVersionId,\n versionId,\n menus: plan.menus,\n changes: plan.changes,\n uid: job.createdBy,\n aiJobId: aiOriginJobId(job),\n versioning,\n now,\n })\n if (written.ok === false) return unspent(written.error)\n return aiUnspentOutcome(model, { outputs: [output(written.done, [...written.notDone, ...plan.problems])] })\n }\n}\n\nexport const runAiJobNavigationStep = createAiJobNavigationStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiNavigationJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n readTarget?: typeof readAiEditTarget\n readPages?: typeof readAiNavigationSitePages\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A navigation change is admitted on a site of the job's own org, for a\n * member who may write its content now, where the site has a layout and the\n * change has somewhere to land that no visitor sees on this plan. Refused in\n * those words before anything exists.\n */\nexport function createAiNavigationJobAdmission(deps: AiNavigationJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n const readTarget = deps.readTarget ?? readAiEditTarget\n const readPages = deps.readPages ?? readAiNavigationSitePages\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_NAVIGATION_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiNavigationAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const plan = await navigationPlanOf(context.firestore as Firestore, { hostId: context.hostId, inputs: context.inputs as Record<string, unknown> }, readPages)\n if (typeof plan === 'string') return { status: 400, error: plan }\n const read = await readTarget(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n inputs: { target: plan.layoutId, targetKind: 'layout', versionId: null },\n jobId: null,\n })\n if ('error' in read) return read\n const placement = aiEditPlacement({\n versioning: checkEntitlement(context.org as never, 'versioning'),\n served: true,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n return placement === 'refused' ? { status: 403, error: AI_EDIT_NEEDS_VERSIONING_COPY } : null\n }\n}\n\nexport const aiNavigationJobAdmission = createAiNavigationJobAdmission()\n\n/** Registers the navigation step and its door (AGL-3728). */\nexport function registerAiNavigationJob(): void {\n registerAiJobStep(AI_NAVIGATION_KIND, runAiJobNavigationStep, { minimumMs: AI_JOB_NAVIGATION_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_NAVIGATION_KIND, aiNavigationJobAdmission)\n}\n"],"names":["hostRoleCanWrite","checkEntitlement","decodeStoredNodes","encodeStoredNodes","getLockdownVerdict","resolveOrgIdForHost","AI_EDIT_GONE_COPY","AI_EDIT_NEEDS_VERSIONING_COPY","AI_EDIT_NO_NODES_COPY","AI_EDIT_VERSION_GONE_COPY","isAiPlanNewRef","AI_BUILD_MADE_INPUT","aiBuildStoreLayoutOf","aiLayoutWithMenuChanges","aiMenuChangesEmpty","aiMenuChangesOf","aiMenusOf","registerAiJobAdmission","aiOriginJobId","aiEditPlacement","aiEditVersionId","aiJobEditModel","readAiEditTarget","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_NAVIGATION_KIND","AI_JOB_NAVIGATION_STEP_MINIMUM_MS","AI_NAVIGATION_NO_SITE_COPY","AI_NAVIGATION_ROLE_COPY","AI_NAVIGATION_LOCKED_COPY","AI_NAVIGATION_NO_LAYOUT_COPY","AI_NAVIGATION_NOTHING_COPY","AI_NAVIGATION_VERSION_NAME","text","value","trim","SITE_PATH","SCREEN_ID","buildMadeOf","inputs","raw","rows","name","Array","isArray","flatMap","entry","row","test","id","pages","layouts","readAiNavigationSitePages","firestore","hostId","snapshot","collection","doc","select","get","docs","kind","layoutId","slug","String","title","template","aiNavigationTarget","destination","context","pageIds","Set","filter","page","map","slice","indexOf","toLowerCase","made","find","has","screenId","replace","startsWith","href","aiNavigationLayoutOf","named","layout","aiNavigationAccessRefusal","input","deps","roles","host","uid","locked","lockdown","staff","org","data","intent","aiNavigationOutputNote","done","notDone","did","length","join","not","writeAiNavigation","hostRef","docRef","sourceRef","sourceVersionId","newRef","versionId","runTransaction","tx","source","already","Promise","all","exists","ok","error","pointer","placement","versioning","served","sourceIsPointer","sourceScheduled","nodes","applied","menus","changes","packed","copy","create","Buffer","from","displayName","createdAt","now","updatedAt","createdBy","aiJobId","navigationPlanOf","job","readPages","read","problems","createAiJobNavigationStep","readTarget","write","modelFor","model","unspent","failure","plan","orgId","target","targetKind","jobId","output","resource","hostSubdomain","label","note","written","outputs","orgData","refused","review","pointerVersionId","scheduledVersionId","runAiJobNavigationStep","createAiNavigationJobAdmission","ownerOf","status","owner","aiNavigationJobAdmission","registerAiNavigationJob","minimumMs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-navigation-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { holdsSiteKey } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { orgPermissionLabel } from '@aglyn/aglyn/app-utils/org-permissions'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport type { Firestore } from 'firebase-admin/firestore'\nimport { AI_EDIT_GONE_COPY, AI_EDIT_NEEDS_VERSIONING_COPY, AI_EDIT_NO_NODES_COPY, AI_EDIT_VERSION_GONE_COPY } from '../model/ai-edit-job'\nimport { isAiPlanNewRef } from '../model/ai-build-plan'\nimport { AI_BUILD_MADE_INPUT, type AiBuildMade } from '../model/ai-build-job'\nimport { aiBuildStoreLayoutOf } from '../model/ai-build-store-pages'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n aiLayoutWithMenuChanges,\n aiMenuChangesEmpty,\n aiMenuChangesOf,\n aiMenusOf,\n type AiMenuChanges,\n type AiMenuName,\n type AiMenuTarget,\n} from '../model/ai-layout-menus'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiEditPlacement, aiEditVersionId, aiJobEditModel, readAiEditTarget } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `navigation` step (AGL-3728): a change to a site's header or footer\n * menu — add, take out, rename, relink or reorder its items — as a\n * first-class build operation, so Assist's one plan card can say \"add About\n * and Pricing to the menu, and take Blog out\" and have it done.\n *\n * NO MODEL. The planner already wrote the change as the item's arguments, in\n * words (`aiMenuChangesOf`); this applies it to the layout's own Besigner\n * elements in code (`aiLayoutWithMenuChanges`), so it costs no credits and\n * cannot invent a link: every destination is one of the site's pages, a page\n * this build made, a path on the site or an https address.\n *\n * ── Where it lands ───────────────────────────────────────────────────────\n *\n * As the `edit` step's change to a layout does: a NEW version beside the\n * layout's current one, which visitors keep seeing until a person publishes\n * the new one. A layout is always served, so without version history there is\n * nowhere for it to land and it is refused as a plan limit, charged nothing.\n * The version is named by the job's id, so a pass asked again finds what it\n * wrote.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The editor's own rule for a layout's versions (`canWriteHostContent` and\n * `hostWritesFrozen` in the Firestore rules): a member with a write role on\n * the site (admin, editor or author), while no lockdown freezes the site, its\n * org or the platform. Asked at the door, again before the write, and the\n * role once more inside the transaction that writes.\n *\n * ── Audit ────────────────────────────────────────────────────────────────\n *\n * The layout it wrote is the job's output, which the job machine records as\n * an `ai.job.output` activity row on the site, as every other operation's is.\n */\n\nexport const AI_NAVIGATION_KIND = 'navigation' as const\n\n/**\n * ASSUMED: the least time the step needs — the site's pages, the layout and\n * its version, the org, and the one transaction that writes. No model, so it\n * fits an inline door.\n */\nexport const AI_JOB_NAVIGATION_STEP_MINIMUM_MS = 5_000\n\nexport const AI_NAVIGATION_NO_SITE_COPY = 'Open the site whose menu should change.'\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const AI_NAVIGATION_ROLE_COPY = `Changing the site’s menus needs the \"${orgPermissionLabel('site.edit')}\" permission on this site — ask an organization admin.`\nexport const AI_NAVIGATION_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_NAVIGATION_NO_LAYOUT_COPY = 'This site has no layout with a header or footer to change. Make one in Layouts first.'\nexport const AI_NAVIGATION_NOTHING_COPY = 'The request named no menu change this could make.'\n\n/** The name the new version carries in the layout's versions. */\nexport const AI_NAVIGATION_VERSION_NAME = 'Menu changes'\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** A path on the site, as a menu item may link it. */\nconst SITE_PATH = /^\\/(?:[A-Za-z0-9._~!$&'()*+,;=:@%-]+(?:\\/[A-Za-z0-9._~!$&'()*+,;=:@%-]+)*)?\\/?(?:[?#][^\\s]*)?$/\nconst SCREEN_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nfunction buildMadeOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiBuildMade {\n const raw = (inputs?.[AI_BUILD_MADE_INPUT] ?? {}) as Partial<Record<'pages' | 'layouts', unknown>>\n const rows = (value: unknown, name: 'title' | 'name') =>\n (Array.isArray(value) ? value : []).flatMap((entry) => {\n const row = (entry ?? {}) as Record<string, unknown>\n return text(row[name]) && SCREEN_ID.test(text(row['id'])) ? [{ [name]: text(row[name]), id: text(row['id']) }] : []\n })\n return { pages: rows(raw.pages, 'title') as AiBuildMade['pages'], layouts: rows(raw.layouts, 'name') as AiBuildMade['layouts'] }\n}\n\n/** The site's pages a menu may link, by id, with the layout each renders inside. */\nexport interface AiNavigationSitePage {\n id: string\n title: string\n slug: string\n template: boolean\n layoutId: string | null\n}\n\n/** The site's pages: not deleted, not an email design. */\nexport async function readAiNavigationSitePages(firestore: Firestore, hostId: string): Promise<AiNavigationSitePage[]> {\n const snapshot = await firestore.collection('hosts').doc(hostId).collection('screens').select('displayName', 'slug', 'kind', 'deletedAt', 'layoutId').get()\n return snapshot.docs.flatMap((doc) => {\n const kind = doc.get('kind')\n if (doc.get('deletedAt') || (kind && kind !== 'template')) return []\n const layoutId = doc.get('layoutId')\n return [\n {\n id: doc.id,\n slug: String(doc.get('slug') ?? ''),\n title: String(doc.get('displayName') ?? ''),\n template: kind === 'template',\n layoutId: typeof layoutId === 'string' && layoutId ? layoutId : null,\n },\n ]\n })\n}\n\n/**\n * What a destination in an item's words links (AGL-3728): a page this build\n * made (`new:<title>`), one of the site's pages by id (`page:<id>` or the bare\n * id), a path on the site, or an https address; `null` for anything else.\n */\nexport function aiNavigationTarget(\n destination: string,\n context: { pages: readonly Pick<AiNavigationSitePage, 'id' | 'template'>[]; made: AiBuildMade },\n): AiMenuTarget | null {\n const value = destination.trim()\n const pageIds = new Set(context.pages.filter((page) => !page.template).map((page) => page.id))\n if (isAiPlanNewRef(value as string | null)) {\n const title = value.slice(value.indexOf(':') + 1).trim().toLowerCase()\n const made = context.made.pages.find((page) => page.title.trim().toLowerCase() === title)\n // A page this build was to make and did not is no destination.\n return made && pageIds.has(made.id) ? { screenId: made.id } : null\n }\n const id = value.replace(/^page:/i, '')\n if (pageIds.has(id)) return { screenId: id }\n if (value.startsWith('/') && !value.startsWith('//') && SITE_PATH.test(value)) return { href: value }\n if (/^https:\\/\\/[^\\s/?#]+\\.[^\\s/?#]+(?:[/?#]\\S*)?$/i.test(value)) return { href: value }\n return null\n}\n\n/** The layout an item changes: the one it names, else the one the site's pages render inside. */\nexport function aiNavigationLayoutOf(\n named: string,\n context: { pages: readonly AiNavigationSitePage[]; made: AiBuildMade },\n): string | null {\n if (isAiPlanNewRef(named as string | null)) {\n const name = named.slice(named.indexOf(':') + 1).trim().toLowerCase()\n return context.made.layouts.find((layout) => layout.name.trim().toLowerCase() === name)?.id ?? null\n }\n if (SCREEN_ID.test(named)) return named\n return aiBuildStoreLayoutOf(null, context.pages)\n}\n\n/** Why this member may not change this site's menus now, or `null`. */\nexport async function aiNavigationAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!holdsSiteKey(input.host, input.uid, 'site.edit')) return AI_NAVIGATION_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_NAVIGATION_LOCKED_COPY : null\n}\n\n/** The new version's output note: what was done, what was not, and that it waits to be published. */\nexport function aiNavigationOutputNote(done: readonly string[], notDone: readonly string[]): string {\n const did = done.length ? `${done.join(' ')} ` : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}Saved as a new version of the layout beside the one visitors see: open it to review it, and publish it when it is ready.${not}`.slice(0, 1_000)\n}\n\nexport type AiNavigationWrite =\n | { ok: true; versionId: string; done: string[]; notDone: string[] }\n | { ok: false; error: string }\n\n/**\n * The change written, in one transaction that reads the layout as it is\n * stored NOW: it still exists, its version is still there, the member still\n * holds a write role, and the plan still keeps version history. The change is\n * applied again to that version's nodes, so a save made since is kept.\n */\nexport async function writeAiNavigation(\n firestore: Firestore,\n input: {\n hostId: string\n layoutId: string\n sourceVersionId: string\n versionId: string\n menus: readonly AiMenuName[]\n changes: AiMenuChanges\n uid: string\n aiJobId: string\n versioning: boolean\n now: Date\n },\n): Promise<AiNavigationWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const docRef = hostRef.collection('layouts').doc(input.layoutId)\n const sourceRef = docRef.collection('versions').doc(input.sourceVersionId)\n const newRef = docRef.collection('versions').doc(input.versionId)\n return firestore.runTransaction(async (tx): Promise<AiNavigationWrite> => {\n const [host, doc, source, already] = await Promise.all([tx.get(hostRef), tx.get(docRef), tx.get(sourceRef), tx.get(newRef)])\n if (already.exists) return { ok: true, versionId: input.versionId, done: [], notDone: [] }\n if (!doc.exists || doc.get('deletedAt') != null) return { ok: false, error: AI_EDIT_GONE_COPY }\n if (!source.exists) return { ok: false, error: AI_EDIT_VERSION_GONE_COPY }\n if (!holdsSiteKey(host, input.uid, 'site.edit')) return { ok: false, error: AI_NAVIGATION_ROLE_COPY }\n const pointer = text(doc.get('versionId')) || null\n const placement = aiEditPlacement({\n versioning: input.versioning,\n served: true,\n sourceIsPointer: pointer === input.sourceVersionId,\n sourceScheduled: false,\n })\n if (placement === 'refused') return { ok: false, error: AI_EDIT_NEEDS_VERSIONING_COPY }\n const nodes = decodeStoredNodes<Record<string, unknown>>(source.get('nodes'))\n if (!nodes) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n const applied = aiLayoutWithMenuChanges(nodes, input.menus, input.changes)\n if (!applied.nodes) return { ok: false, error: applied.notDone.join(' ') || AI_NAVIGATION_NOTHING_COPY }\n const packed = encodeStoredNodes(applied.nodes as never)\n if (!packed) return { ok: false, error: AI_EDIT_NO_NODES_COPY }\n // A copy of the version it started from — everything the editor stored —\n // with the changed menus, as the versions route copies one.\n const copy = { ...(source.data() as Record<string, unknown>) }\n delete copy['aiEditJobId']\n tx.create(newRef, {\n ...copy,\n nodes: Buffer.from(packed),\n displayName: AI_NAVIGATION_VERSION_NAME,\n createdAt: input.now,\n updatedAt: input.now,\n createdBy: input.uid,\n aiJobId: input.aiJobId,\n })\n return { ok: true, versionId: input.versionId, done: applied.done, notDone: applied.notDone }\n })\n}\n\nexport interface AiJobNavigationStepDeps {\n readTarget?: typeof readAiEditTarget\n readPages?: typeof readAiNavigationSitePages\n write?: typeof writeAiNavigation\n lockdown?: typeof getLockdownVerdict\n}\n\n/** What the step decided before writing: the layout, its version, the change and what was read away. */\ninterface AiNavigationPlan {\n layoutId: string\n menus: AiMenuName[]\n changes: AiMenuChanges\n problems: string[]\n}\n\nasync function navigationPlanOf(\n firestore: Firestore,\n job: Pick<AiJob, 'hostId' | 'inputs'>,\n readPages: typeof readAiNavigationSitePages,\n): Promise<AiNavigationPlan | string> {\n if (!job.hostId) return AI_NAVIGATION_NO_SITE_COPY\n const inputs = job.inputs ?? {}\n const pages = await readPages(firestore, job.hostId)\n const made = buildMadeOf(inputs)\n const layoutId = aiNavigationLayoutOf(text(inputs['layout']), { pages, made })\n if (!layoutId) return AI_NAVIGATION_NO_LAYOUT_COPY\n const read = aiMenuChangesOf(inputs, (destination) => aiNavigationTarget(destination, { pages, made }))\n if (aiMenuChangesEmpty(read.changes)) return [AI_NAVIGATION_NOTHING_COPY, ...read.problems].join(' ')\n return { layoutId, menus: aiMenusOf(inputs['menu']), changes: read.changes, problems: read.problems }\n}\n\nexport function createAiJobNavigationStep(deps: AiJobNavigationStepDeps = {}): AiJobStepRunner {\n const readTarget = deps.readTarget ?? readAiEditTarget\n const readPages = deps.readPages ?? readAiNavigationSitePages\n const write = deps.write ?? writeAiNavigation\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_NAVIGATION_NO_SITE_COPY)\n const plan = await navigationPlanOf(firestore, job, readPages)\n if (typeof plan === 'string') return unspent(plan)\n const versionId = aiEditVersionId(job)\n const read = await readTarget(firestore, {\n orgId: job.orgId,\n hostId,\n inputs: { target: plan.layoutId, targetKind: 'layout', versionId: null },\n jobId: versionId,\n })\n if ('error' in read) return unspent(read.error)\n const output = (done: readonly string[], notDone: readonly string[]): AiJobOutput => ({\n resource: 'layout',\n id: read.id,\n versionId,\n hostId,\n hostSubdomain: read.hostSubdomain,\n label: read.name,\n note: aiNavigationOutputNote(done, notDone),\n })\n if (read.written) return aiUnspentOutcome(model, { outputs: [output([], [])] })\n const orgData = org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiNavigationAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const versioning = checkEntitlement(orgData as never, 'versioning')\n const placement = aiEditPlacement({\n versioning,\n served: true,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n if (placement === 'refused') return aiUnspentOutcome(model, { review: aiLimitReview(AI_EDIT_NEEDS_VERSIONING_COPY) })\n const written = await write(firestore, {\n hostId,\n layoutId: read.id,\n sourceVersionId: read.sourceVersionId,\n versionId,\n menus: plan.menus,\n changes: plan.changes,\n uid: job.createdBy,\n aiJobId: aiOriginJobId(job),\n versioning,\n now,\n })\n if (written.ok === false) return unspent(written.error)\n return aiUnspentOutcome(model, { outputs: [output(written.done, [...written.notDone, ...plan.problems])] })\n }\n}\n\nexport const runAiJobNavigationStep = createAiJobNavigationStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiNavigationJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n readTarget?: typeof readAiEditTarget\n readPages?: typeof readAiNavigationSitePages\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A navigation change is admitted on a site of the job's own org, for a\n * member who may write its content now, where the site has a layout and the\n * change has somewhere to land that no visitor sees on this plan. Refused in\n * those words before anything exists.\n */\nexport function createAiNavigationJobAdmission(deps: AiNavigationJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n const readTarget = deps.readTarget ?? readAiEditTarget\n const readPages = deps.readPages ?? readAiNavigationSitePages\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_NAVIGATION_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiNavigationAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const plan = await navigationPlanOf(context.firestore as Firestore, { hostId: context.hostId, inputs: context.inputs as Record<string, unknown> }, readPages)\n if (typeof plan === 'string') return { status: 400, error: plan }\n const read = await readTarget(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n inputs: { target: plan.layoutId, targetKind: 'layout', versionId: null },\n jobId: null,\n })\n if ('error' in read) return read\n const placement = aiEditPlacement({\n versioning: checkEntitlement(context.org as never, 'versioning'),\n served: true,\n sourceIsPointer: read.sourceVersionId === read.pointerVersionId,\n sourceScheduled: read.scheduledVersionId === read.sourceVersionId,\n })\n return placement === 'refused' ? { status: 403, error: AI_EDIT_NEEDS_VERSIONING_COPY } : null\n }\n}\n\nexport const aiNavigationJobAdmission = createAiNavigationJobAdmission()\n\n/** Registers the navigation step and its door (AGL-3728). */\nexport function registerAiNavigationJob(): void {\n registerAiJobStep(AI_NAVIGATION_KIND, runAiJobNavigationStep, { minimumMs: AI_JOB_NAVIGATION_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_NAVIGATION_KIND, aiNavigationJobAdmission)\n}\n"],"names":["checkEntitlement","holdsSiteKey","orgPermissionLabel","decodeStoredNodes","encodeStoredNodes","getLockdownVerdict","resolveOrgIdForHost","AI_EDIT_GONE_COPY","AI_EDIT_NEEDS_VERSIONING_COPY","AI_EDIT_NO_NODES_COPY","AI_EDIT_VERSION_GONE_COPY","isAiPlanNewRef","AI_BUILD_MADE_INPUT","aiBuildStoreLayoutOf","aiLayoutWithMenuChanges","aiMenuChangesEmpty","aiMenuChangesOf","aiMenusOf","registerAiJobAdmission","aiOriginJobId","aiEditPlacement","aiEditVersionId","aiJobEditModel","readAiEditTarget","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_NAVIGATION_KIND","AI_JOB_NAVIGATION_STEP_MINIMUM_MS","AI_NAVIGATION_NO_SITE_COPY","AI_NAVIGATION_ROLE_COPY","AI_NAVIGATION_LOCKED_COPY","AI_NAVIGATION_NO_LAYOUT_COPY","AI_NAVIGATION_NOTHING_COPY","AI_NAVIGATION_VERSION_NAME","text","value","trim","SITE_PATH","SCREEN_ID","buildMadeOf","inputs","raw","rows","name","Array","isArray","flatMap","entry","row","test","id","pages","layouts","readAiNavigationSitePages","firestore","hostId","snapshot","collection","doc","select","get","docs","kind","layoutId","slug","String","title","template","aiNavigationTarget","destination","context","pageIds","Set","filter","page","map","slice","indexOf","toLowerCase","made","find","has","screenId","replace","startsWith","href","aiNavigationLayoutOf","named","layout","aiNavigationAccessRefusal","input","deps","host","uid","locked","lockdown","staff","org","data","intent","aiNavigationOutputNote","done","notDone","did","length","join","not","writeAiNavigation","hostRef","docRef","sourceRef","sourceVersionId","newRef","versionId","runTransaction","tx","source","already","Promise","all","exists","ok","error","pointer","placement","versioning","served","sourceIsPointer","sourceScheduled","nodes","applied","menus","changes","packed","copy","create","Buffer","from","displayName","createdAt","now","updatedAt","createdBy","aiJobId","navigationPlanOf","job","readPages","read","problems","createAiJobNavigationStep","readTarget","write","modelFor","model","unspent","failure","plan","orgId","target","targetKind","jobId","output","resource","hostSubdomain","label","note","written","outputs","orgData","refused","review","pointerVersionId","scheduledVersionId","runAiJobNavigationStep","createAiNavigationJobAdmission","ownerOf","status","owner","aiNavigationJobAdmission","registerAiNavigationJob","minimumMs"],"mappings":"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"}
|
|
@@ -683,10 +683,16 @@ export const AI_JOB_PAGE_LANGUAGE_BUDGET = aiJobStepBudget({
|
|
|
683
683
|
];
|
|
684
684
|
// An attribute a rule has one answer for is settled, not asked of the model again (AGL-3660),
|
|
685
685
|
// and a link sent home for nothing is sent to the form or left out (AGL-3729).
|
|
686
|
+
// And on the last answer, a link that goes nowhere, as the check below reads it (AGL-4076).
|
|
686
687
|
let nodes = aiSettleUnrelatedScreenLinks(aiSettleMechanicalRules({
|
|
687
688
|
rootId: CANVAS_ROOT_ELEMENT_ID,
|
|
688
689
|
nodes: copy.nodes
|
|
689
|
-
}, 'page'
|
|
690
|
+
}, 'page', last ? _extends({}, context, {
|
|
691
|
+
scrollTargetIds: input.sectionIds,
|
|
692
|
+
ownScreenIds: input.targets.pageId ? [
|
|
693
|
+
input.targets.pageId
|
|
694
|
+
] : []
|
|
695
|
+
}) : undefined), 'page', _extends({}, context, {
|
|
690
696
|
ownScreenIds: input.targets.pageId ? [
|
|
691
697
|
input.targets.pageId
|
|
692
698
|
] : []
|