@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-duplicate-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 {\n DUPLICABLE_RESOURCE_NOUNS,\n type DuplicableHostResourceKind,\n} from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { duplicateResource, recipeFor } from '@aglyn/tenant-data-admin/server/duplicate-resource'\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 { isAiPlanNewRef } from '../model/ai-build-plan'\nimport type { AiJob, AiJobOutput, AiJobOutputResource } from '../model/ai-jobs.types'\nimport { parseAiDuplicateResourceInput, type AiDuplicateResourceInput } from '../tools/ai-duplicate-resource-tool'\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 `duplicate` step (AGL-3740): copying something the site already has —\n * a page, a component, a layout, a template, a form, an email design or a\n * workflow — as a first-class build operation, so \"make another page like\n * Services\" copies instead of regenerating.\n *\n * NO MODEL. The copy is core's `duplicateResource`, the very function the\n * console's Duplicate… row menus and `/api/hosts/resources` (`action:\n * 'duplicate'`) call, and the one the `duplicate_resource` tool wraps: the\n * same fields carried, the same uniqueness rules, the same plan counters met\n * inside the transaction, the same `*.duplicated` activity row. So it costs\n * no credits, and it can copy nothing the console could not.\n *\n * DRAFTS ONLY, by construction of the copy: a page copy has no address, a\n * workflow copy no trigger, and nothing places a component, layout or form\n * copy until a person does.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The console door's rule: a member with a write role on the site (admin,\n * editor or author), while no lockdown freezes the site, its org or the\n * platform. `ai.generate` is the jobs door's own check. Asked at the door\n * and again before the copy.\n *\n * ── Once per job ─────────────────────────────────────────────────────────\n *\n * The job's id is the copy's attempt key, so a pass asked again answers with\n * the copy the first made rather than making a second.\n */\n\nexport const AI_DUPLICATE_KIND = 'duplicate' as const\n\n/**\n * ASSUMED: the least time the step needs — the site, the org, the source's\n * collection when it is named by name, and the one transaction that copies.\n * No model, so it fits an inline door.\n */\nexport const AI_JOB_DUPLICATE_STEP_MINIMUM_MS = 5_000\n\nexport const AI_DUPLICATE_NO_SITE_COPY = 'Open the site whose content should be copied.'\nexport const AI_DUPLICATE_ROLE_COPY = 'Copying on this site needs the admin, editor or author role on it.'\nexport const AI_DUPLICATE_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_DUPLICATE_UNREADABLE_COPY = 'The request did not name something on this site to copy.'\nexport const AI_DUPLICATE_NOT_YET_COPY = 'Only something the site already has can be copied, not something this build is making.'\n\n/** The output resource each duplicable kind is opened as. */\nexport const AI_DUPLICATE_OUTPUT_RESOURCES: Readonly<Record<DuplicableHostResourceKind, AiJobOutputResource>> = {\n screen: 'screen',\n component: 'reusableComponent',\n layout: 'layout',\n template: 'template',\n form: 'form',\n emailDesign: 'emailScreen',\n workflow: 'workflow',\n}\n\n/** The copy's attempt key: one copy per job, however often it is asked. */\nexport function aiDuplicateAttemptKey(job: Pick<AiJob, '$id'>): string {\n return `ai-job:${job.$id}`.slice(0, 200)\n}\n\n/** What the plan's item asks, read; a refusal in words when it is unreadable. */\nexport function aiDuplicateRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiDuplicateResourceInput | string {\n const source = typeof inputs?.['source'] === 'string' ? inputs['source'].trim() : ''\n if (isAiPlanNewRef(source)) return AI_DUPLICATE_NOT_YET_COPY\n const name = typeof inputs?.['name'] === 'string' ? inputs['name'] : null\n return parseAiDuplicateResourceInput({ kind: inputs?.['kind'], sourceId: source, name }) ?? AI_DUPLICATE_UNREADABLE_COPY\n}\n\n/** One row of a kind's collection, as a source is looked up by. */\nexport interface AiDuplicateSourceRow {\n id: string\n name: string\n kind: unknown\n deleted: boolean\n}\n\n/** The rows of a kind's collection on a site. */\nexport async function readAiDuplicateSourceRows(\n firestore: Firestore,\n hostId: string,\n kind: DuplicableHostResourceKind,\n): Promise<AiDuplicateSourceRow[]> {\n const recipe = recipeFor(kind)\n if (!recipe) return []\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection(recipe.collection)\n .select(recipe.nameField, 'kind', 'deletedAt')\n .get()\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get(recipe.nameField) ?? ''),\n kind: doc.get('kind'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\n/** Whether a row of a shared collection is of this kind: pages and email designs share `screens`. */\nfunction rowIsKind(kind: DuplicableHostResourceKind, row: AiDuplicateSourceRow): boolean {\n if (kind === 'emailDesign') return row.kind === 'email'\n if (kind === 'screen') return row.kind !== 'email'\n return true\n}\n\n/**\n * The source's id: the row whose id it names, else the one row whose name it\n * is (case aside) — so \"copy the Welcome email\" works where the inventory\n * lists no email designs. Two rows of one name, or none, are refused in words.\n */\nexport function aiDuplicateSourceId(\n kind: DuplicableHostResourceKind,\n source: string,\n rows: readonly AiDuplicateSourceRow[],\n): { id: string } | { error: string } {\n const noun = DUPLICABLE_RESOURCE_NOUNS[kind]\n const live = rows.filter((row) => !row.deleted && rowIsKind(kind, row))\n if (live.some((row) => row.id === source)) return { id: source }\n const wanted = source.trim().toLowerCase()\n const named = live.filter((row) => row.name.trim().toLowerCase() === wanted)\n if (named.length === 1) return { id: named[0].id }\n if (named.length > 1) return { error: `This site has more than one ${noun} called “${source}”; name the one to copy.` }\n return { error: `This site has no ${noun} “${source}” to copy.` }\n}\n\n/** Why this member may not copy on this site now, or `null`. */\nexport async function aiDuplicateAccessRefusal(\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_DUPLICATE_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_DUPLICATE_LOCKED_COPY : null\n}\n\n/** The copy's output note: what it is and what it waits for. */\nexport function aiDuplicateOutputNote(kind: DuplicableHostResourceKind): string {\n const noun = DUPLICABLE_RESOURCE_NOUNS[kind]\n return `A copy of the ${noun}, saved as a draft: nothing on the live site uses it until you do.`\n}\n\nexport interface AiJobDuplicateStepDeps {\n duplicate?: typeof duplicateResource\n readRows?: typeof readAiDuplicateSourceRows\n lockdown?: typeof getLockdownVerdict\n}\n\n/** The request read and its source found, or a refusal in words. */\nasync function duplicatePlanOf(\n firestore: Firestore,\n job: Pick<AiJob, 'hostId' | 'inputs'>,\n readRows: typeof readAiDuplicateSourceRows,\n): Promise<(AiDuplicateResourceInput & { id: string }) | string> {\n if (!job.hostId) return AI_DUPLICATE_NO_SITE_COPY\n const request = aiDuplicateRequestOf(job.inputs)\n if (typeof request === 'string') return request\n const found = aiDuplicateSourceId(request.kind, request.sourceId, await readRows(firestore, job.hostId, request.kind))\n if ('error' in found) return found.error\n return { ...request, id: found.id }\n}\n\nexport function createAiJobDuplicateStep(deps: AiJobDuplicateStepDeps = {}): AiJobStepRunner {\n const duplicate = deps.duplicate ?? duplicateResource\n const readRows = deps.readRows ?? readAiDuplicateSourceRows\n return async ({ job, 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_DUPLICATE_NO_SITE_COPY)\n const plan = await duplicatePlanOf(firestore, job, readRows)\n if (typeof plan === 'string') return unspent(plan)\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 aiDuplicateAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const result = await duplicate(plan.kind, {\n orgId: job.orgId,\n hostId,\n sourceId: plan.id,\n name: plan.name,\n uid: job.createdBy,\n email: null,\n org: orgData,\n attemptKey: aiDuplicateAttemptKey(job),\n })\n if (result.ok === false) {\n // A plan counter or a feature the plan lacks is a limit to relay, not a fault.\n return result.status === 403 ? aiUnspentOutcome(model, { review: aiLimitReview(result.error) }) : unspent(result.error)\n }\n const output: AiJobOutput = {\n resource: AI_DUPLICATE_OUTPUT_RESOURCES[plan.kind],\n id: result.id,\n versionId: result.versionId,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: result.name,\n note: aiDuplicateOutputNote(plan.kind),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobDuplicateStep = createAiJobDuplicateStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiDuplicateJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n readRows?: typeof readAiDuplicateSourceRows\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A copy is admitted on a site of the job's own org, for a member who may\n * write its content now, of a source the site holds. Refused in those words\n * before anything exists; what the plan's counters allow is the copy's own\n * transaction to say.\n */\nexport function createAiDuplicateJobAdmission(deps: AiDuplicateJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n const readRows = deps.readRows ?? readAiDuplicateSourceRows\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_DUPLICATE_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 aiDuplicateAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const plan = await duplicatePlanOf(context.firestore as Firestore, { hostId: context.hostId, inputs: context.inputs as Record<string, unknown> }, readRows)\n return typeof plan === 'string' ? { status: 400, error: plan } : null\n }\n}\n\nexport const aiDuplicateJobAdmission = createAiDuplicateJobAdmission()\n\n/** Registers the duplicate step and its door (AGL-3740). */\nexport function registerAiDuplicateJob(): void {\n registerAiJobStep(AI_DUPLICATE_KIND, runAiJobDuplicateStep, { minimumMs: AI_JOB_DUPLICATE_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_DUPLICATE_KIND, aiDuplicateJobAdmission)\n}\n"],"names":["DUPLICABLE_RESOURCE_NOUNS","hostRoleCanWrite","duplicateResource","recipeFor","getLockdownVerdict","resolveOrgIdForHost","isAiPlanNewRef","parseAiDuplicateResourceInput","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_DUPLICATE_KIND","AI_JOB_DUPLICATE_STEP_MINIMUM_MS","AI_DUPLICATE_NO_SITE_COPY","AI_DUPLICATE_ROLE_COPY","AI_DUPLICATE_LOCKED_COPY","AI_DUPLICATE_UNREADABLE_COPY","AI_DUPLICATE_NOT_YET_COPY","AI_DUPLICATE_OUTPUT_RESOURCES","screen","component","layout","template","form","emailDesign","workflow","aiDuplicateAttemptKey","job","$id","slice","aiDuplicateRequestOf","inputs","source","trim","name","kind","sourceId","readAiDuplicateSourceRows","firestore","hostId","recipe","snapshot","collection","doc","select","nameField","get","docs","map","id","String","deleted","rowIsKind","row","aiDuplicateSourceId","rows","noun","live","filter","some","wanted","toLowerCase","named","length","error","aiDuplicateAccessRefusal","input","deps","host","exists","roles","uid","locked","lockdown","staff","org","data","intent","aiDuplicateOutputNote","duplicatePlanOf","readRows","request","found","createAiJobDuplicateStep","duplicate","modelFor","model","unspent","failure","plan","orgData","orgId","refused","createdBy","review","result","email","attemptKey","ok","status","output","resource","versionId","hostSubdomain","label","note","outputs","runAiJobDuplicateStep","createAiDuplicateJobAdmission","ownerOf","context","owner","aiDuplicateJobAdmission","registerAiDuplicateJob","minimumMs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-duplicate-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 { holdsSiteKey } from '@aglyn/aglyn/app-utils/host-permissions'\nimport { orgPermissionLabel } from '@aglyn/aglyn/app-utils/org-permissions'\nimport {\n DUPLICABLE_RESOURCE_NOUNS,\n type DuplicableHostResourceKind,\n} from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { duplicateResource, recipeFor } from '@aglyn/tenant-data-admin/server/duplicate-resource'\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 { isAiPlanNewRef } from '../model/ai-build-plan'\nimport type { AiJob, AiJobOutput, AiJobOutputResource } from '../model/ai-jobs.types'\nimport { parseAiDuplicateResourceInput, type AiDuplicateResourceInput } from '../tools/ai-duplicate-resource-tool'\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 `duplicate` step (AGL-3740): copying something the site already has —\n * a page, a component, a layout, a template, a form, an email design or a\n * workflow — as a first-class build operation, so \"make another page like\n * Services\" copies instead of regenerating.\n *\n * NO MODEL. The copy is core's `duplicateResource`, the very function the\n * console's Duplicate… row menus and `/api/hosts/resources` (`action:\n * 'duplicate'`) call, and the one the `duplicate_resource` tool wraps: the\n * same fields carried, the same uniqueness rules, the same plan counters met\n * inside the transaction, the same `*.duplicated` activity row. So it costs\n * no credits, and it can copy nothing the console could not.\n *\n * DRAFTS ONLY, by construction of the copy: a page copy has no address, a\n * workflow copy no trigger, and nothing places a component, layout or form\n * copy until a person does.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The console door's rule: a member with a write role on the site (admin,\n * editor or author), while no lockdown freezes the site, its org or the\n * platform. `ai.generate` is the jobs door's own check. Asked at the door\n * and again before the copy.\n *\n * ── Once per job ─────────────────────────────────────────────────────────\n *\n * The job's id is the copy's attempt key, so a pass asked again answers with\n * the copy the first made rather than making a second.\n */\n\nexport const AI_DUPLICATE_KIND = 'duplicate' as const\n\n/**\n * ASSUMED: the least time the step needs — the site, the org, the source's\n * collection when it is named by name, and the one transaction that copies.\n * No model, so it fits an inline door.\n */\nexport const AI_JOB_DUPLICATE_STEP_MINIMUM_MS = 5_000\n\nexport const AI_DUPLICATE_NO_SITE_COPY = 'Open the site whose content should be copied.'\n/** Names \"Edit site content\" (`site.edit`, AGL-4047), never a role. */\nexport const AI_DUPLICATE_ROLE_COPY = `Copying on this site needs the \"${orgPermissionLabel('site.edit')}\" permission on this site — ask an organization admin.`\nexport const AI_DUPLICATE_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_DUPLICATE_UNREADABLE_COPY = 'The request did not name something on this site to copy.'\nexport const AI_DUPLICATE_NOT_YET_COPY = 'Only something the site already has can be copied, not something this build is making.'\n\n/** The output resource each duplicable kind is opened as. */\nexport const AI_DUPLICATE_OUTPUT_RESOURCES: Readonly<Record<DuplicableHostResourceKind, AiJobOutputResource>> = {\n screen: 'screen',\n component: 'reusableComponent',\n layout: 'layout',\n template: 'template',\n form: 'form',\n emailDesign: 'emailScreen',\n workflow: 'workflow',\n}\n\n/** The copy's attempt key: one copy per job, however often it is asked. */\nexport function aiDuplicateAttemptKey(job: Pick<AiJob, '$id'>): string {\n return `ai-job:${job.$id}`.slice(0, 200)\n}\n\n/** What the plan's item asks, read; a refusal in words when it is unreadable. */\nexport function aiDuplicateRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiDuplicateResourceInput | string {\n const source = typeof inputs?.['source'] === 'string' ? inputs['source'].trim() : ''\n if (isAiPlanNewRef(source)) return AI_DUPLICATE_NOT_YET_COPY\n const name = typeof inputs?.['name'] === 'string' ? inputs['name'] : null\n return parseAiDuplicateResourceInput({ kind: inputs?.['kind'], sourceId: source, name }) ?? AI_DUPLICATE_UNREADABLE_COPY\n}\n\n/** One row of a kind's collection, as a source is looked up by. */\nexport interface AiDuplicateSourceRow {\n id: string\n name: string\n kind: unknown\n deleted: boolean\n}\n\n/** The rows of a kind's collection on a site. */\nexport async function readAiDuplicateSourceRows(\n firestore: Firestore,\n hostId: string,\n kind: DuplicableHostResourceKind,\n): Promise<AiDuplicateSourceRow[]> {\n const recipe = recipeFor(kind)\n if (!recipe) return []\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection(recipe.collection)\n .select(recipe.nameField, 'kind', 'deletedAt')\n .get()\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get(recipe.nameField) ?? ''),\n kind: doc.get('kind'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\n/** Whether a row of a shared collection is of this kind: pages and email designs share `screens`. */\nfunction rowIsKind(kind: DuplicableHostResourceKind, row: AiDuplicateSourceRow): boolean {\n if (kind === 'emailDesign') return row.kind === 'email'\n if (kind === 'screen') return row.kind !== 'email'\n return true\n}\n\n/**\n * The source's id: the row whose id it names, else the one row whose name it\n * is (case aside) — so \"copy the Welcome email\" works where the inventory\n * lists no email designs. Two rows of one name, or none, are refused in words.\n */\nexport function aiDuplicateSourceId(\n kind: DuplicableHostResourceKind,\n source: string,\n rows: readonly AiDuplicateSourceRow[],\n): { id: string } | { error: string } {\n const noun = DUPLICABLE_RESOURCE_NOUNS[kind]\n const live = rows.filter((row) => !row.deleted && rowIsKind(kind, row))\n if (live.some((row) => row.id === source)) return { id: source }\n const wanted = source.trim().toLowerCase()\n const named = live.filter((row) => row.name.trim().toLowerCase() === wanted)\n if (named.length === 1) return { id: named[0].id }\n if (named.length > 1) return { error: `This site has more than one ${noun} called “${source}”; name the one to copy.` }\n return { error: `This site has no ${noun} “${source}” to copy.` }\n}\n\n/** Why this member may not copy on this site now, or `null`. */\nexport async function aiDuplicateAccessRefusal(\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_DUPLICATE_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_DUPLICATE_LOCKED_COPY : null\n}\n\n/** The copy's output note: what it is and what it waits for. */\nexport function aiDuplicateOutputNote(kind: DuplicableHostResourceKind): string {\n const noun = DUPLICABLE_RESOURCE_NOUNS[kind]\n return `A copy of the ${noun}, saved as a draft: nothing on the live site uses it until you do.`\n}\n\nexport interface AiJobDuplicateStepDeps {\n duplicate?: typeof duplicateResource\n readRows?: typeof readAiDuplicateSourceRows\n lockdown?: typeof getLockdownVerdict\n}\n\n/** The request read and its source found, or a refusal in words. */\nasync function duplicatePlanOf(\n firestore: Firestore,\n job: Pick<AiJob, 'hostId' | 'inputs'>,\n readRows: typeof readAiDuplicateSourceRows,\n): Promise<(AiDuplicateResourceInput & { id: string }) | string> {\n if (!job.hostId) return AI_DUPLICATE_NO_SITE_COPY\n const request = aiDuplicateRequestOf(job.inputs)\n if (typeof request === 'string') return request\n const found = aiDuplicateSourceId(request.kind, request.sourceId, await readRows(firestore, job.hostId, request.kind))\n if ('error' in found) return found.error\n return { ...request, id: found.id }\n}\n\nexport function createAiJobDuplicateStep(deps: AiJobDuplicateStepDeps = {}): AiJobStepRunner {\n const duplicate = deps.duplicate ?? duplicateResource\n const readRows = deps.readRows ?? readAiDuplicateSourceRows\n return async ({ job, 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_DUPLICATE_NO_SITE_COPY)\n const plan = await duplicatePlanOf(firestore, job, readRows)\n if (typeof plan === 'string') return unspent(plan)\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 aiDuplicateAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const result = await duplicate(plan.kind, {\n orgId: job.orgId,\n hostId,\n sourceId: plan.id,\n name: plan.name,\n uid: job.createdBy,\n email: null,\n org: orgData,\n attemptKey: aiDuplicateAttemptKey(job),\n })\n if (result.ok === false) {\n // A plan counter or a feature the plan lacks is a limit to relay, not a fault.\n return result.status === 403 ? aiUnspentOutcome(model, { review: aiLimitReview(result.error) }) : unspent(result.error)\n }\n const output: AiJobOutput = {\n resource: AI_DUPLICATE_OUTPUT_RESOURCES[plan.kind],\n id: result.id,\n versionId: result.versionId,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: result.name,\n note: aiDuplicateOutputNote(plan.kind),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobDuplicateStep = createAiJobDuplicateStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiDuplicateJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n readRows?: typeof readAiDuplicateSourceRows\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A copy is admitted on a site of the job's own org, for a member who may\n * write its content now, of a source the site holds. Refused in those words\n * before anything exists; what the plan's counters allow is the copy's own\n * transaction to say.\n */\nexport function createAiDuplicateJobAdmission(deps: AiDuplicateJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n const readRows = deps.readRows ?? readAiDuplicateSourceRows\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_DUPLICATE_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 aiDuplicateAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const plan = await duplicatePlanOf(context.firestore as Firestore, { hostId: context.hostId, inputs: context.inputs as Record<string, unknown> }, readRows)\n return typeof plan === 'string' ? { status: 400, error: plan } : null\n }\n}\n\nexport const aiDuplicateJobAdmission = createAiDuplicateJobAdmission()\n\n/** Registers the duplicate step and its door (AGL-3740). */\nexport function registerAiDuplicateJob(): void {\n registerAiJobStep(AI_DUPLICATE_KIND, runAiJobDuplicateStep, { minimumMs: AI_JOB_DUPLICATE_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_DUPLICATE_KIND, aiDuplicateJobAdmission)\n}\n"],"names":["holdsSiteKey","orgPermissionLabel","DUPLICABLE_RESOURCE_NOUNS","duplicateResource","recipeFor","getLockdownVerdict","resolveOrgIdForHost","isAiPlanNewRef","parseAiDuplicateResourceInput","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_DUPLICATE_KIND","AI_JOB_DUPLICATE_STEP_MINIMUM_MS","AI_DUPLICATE_NO_SITE_COPY","AI_DUPLICATE_ROLE_COPY","AI_DUPLICATE_LOCKED_COPY","AI_DUPLICATE_UNREADABLE_COPY","AI_DUPLICATE_NOT_YET_COPY","AI_DUPLICATE_OUTPUT_RESOURCES","screen","component","layout","template","form","emailDesign","workflow","aiDuplicateAttemptKey","job","$id","slice","aiDuplicateRequestOf","inputs","source","trim","name","kind","sourceId","readAiDuplicateSourceRows","firestore","hostId","recipe","snapshot","collection","doc","select","nameField","get","docs","map","id","String","deleted","rowIsKind","row","aiDuplicateSourceId","rows","noun","live","filter","some","wanted","toLowerCase","named","length","error","aiDuplicateAccessRefusal","input","deps","host","exists","uid","locked","lockdown","staff","org","data","intent","aiDuplicateOutputNote","duplicatePlanOf","readRows","request","found","createAiJobDuplicateStep","duplicate","modelFor","model","unspent","failure","plan","orgData","orgId","refused","createdBy","review","result","email","attemptKey","ok","status","output","resource","versionId","hostSubdomain","label","note","outputs","runAiJobDuplicateStep","createAiDuplicateJobAdmission","ownerOf","context","owner","aiDuplicateJobAdmission","registerAiDuplicateJob","minimumMs"],"mappings":"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"}
|
|
@@ -62,7 +62,8 @@ export declare const AI_SITE_IMPORT_COPY: {
|
|
|
62
62
|
readonly pageUnavailable: "Pages cannot be built here right now.";
|
|
63
63
|
readonly pageNotWritten: "This page could not be written from its old page. Its spend is given back.";
|
|
64
64
|
readonly plan: "Bringing an existing site in is included on every paid plan.";
|
|
65
|
-
|
|
65
|
+
/** Names "Bring a site in" (`ai.import`, AGL-4047), never a role. */
|
|
66
|
+
readonly role: "Your role does not include \"Bring a site in\" — ask an organization admin";
|
|
66
67
|
};
|
|
67
68
|
export interface AiSiteImportOwnership {
|
|
68
69
|
current: boolean;
|
|
@@ -120,7 +121,8 @@ export declare function siteImportWaveReads(asked: readonly AiSiteImportPage[],
|
|
|
120
121
|
export interface AiSiteImportAdmissionDeps {
|
|
121
122
|
releaseFlagOn?: (orgId: string) => Promise<boolean>;
|
|
122
123
|
ownership?: (orgId: string, domain: string) => Promise<AiSiteImportOwnership | null>;
|
|
123
|
-
|
|
124
|
+
/** "Bring a site in" (`ai.import`, AGL-4047) for this member; specs hand in a double. */
|
|
125
|
+
holdsSiteImport?: (uid: string, orgId: string) => Promise<boolean>;
|
|
124
126
|
}
|
|
125
127
|
/**
|
|
126
128
|
* What an import asks before it exists and before each wave (AGL-3976): the
|
|
@@ -46,7 +46,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
46
46
|
import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
|
|
47
47
|
import { pluginMediaIngest } from "@aglyn/aglyn/plugin-manager/plugin-media-ingest";
|
|
48
48
|
import { listDomainOwnershipClaims, normalizeOwnershipDomain } from "@aglyn/tenant-data-admin/server/domain-ownership";
|
|
49
|
-
import { getOrgForUser } from "@aglyn/tenant-data-admin/server/organizations";
|
|
49
|
+
import { getOrgForUser, memberHasOrgPermission } from "@aglyn/tenant-data-admin/server/organizations";
|
|
50
50
|
import { consumeRateLimit } from "@aglyn/tenant-data-admin/server/rate-limit-store";
|
|
51
51
|
import { isServerReleaseFlagOnForOrg } from "@aglyn/tenant-data-admin/server/release-flags";
|
|
52
52
|
import { AI_SITE_IMPORT_DOMAIN_INPUT, AI_SITE_IMPORT_FRAME_SLOT, AI_SITE_IMPORT_LEFT_OUT_COPY, AI_SITE_IMPORT_MAX_PAGES, AI_SITE_IMPORT_MIN_WORDS, AI_SITE_IMPORT_WAVE_PAGES, aiSiteImportInventory, aiSiteImportItemOpen, aiSiteImportLedger, aiSiteImportNextWork, aiSiteImportPageSettled, aiSiteImportPagesRead, aiSiteImportPhotosIn, aiSiteImportWaveClosed, aiSiteImportWaveMessage, aiSiteImportWaveStarted } from "../model/ai-site-import.js";
|
|
@@ -101,7 +101,7 @@ const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
101
101
|
pageUnavailable: 'Pages cannot be built here right now.',
|
|
102
102
|
pageNotWritten: 'This page could not be written from its old page. Its spend is given back.',
|
|
103
103
|
plan: 'Bringing an existing site in is included on every paid plan.',
|
|
104
|
-
role: '
|
|
104
|
+
/** Names "Bring a site in" (`ai.import`, AGL-4047), never a role. */ role: 'Your role does not include "Bring a site in" — ask an organization admin'
|
|
105
105
|
};
|
|
106
106
|
async function readOwnership(orgId, domain) {
|
|
107
107
|
const claims = await listDomainOwnershipClaims(orgId);
|
|
@@ -757,13 +757,14 @@ function siteImportReadOrEmpty(page, at) {
|
|
|
757
757
|
* and a CURRENT proof that the workspace owns the domain. Nothing is spent on
|
|
758
758
|
* a refusal, and the day's limit is the step's to count, once a wave runs.
|
|
759
759
|
*/ export function createAiSiteImportAdmission(deps = {}) {
|
|
760
|
-
var _deps_releaseFlagOn, _deps_ownership,
|
|
760
|
+
var _deps_releaseFlagOn, _deps_ownership, _deps_holdsSiteImport;
|
|
761
761
|
const flagOn = (_deps_releaseFlagOn = deps.releaseFlagOn) != null ? _deps_releaseFlagOn : (orgId)=>isServerReleaseFlagOnForOrg(SITE_IMPORT_RELEASE_FLAG, orgId);
|
|
762
762
|
const ownershipOf = (_deps_ownership = deps.ownership) != null ? _deps_ownership : readOwnership;
|
|
763
|
-
const
|
|
763
|
+
const holdsSiteImport = (_deps_holdsSiteImport = deps.holdsSiteImport) != null ? _deps_holdsSiteImport : async (uid, orgId)=>{
|
|
764
764
|
var _ref;
|
|
765
|
-
var
|
|
766
|
-
|
|
765
|
+
var _this;
|
|
766
|
+
const member = (_ref = (_this = await getOrgForUser(uid, orgId)) == null ? void 0 : _this.member) != null ? _ref : null;
|
|
767
|
+
return Boolean(member) && await memberHasOrgPermission(orgId, member, 'ai.import');
|
|
767
768
|
};
|
|
768
769
|
return async (context)=>{
|
|
769
770
|
var _context_inputs_AI_SITE_IMPORT_DOMAIN_INPUT;
|
|
@@ -779,11 +780,14 @@ function siteImportReadOrEmpty(page, at) {
|
|
|
779
780
|
status: 403,
|
|
780
781
|
error: AI_SITE_IMPORT_COPY.plan
|
|
781
782
|
};
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
783
|
+
// "Bring a site in" (`ai.import`, AGL-4047), whose default is the owner
|
|
784
|
+
// and admin this asked for.
|
|
785
|
+
if (!context.uid || !await holdsSiteImport(context.uid, context.orgId)) {
|
|
786
|
+
return {
|
|
787
|
+
status: 403,
|
|
788
|
+
error: AI_SITE_IMPORT_COPY.role
|
|
789
|
+
};
|
|
790
|
+
}
|
|
787
791
|
const { domain, error } = normalizeOwnershipDomain(String((_context_inputs_AI_SITE_IMPORT_DOMAIN_INPUT = context.inputs[AI_SITE_IMPORT_DOMAIN_INPUT]) != null ? _context_inputs_AI_SITE_IMPORT_DOMAIN_INPUT : ''));
|
|
788
792
|
if (!domain) return {
|
|
789
793
|
status: 400,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-import-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\n/*==========================================\n * THE `import` JOB: BRING MY SITE, IN REVIEWED WAVES (AGL-3976).\n *\n * One step, many passes, each one unit of work (`aiSiteImportNextWork`):\n *\n * 1. READ the proved site (no model): the crawler reads it as AglynBot,\n * main pages first, up to the plan's page count; the inventory is cut\n * into waves (`model/ai-site-import.ts`), every page gets a ledger row,\n * and Trust & Safety gets its row. The job then WAITS (`needs_review`,\n * reason `wave`): nothing is brought in until the person starts wave 1.\n * 2. START A WAVE (no model), on the person's say-so: the proof is still\n * current, the day's limit has room, the plan still has room; the\n * wave's pages are read again, fresh, and a page that became a sign-in\n * page, went away or is now off limits to AglynBot is left out.\n * 3. THE HEADER AND FOOTER, in the first wave: the layout step's own pass,\n * linking the wave's pages.\n * 4. EACH PAGE: its photos into the media library (no model, deduped by\n * source key), then the page step's own passes in the layout language,\n * told the page's text FENCED as untrusted data.\n * 5. CLOSE THE WAVE: the job waits again, with the wave's pages as drafts\n * to review, until the person starts the next one. The last wave ends\n * the job.\n *\n * Every page is an item of the job's ledger, so a page that fails on our\n * side gives back its own spend (the machine's `refundBuildCredits`) and the\n * wave goes on. Nothing here publishes: every page is a draft, and the site\n * stays unpublished until a member publishes it, which they can only reach\n * once the import, behind its ownership proof, has written something.\n *=========================================*/\n\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { pluginMediaIngest, type PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport {\n listDomainOwnershipClaims,\n normalizeOwnershipDomain,\n} from '@aglyn/tenant-data-admin/server/domain-ownership'\nimport { getOrgForUser } from '@aglyn/tenant-data-admin/server/organizations'\nimport { consumeRateLimit } from '@aglyn/tenant-data-admin/server/rate-limit-store'\nimport { isServerReleaseFlagOnForOrg } from '@aglyn/tenant-data-admin/server/release-flags'\nimport type { AiBuildPlanCreate, AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobItemLedger, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport {\n AI_SITE_IMPORT_DOMAIN_INPUT,\n AI_SITE_IMPORT_FRAME_SLOT,\n AI_SITE_IMPORT_LEFT_OUT_COPY,\n AI_SITE_IMPORT_MAX_PAGES,\n AI_SITE_IMPORT_MIN_WORDS,\n AI_SITE_IMPORT_WAVE_PAGES,\n aiSiteImportInventory,\n aiSiteImportItemOpen,\n aiSiteImportLedger,\n aiSiteImportNextWork,\n aiSiteImportPageSettled,\n aiSiteImportPagesRead,\n aiSiteImportPhotosIn,\n aiSiteImportWaveClosed,\n aiSiteImportWaveMessage,\n aiSiteImportWaveStarted,\n type AiSiteImportLeftOutReason,\n type AiSiteImportPage,\n type AiSiteImportPageRead,\n type AiSiteImportState,\n type AiSiteImportWave,\n} from '../model/ai-site-import'\nimport { aiModelForStep } from '../providers/routing'\nimport { SITE_IMPORT_RELEASE_FLAG } from '../site-import/site-import-flag'\nimport { crawlSiteForImport, type SiteImportCrawlSeams, type SiteImportInventory } from '../site-import/site-import-crawl'\nimport { bringInSiteImportPhotos, type SiteImportPhotosDeps } from '../site-import/site-import-images'\nimport { siteImportLeftOutReason, siteImportListedPage, siteImportPageRead, siteImportPageWords } from '../site-import/site-import-page'\nimport { fileSiteImportReview } from '../site-import/site-import-review'\nimport { aiUnitErrorRetryable, aiUnitFailure, aiUnitSpend } from './ai-build-unit-outcome'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { AI_JOB_STEP_OVERHEAD_MS } from './ai-job-budget'\nimport { readAiScreenRoom } from './ai-job-drafts'\nimport { aiUnspentOutcome } from './ai-job-generation'\nimport { AI_LAYOUT_PAGE_PHOTOS_INPUT } from './ai-layout-owner-photos'\nimport { AI_LAYOUT_SITE_PAGES_INPUT, AI_LAYOUT_SITE_PAGES_MAX, type AiLayoutSitePage } from './ai-job-layout-site-pages'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport { AI_LAYOUT_IMPORTED_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT } from './ai-job-page-language'\nimport { aiSitePageWritten } from './ai-job-site-step'\nimport { AI_STOCK_PHOTO_PAGES_INPUT } from './ai-layout-stock-photos'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobItemOutcome, type AiJobStepContext, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiApplyJobItemRecord,\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/** Waves (the read included) one workspace may start in a day. */\nexport const SITE_IMPORT_WAVES_PER_ORG_PER_DAY = 12\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/** The rate-limit key a workspace's imports share. */\nexport function siteImportRateKey(orgId: string): string {\n return `ai-import:${orgId}`\n}\n\n/** The crawl's clock when the import reads the whole site. */\nexport const AI_SITE_IMPORT_READ_MS = 150_000\n\n/** The crawl's clock when a wave reads its own pages again. */\nexport const AI_SITE_IMPORT_WAVE_READ_MS = 90_000\n\n/** The clock for one page's photos: each is fetched and stored through the library's door. */\nexport const AI_SITE_IMPORT_PHOTOS_MS = 90_000\n\n/** What the step's own reads and writes around a pass take: the claims, the room, the row. */\nconst OWN_READS_MS = 10_000\n\n/** The least time each pass that asks no model needs. */\nexport const AI_SITE_IMPORT_PASS_MINIMUM_MS = {\n read: AI_SITE_IMPORT_READ_MS + OWN_READS_MS + AI_JOB_STEP_OVERHEAD_MS,\n wave: AI_SITE_IMPORT_WAVE_READ_MS + OWN_READS_MS + AI_JOB_STEP_OVERHEAD_MS,\n photos: AI_SITE_IMPORT_PHOTOS_MS + OWN_READS_MS + AI_JOB_STEP_OVERHEAD_MS,\n} as const\n\n/**\n * The passes one import may take: a read, then for every page its photos,\n * its answer and its listing, every wave's start and the header and footer,\n * with room for re-asks.\n */\nexport const AI_SITE_IMPORT_MAX_PASSES = AI_SITE_IMPORT_MAX_PAGES * 4 + 60\n\n/** Customer-safe sentences. */\nexport const AI_SITE_IMPORT_COPY = {\n noSite: 'Open the site to bring your website into.',\n badDomain: 'This website’s address cannot be brought in.',\n ownership: (domain: string) =>\n `Verify that you own ${domain} before bringing it in. A proof is good for 30 days; verify again, then start this wave again.`,\n limit: `This workspace has started as many site waves as it can today (${SITE_IMPORT_WAVES_PER_ORG_PER_DAY}). Start this wave again tomorrow.`,\n robots: (domain: string) =>\n `The robots.txt file of ${domain} could not be read just now, so nothing was read. Try again in a few minutes.`,\n nothing: (domain: string) => `No page of ${domain} could be read. Check that the site is up, then try again.`,\n blocked: (domain: string) =>\n `The bot protection or firewall of ${domain} turned AglynBot away, so no page could be read. Allow AglynBot in your site’s bot or firewall settings, then read the site again.`,\n unavailable: 'Bringing a site in is not available on this workspace right now.',\n frameUnavailable: 'The header and footer could not be built here; your pages use the site’s own layout.',\n pageUnavailable: 'Pages cannot be built here right now.',\n pageNotWritten: 'This page could not be written from its old page. Its spend is given back.',\n plan: 'Bringing an existing site in is included on every paid plan.',\n role: 'Only the workspace owner or an admin can bring a site in.',\n} as const\n\n/*------------------------------------------\n * Seams\n *-----------------------------------------*/\n\nexport interface AiSiteImportOwnership {\n current: boolean\n method: string | null\n provenAt: number | null\n detail: string | null\n}\n\nexport interface AiSiteImportStepDeps {\n crawl?: typeof crawlSiteForImport\n crawlSeams?: SiteImportCrawlSeams\n runnerFor?: typeof aiJobStepRunnerFor\n ownership?: (orgId: string, domain: string) => Promise<AiSiteImportOwnership | null>\n rateLimit?: (key: string, options: { limit: number; windowMs: number }) => Promise<{ allowed: boolean }>\n releaseFlagOn?: (orgId: string) => Promise<boolean>\n ingest?: () => PluginMediaIngest | null\n fetchImage?: SiteImportPhotosDeps['fetch']\n readRoom?: typeof readAiScreenRoom\n readSiteName?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<string>\n fileReview?: typeof fileSiteImportReview\n pageWritten?: typeof aiSitePageWritten\n mintId?: () => string\n clock?: () => number\n}\n\nasync function readOwnership(orgId: string, domain: string): Promise<AiSiteImportOwnership | null> {\n const claims = await listDomainOwnershipClaims(orgId)\n const claim = claims.find((entry) => entry.domain === domain)\n if (!claim) return null\n return { current: claim.current, method: claim.provenMethod, provenAt: claim.provenAt, detail: claim.provenDetail }\n}\n\nasync function readSiteName(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<string> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const name = host.get('displayName')\n return typeof name === 'string' ? name.trim() : ''\n}\n\n/** The proved domain an import job is for, or `null`. */\nexport function aiSiteImportDomainOf(job: Pick<AiJob, 'inputs'>): string | null {\n return normalizeOwnershipDomain(String(job.inputs?.[AI_SITE_IMPORT_DOMAIN_INPUT] ?? '')).domain\n}\n\n/*------------------------------------------\n * The jobs a unit is handed to\n *-----------------------------------------*/\n\nconst pathOf = (slug: string) => (slug === '/' ? '/' : `/${slug}`)\n\n/** A plan as a delegated unit reads it: confirmed, with what it builds and nothing else. */\nfunction unitPlan(job: AiJob, input: { create?: AiBuildPlanCreate[]; screens?: AiBuildPlanScreen[] }): AiJobPlan {\n return {\n reuse: [],\n create: input.create ?? [],\n screens: input.screens ?? [],\n labels: {},\n status: 'confirmed',\n proposedAt: job.createdAt,\n confirmedAt: job.createdAt,\n confirmedBy: job.createdBy,\n }\n}\n\nconst delivered = (items: readonly AiJobItemLedger[], slot: string) =>\n items.some((row) => row.slot === slot && (row.status === 'succeeded' || row.status === 'degraded'))\n\n/**\n * The pages the header and a page may link: the ones of this wave and those\n * before it that are in or still coming, home first, at most the header's\n * seven. A page of a later wave is not linked: it may never be brought in.\n */\nexport function aiSiteImportLinkedPages(\n state: AiSiteImportState,\n items: readonly AiJobItemLedger[],\n wave: number,\n): AiLayoutSitePage[] {\n const rows = new Map(items.map((row) => [row.slot, row]))\n const pages = state.pages\n .filter((page) => page.wave <= wave && !page.refused)\n .filter((page) => {\n const row = rows.get(page.slot)\n return !row || row.status === 'pending' || row.status === 'running' || row.status === 'succeeded' || row.status === 'degraded'\n })\n .map((page) => ({ id: page.draftId, label: page.title, slug: pathOf(page.slug) }))\n return [...pages.filter((page) => page.slug === '/'), ...pages.filter((page) => page.slug !== '/')].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The header and footer's job: a layout of the site's name, linking the wave's pages, in the layout language. */\nexport function aiSiteImportFrameJob(job: AiJob, state: AiSiteImportState, wave: AiSiteImportWave, items: readonly AiJobItemLedger[]): AiJob {\n const draftId = state.frame?.draftId ?? `${job.$id}-${AI_SITE_IMPORT_FRAME_SLOT}`\n const creation: AiBuildPlanCreate = {\n kind: 'layout',\n name: 'Header and footer',\n why: `The header and footer every page brought in from ${state.domain} renders inside.`,\n duplicateOf: null,\n fields: ['header', 'nav', 'main', 'footer'],\n id: draftId,\n }\n return {\n ...job,\n $id: draftId,\n kind: 'layout',\n steps: [],\n outputs: [],\n items: null,\n siteImport: null,\n plan: unitPlan(job, { create: [creation] }),\n inputs: {\n originJobId: job.$id,\n businessName: state.siteName,\n [AI_LAYOUT_LANGUAGE_INPUT]: true,\n [AI_LAYOUT_SITE_PAGES_INPUT]: aiSiteImportLinkedPages(state, items, wave.index),\n },\n brief: `Build the header and footer of “${state.siteName}”, a website brought in from ${state.domain}.`.slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/** One page's job: a page job's plan of one screen, its sections from the old page's headings, its text fenced. */\nexport function aiSiteImportPageJob(\n job: AiJob,\n state: AiSiteImportState,\n page: AiSiteImportPage,\n items: readonly AiJobItemLedger[],\n): AiJob {\n const sections = (page.read?.sections.length ? page.read.sections : ['Introduction']).map((name) => ({\n name,\n uses: [],\n items: 0,\n }))\n const screen: AiBuildPlanScreen = {\n title: page.title,\n slug: pathOf(page.slug),\n // Inside the header and footer this import built, once it is in.\n layout: state.frame && delivered(items, AI_SITE_IMPORT_FRAME_SLOT) ? state.frame.draftId : null,\n template: null,\n duplicateOf: null,\n nav: false,\n seoTitle: page.title,\n seoDescription: '',\n sections,\n record: null,\n id: page.draftId,\n }\n // The other pages already in, whose stock photos this page does not repeat.\n const others = state.pages.filter((entry) => entry.slot !== page.slot && delivered(items, entry.slot)).map((entry) => entry.draftId)\n return {\n ...job,\n $id: page.draftId,\n kind: 'page',\n steps: [],\n outputs: [],\n items: null,\n siteImport: null,\n plan: unitPlan(job, { screens: [screen] }),\n inputs: {\n originJobId: job.$id,\n businessName: state.siteName,\n [AI_LAYOUT_LANGUAGE_INPUT]: true,\n [AI_LAYOUT_SITE_PAGES_INPUT]: aiSiteImportLinkedPages(state, items, page.wave),\n [AI_LAYOUT_IMPORTED_PAGE_INPUT]: { url: page.url, text: page.read?.text ?? '' },\n [AI_LAYOUT_PAGE_PHOTOS_INPUT]: page.photos?.assets ?? [],\n ...(others.length ? { [AI_STOCK_PHOTO_PAGES_INPUT]: others } : {}),\n },\n brief: `Bring in the page “${page.title}” of ${state.domain} as a page of “${state.siteName}”.`.slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/** The least time the import's next pass needs: a read's, a photo pass's, or the time of the step its unit is handed to. */\nexport function aiSiteImportRunMinimumMs(job: AiJob, runnerFor: typeof aiJobStepRunnerFor = aiJobStepRunnerFor): number {\n const state = job.siteImport ?? null\n const items = job.items ?? []\n const work = aiSiteImportNextWork(state, items)\n switch (work.kind) {\n case 'inventory':\n return AI_SITE_IMPORT_PASS_MINIMUM_MS.read\n case 'start-wave':\n case 'read':\n return AI_SITE_IMPORT_PASS_MINIMUM_MS.wave\n case 'images':\n return AI_SITE_IMPORT_PASS_MINIMUM_MS.photos\n case 'frame':\n return runnerFor('layout') && state ? aiJobStepRunMinimumMs(aiSiteImportFrameJob(job, state, work.wave, items)) : 0\n case 'page':\n return runnerFor('page') && state ? aiJobStepRunMinimumMs(aiSiteImportPageJob(job, state, work.page, items)) : 0\n default:\n return 0\n }\n}\n\n/*------------------------------------------\n * The step\n *-----------------------------------------*/\n\nconst waveReview = (message: string): AiJobReview => ({ reason: 'wave', message, findings: [] })\n\n/** The rows a wave's read left out, settled as not brought in, with the reason. */\nfunction withLeftOutRows(\n items: readonly AiJobItemLedger[],\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiJobItemLedger[] {\n return items.map((row) => {\n const read = reads.get(row.slot)\n return typeof read === 'string' && (row.status === 'pending' || row.status === 'running')\n ? { ...row, status: 'skipped', note: AI_SITE_IMPORT_LEFT_OUT_COPY[read] }\n : row\n })\n}\n\n/** Whether every row of the wave is settled, its header and footer included. */\nfunction waveSettled(state: AiSiteImportState, wave: AiSiteImportWave, items: readonly AiJobItemLedger[]): boolean {\n if (wave.frame && state.frame && aiSiteImportItemOpen(items, AI_SITE_IMPORT_FRAME_SLOT)) return false\n return wave.slots.every((slot) => !aiSiteImportItemOpen(items, slot))\n}\n\nexport function createAiJobImportStep(deps: AiSiteImportStepDeps = {}): AiJobStepRunner {\n const crawl = deps.crawl ?? crawlSiteForImport\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n const ownershipOf = deps.ownership ?? readOwnership\n const rateLimit = deps.rateLimit ?? consumeRateLimit\n const flagOn = deps.releaseFlagOn ?? ((orgId: string) => isServerReleaseFlagOnForOrg(SITE_IMPORT_RELEASE_FLAG, orgId))\n const ingestOf = deps.ingest ?? pluginMediaIngest\n const readRoom = deps.readRoom ?? readAiScreenRoom\n const siteNameOf = deps.readSiteName ?? readSiteName\n const fileReview = deps.fileReview ?? fileSiteImportReview\n const pageWritten = deps.pageWritten ?? aiSitePageWritten\n const mintId = deps.mintId ?? createResourceUid\n const clock = deps.clock ?? Date.now\n\n return async (context: AiJobStepContext): Promise<AiJobStepOutcome> => {\n const { job, firestore, now, signal } = context\n const model = aiModelForStep('job.page')\n const unspent = (rest: Partial<AiJobStepOutcome> = {}): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), ...rest })\n const hostId = job.hostId\n if (!hostId) return unspent({ failure: AI_SITE_IMPORT_COPY.noSite })\n const domain = aiSiteImportDomainOf(job)\n if (!domain) return unspent({ failure: AI_SITE_IMPORT_COPY.badDomain })\n const state = job.siteImport ?? null\n const items = job.items ?? []\n const work = aiSiteImportNextWork(state, items)\n const nowIso = now.toISOString()\n const review = (event: 'read' | 'wave-started' | 'wave-finished', next: AiSiteImportState, ledger: readonly AiJobItemLedger[], wave?: number) =>\n fileReview(firestore, { orgId: job.orgId, hostId, jobId: job.$id, state: next, items: ledger, event, nowMs: now.getTime(), ...(wave !== undefined ? { wave } : {}) })\n\n /** Closes the wave when this pass settled its last row: the review, or the job's end after the last wave. */\n const closing = async (\n next: AiSiteImportState,\n ledger: readonly AiJobItemLedger[],\n wave: AiSiteImportWave,\n outcome: AiJobStepOutcome,\n ): Promise<AiJobStepOutcome> => {\n if (!waveSettled(next, wave, ledger)) return { ...outcome, siteImport: next, continue: true }\n const closed = aiSiteImportWaveClosed(next, wave.index, nowIso)\n await review('wave-finished', closed, ledger, wave.index)\n const waiting = closed.waves.find((entry) => entry.index === wave.index)?.status === 'review'\n return waiting\n ? { ...outcome, siteImport: closed, review: waveReview(aiSiteImportWaveMessage(closed, ledger)) }\n : { ...outcome, siteImport: closed }\n }\n\n /** The proof, the flag and the day's limit, asked before anything is read. */\n const gate = async (): Promise<{ ownership: AiSiteImportOwnership } | AiJobStepOutcome> => {\n if (!(await flagOn(job.orgId))) return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.unavailable) })\n const ownership = await ownershipOf(job.orgId, domain)\n if (!ownership?.current) {\n return unspent({ review: { reason: 'ownership', message: AI_SITE_IMPORT_COPY.ownership(domain), findings: [] } })\n }\n const window = await rateLimit(siteImportRateKey(job.orgId), { limit: SITE_IMPORT_WAVES_PER_ORG_PER_DAY, windowMs: DAY_MS })\n if (!window.allowed) return unspent({ review: { reason: 'limit', message: AI_SITE_IMPORT_COPY.limit, findings: [] } })\n return { ownership }\n }\n\n // ── 1. Read the site ──\n if (work.kind === 'inventory') {\n const passed = await gate()\n if (!('ownership' in passed)) return passed\n const room = await readRoom(firestore, { hostId, org: (context.org ?? null) as never })\n const left = room.limit === null ? AI_SITE_IMPORT_MAX_PAGES : Math.max(0, room.limit - room.used)\n const crawled = await crawl(\n {\n domain,\n // A wave's worth past the room, so what the plan leaves out is said.\n maxPages: Math.min(AI_SITE_IMPORT_MAX_PAGES, left + AI_SITE_IMPORT_WAVE_PAGES),\n timeBudgetMs: AI_SITE_IMPORT_READ_MS,\n shallowFirst: true,\n ...(signal ? { signal } : {}),\n },\n deps.crawlSeams,\n )\n if (crawled.refused) return unspent({ failure: AI_SITE_IMPORT_COPY.badDomain })\n if (crawled.stoppedBy === 'robots-unreachable') return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.robots(domain)) })\n const skipped = crawled.skipped.map((entry) => ({ url: entry.url, reason: siteImportLeftOutReason(entry.reason, entry.status) }))\n if (!crawled.pages.length) {\n // A site its bot protection keeps AglynBot off is up: say what lets AglynBot in, not to check that it is.\n const blocked = skipped.some((entry) => entry.reason === 'blocked')\n return unspent({ review: waveReview(AI_SITE_IMPORT_COPY[blocked ? 'blocked' : 'nothing'](domain)) })\n }\n const inventory = aiSiteImportInventory({\n domain,\n siteName: await siteNameOf(firestore, hostId),\n ownership: { method: passed.ownership.method, provenAt: passed.ownership.provenAt, detail: passed.ownership.detail },\n readAt: nowIso,\n pages: crawled.pages.map(siteImportListedPage),\n menu: siteImportMenu(crawled),\n skipped,\n skippedOverflow: crawled.skippedOverflow,\n stoppedBy: crawled.stoppedBy,\n pageLimit: room.limit,\n existingPages: room.used,\n mintId,\n })\n const ledger = aiSiteImportLedger(inventory)\n const reviewId = await review('read', inventory, ledger)\n const next = { ...inventory, reviewId }\n return unspent({ items: ledger, siteImport: next, review: waveReview(aiSiteImportWaveMessage(next, ledger)) })\n }\n\n if (!state) return unspent({ failure: AI_SITE_IMPORT_COPY.badDomain })\n if (work.kind === 'done') return unspent()\n\n // ── 2. Start a wave ──\n if (work.kind === 'start-wave') {\n const passed = await gate()\n if (!('ownership' in passed)) return passed\n const wave = work.wave\n const pages = wave.slots\n .map((slot) => state.pages.find((page) => page.slot === slot))\n .filter((page): page is AiSiteImportPage => !!page)\n const reads = new Map<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>()\n // Never past the plan: the room is read again, since pages may have been added since the site was read.\n const room = await readRoom(firestore, { hostId, org: (context.org ?? null) as never })\n let left = room.limit === null ? Number.POSITIVE_INFINITY : Math.max(0, room.limit - room.used)\n const toRead: AiSiteImportPage[] = []\n for (const page of pages) {\n if (left > 0) {\n toRead.push(page)\n left -= 1\n } else {\n reads.set(page.slot, 'plan-limit')\n }\n }\n if (toRead.length) {\n const crawled = await crawl(\n {\n domain,\n maxPages: toRead.length,\n only: toRead.map((page) => page.url),\n timeBudgetMs: AI_SITE_IMPORT_WAVE_READ_MS,\n ...(signal ? { signal } : {}),\n },\n deps.crawlSeams,\n )\n if (crawled.stoppedBy === 'robots-unreachable') return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.robots(domain)) })\n // A read the clock cut short leaves its other pages for the next pass, never \"unreadable\".\n for (const [slot, read] of siteImportWaveReads(toRead, crawled, nowIso, { cutShort: crawled.stoppedBy === 'time' })) {\n reads.set(slot, read)\n }\n }\n let ledger = withLeftOutRows(items, reads)\n // A wave none of whose pages could be read builds no header for them.\n const anyRead = [...reads.values()].some((read) => typeof read !== 'string')\n if (wave.frame && !anyRead && reads.size === pages.length) {\n ledger = ledger.map((row) =>\n row.slot === AI_SITE_IMPORT_FRAME_SLOT && (row.status === 'pending' || row.status === 'running')\n ? { ...row, status: 'skipped', note: 'Not built: no page of this wave could be read.' }\n : row,\n )\n }\n const started = aiSiteImportWaveStarted(state, wave.index, nowIso, reads)\n await review('wave-started', started, ledger, wave.index)\n const running = started.waves.find((entry) => entry.index === wave.index) as AiSiteImportWave\n return closing(started, ledger, running, unspent({ items: ledger }))\n }\n\n const wave = work.wave\n\n // ── 2b. The rest of a wave's read, where the clock cut it short ──\n if (work.kind === 'read') {\n const crawled = await crawl(\n {\n domain,\n maxPages: work.pages.length,\n only: work.pages.map((page) => page.url),\n timeBudgetMs: AI_SITE_IMPORT_WAVE_READ_MS,\n ...(signal ? { signal } : {}),\n },\n deps.crawlSeams,\n )\n if (crawled.stoppedBy === 'robots-unreachable') return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.robots(domain)) })\n const reads = siteImportWaveReads(work.pages, crawled, nowIso, { cutShort: crawled.stoppedBy === 'time' })\n const ledger = withLeftOutRows(items, reads)\n return closing(aiSiteImportPagesRead(state, reads), ledger, wave, unspent({ items: ledger }))\n }\n\n // ── 3. The header and footer ──\n if (work.kind === 'frame') {\n const runner = runnerFor('layout')\n if (!runner) {\n const item: AiJobItemOutcome = { slot: AI_SITE_IMPORT_FRAME_SLOT, status: 'skipped', note: AI_SITE_IMPORT_COPY.frameUnavailable }\n return closing(state, aiApplyJobItemRecord(items, item, 0, now), wave, unspent({ item }))\n }\n const derived = aiSiteImportFrameJob(job, state, wave, items)\n return runUnit(AI_SITE_IMPORT_FRAME_SLOT, runner, derived, state, null)\n }\n\n // ── 4a. A page's photos ──\n if (work.kind === 'images') {\n const photos = signal?.aborted\n ? { brought: 0, reused: 0, skipped: work.page.read?.images.length ?? 0, assets: [] }\n : await bringInSiteImportPhotos(\n {\n hostId,\n uid: job.createdBy,\n domain,\n images: work.page.read?.images ?? [],\n deadlineAt: clock() + AI_SITE_IMPORT_PHOTOS_MS,\n ...(signal ? { signal } : {}),\n },\n { ingest: ingestOf(), ...(deps.fetchImage ? { fetch: deps.fetchImage } : {}) },\n )\n return unspent({\n item: { slot: work.page.slot, status: 'running' },\n siteImport: aiSiteImportPhotosIn(state, work.page.slot, photos),\n continue: true,\n })\n }\n\n // ── 4b. The page ──\n if (work.kind === 'page') {\n const runner = runnerFor('page')\n if (!runner) {\n const item: AiJobItemOutcome = {\n slot: work.page.slot,\n status: 'failed',\n failure: { ours: true, reason: 'unavailable', message: AI_SITE_IMPORT_COPY.pageUnavailable },\n }\n return closing(state, aiApplyJobItemRecord(items, item, 0, now), wave, unspent({ item }))\n }\n return runUnit(work.page.slot, runner, aiSiteImportPageJob(job, state, work.page, items), state, work.page)\n }\n\n // ── 5. Close the wave ──\n return closing(state, items, wave, unspent())\n\n /** One delegated pass: the unit's spend is the pass's, what it came to is its row. */\n async function runUnit(\n slot: string,\n runner: AiJobStepRunner,\n derived: AiJob,\n current: AiSiteImportState,\n page: AiSiteImportPage | null,\n ): Promise<AiJobStepOutcome> {\n let outcome: AiJobStepOutcome\n try {\n outcome = await runner({ ...context, job: derived })\n } catch (error) {\n // A provider that will answer later, a budget that ended, a cancel: the machine's.\n if (aiUnitErrorRetryable(error) || (signal?.aborted ?? false)) throw error\n console.error('ai site import unit threw', { orgId: job.orgId, jobId: job.$id, slot, error })\n const item: AiJobItemOutcome = {\n slot,\n status: 'failed',\n failure: { ours: true, reason: 'provider', message: AI_SITE_IMPORT_COPY.pageNotWritten },\n }\n return closing(current, aiApplyJobItemRecord(items, item, 0, now), wave, unspent({ item }))\n }\n const spent = aiUnitSpend(outcome)\n const stopped = aiUnitFailure(slot, outcome)\n if (stopped?.status === 'running') return { ...spent, outputs: [], item: stopped, siteImport: current, continue: true }\n let item: AiJobItemOutcome\n if (stopped) {\n item = stopped\n } else if (page && derived.plan) {\n // A page counts as in only when its sections are in it (AGL-3596).\n const screen = derived.plan.screens[0]\n const written = await pageWritten(firestore, {\n hostId: hostId as string,\n unitJobId: derived.$id,\n screen,\n draftId: page.draftId,\n layoutLanguage: true,\n })\n item = written\n ? { slot, status: 'succeeded', outputs: spent.outputs.map((output) => output.id) }\n : { slot, status: 'failed', failure: { ours: true, reason: 'step-failure', message: AI_SITE_IMPORT_COPY.pageNotWritten } }\n } else {\n item = { slot, status: 'succeeded', outputs: spent.outputs.map((output) => output.id) }\n }\n const settledState = page ? aiSiteImportPageSettled(current, slot) : current\n return closing(\n settledState,\n aiApplyJobItemRecord(items, item, 0, now),\n wave,\n { ...spent, outputs: item.status === 'succeeded' ? spent.outputs : [], item },\n )\n }\n }\n}\n\n/** The home page's menu, as same-site paths in its order. */\nexport function siteImportMenu(crawled: Pick<SiteImportInventory, 'pages' | 'origins'>): string[] {\n const home = crawled.pages.find((page) => page.foundBy === 'home') ?? crawled.pages[0]\n const paths: string[] = []\n for (const link of home?.navLinks ?? []) {\n try {\n const url = new URL(link.url)\n if (!crawled.origins.includes(url.origin)) continue\n const path = url.pathname.replace(/\\/+$/, '') || '/'\n if (!paths.includes(path)) paths.push(path)\n } catch {\n // A menu entry that is not an address names no page.\n }\n }\n return paths\n}\n\n/**\n * What a wave's read came to for each page: the page read again, or why it\n * is left out now. Matched by address; a page the site sent to another of its\n * own addresses is matched in the order the wave asked for it.\n */\nexport function siteImportWaveReads(\n asked: readonly AiSiteImportPage[],\n crawled: Pick<SiteImportInventory, 'pages' | 'skipped'>,\n at: string,\n options: {\n /** The read's clock ran out: a page it did not reach is left for the next pass, not left out. */\n cutShort?: boolean\n } = {},\n): Map<string, AiSiteImportPageRead | AiSiteImportLeftOutReason> {\n const reads = new Map<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>()\n const pathKey = (url: string) => {\n try {\n const parsed = new URL(url)\n return `${parsed.pathname.replace(/\\/+$/, '') || '/'}${parsed.search}`\n } catch {\n return url\n }\n }\n const pages = new Map(crawled.pages.map((page) => [pathKey(page.url), page]))\n const skipped = new Map(crawled.skipped.map((entry) => [pathKey(entry.url), entry]))\n const unmatched = [...crawled.pages]\n const take = (page: (typeof crawled.pages)[number]) => {\n const index = unmatched.indexOf(page)\n if (index !== -1) unmatched.splice(index, 1)\n }\n const pending: AiSiteImportPage[] = []\n for (const page of asked) {\n const found = pages.get(pathKey(page.url))\n if (found) {\n take(found)\n reads.set(page.slot, siteImportReadOrEmpty(found, at))\n continue\n }\n const left = skipped.get(pathKey(page.url))\n if (left) {\n reads.set(page.slot, siteImportLeftOutReason(left.reason, left.status))\n continue\n }\n pending.push(page)\n }\n for (const page of pending) {\n const found = unmatched.shift()\n if (found) reads.set(page.slot, siteImportReadOrEmpty(found, at))\n else if (!options.cutShort) reads.set(page.slot, 'unreadable')\n }\n return reads\n}\n\nfunction siteImportReadOrEmpty(page: SiteImportInventory['pages'][number], at: string): AiSiteImportPageRead | 'empty' {\n if (siteImportPageWords(page) < AI_SITE_IMPORT_MIN_WORDS && !page.images.length) return 'empty'\n return siteImportPageRead(page, at)\n}\n\n/*------------------------------------------\n * Admission, and registration\n *-----------------------------------------*/\n\nexport interface AiSiteImportAdmissionDeps {\n releaseFlagOn?: (orgId: string) => Promise<boolean>\n ownership?: (orgId: string, domain: string) => Promise<AiSiteImportOwnership | null>\n memberRole?: (uid: string, orgId: string) => Promise<string | null>\n}\n\n/**\n * What an import asks before it exists and before each wave (AGL-3976): the\n * release flag, a site to bring it into, a paid plan, the owner or an admin,\n * and a CURRENT proof that the workspace owns the domain. Nothing is spent on\n * a refusal, and the day's limit is the step's to count, once a wave runs.\n */\nexport function createAiSiteImportAdmission(deps: AiSiteImportAdmissionDeps = {}): AiJobAdmission {\n const flagOn = deps.releaseFlagOn ?? ((orgId: string) => isServerReleaseFlagOnForOrg(SITE_IMPORT_RELEASE_FLAG, orgId))\n const ownershipOf = deps.ownership ?? readOwnership\n const memberRole =\n deps.memberRole ??\n (async (uid: string, orgId: string) => (await getOrgForUser(uid, orgId))?.member?.role ?? null)\n return async (context) => {\n if (!(await flagOn(context.orgId))) return { status: 404, error: 'Not found' }\n if (!context.hostId && !context.siteToCome) return { status: 400, error: AI_SITE_IMPORT_COPY.noSite }\n if (resolveEffectivePlan(context.org as never) === 'free') return { status: 403, error: AI_SITE_IMPORT_COPY.plan }\n const role = context.uid ? await memberRole(context.uid, context.orgId) : null\n if (role !== 'owner' && role !== 'admin') return { status: 403, error: AI_SITE_IMPORT_COPY.role }\n const { domain, error } = normalizeOwnershipDomain(String(context.inputs[AI_SITE_IMPORT_DOMAIN_INPUT] ?? ''))\n if (!domain) return { status: 400, error: error ?? AI_SITE_IMPORT_COPY.badDomain }\n const ownership = await ownershipOf(context.orgId, domain)\n if (!ownership?.current) return { status: 403, error: AI_SITE_IMPORT_COPY.ownership(domain) }\n return null\n }\n}\n\n/**\n * The import kind's registrations (AGL-3976), by a call from `server.ts`,\n * never by an import: its runner with each pass's least time, its pass bound\n * and its admission.\n */\nexport function registerAiSiteImportJob(deps: AiSiteImportStepDeps = {}): void {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n registerAiJobStep('import', createAiJobImportStep(deps), {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: (job) => aiSiteImportRunMinimumMs(job, runnerFor),\n })\n registerAiJobStepPasses('import', AI_SITE_IMPORT_MAX_PASSES)\n registerAiJobAdmission('import', createAiSiteImportAdmission())\n}\n"],"names":["resolveEffectivePlan","createResourceUid","pluginMediaIngest","listDomainOwnershipClaims","normalizeOwnershipDomain","getOrgForUser","consumeRateLimit","isServerReleaseFlagOnForOrg","AI_SITE_IMPORT_DOMAIN_INPUT","AI_SITE_IMPORT_FRAME_SLOT","AI_SITE_IMPORT_LEFT_OUT_COPY","AI_SITE_IMPORT_MAX_PAGES","AI_SITE_IMPORT_MIN_WORDS","AI_SITE_IMPORT_WAVE_PAGES","aiSiteImportInventory","aiSiteImportItemOpen","aiSiteImportLedger","aiSiteImportNextWork","aiSiteImportPageSettled","aiSiteImportPagesRead","aiSiteImportPhotosIn","aiSiteImportWaveClosed","aiSiteImportWaveMessage","aiSiteImportWaveStarted","aiModelForStep","SITE_IMPORT_RELEASE_FLAG","crawlSiteForImport","bringInSiteImportPhotos","siteImportLeftOutReason","siteImportListedPage","siteImportPageRead","siteImportPageWords","fileSiteImportReview","aiUnitErrorRetryable","aiUnitFailure","aiUnitSpend","registerAiJobAdmission","AI_JOB_STEP_OVERHEAD_MS","readAiScreenRoom","aiUnspentOutcome","AI_LAYOUT_PAGE_PHOTOS_INPUT","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","AI_JOB_PAGE_STEP_MINIMUM_MS","AI_LAYOUT_IMPORTED_PAGE_INPUT","AI_LAYOUT_LANGUAGE_INPUT","aiSitePageWritten","AI_STOCK_PHOTO_PAGES_INPUT","AI_JOB_BRIEF_MAX_CHARS","aiApplyJobItemRecord","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","SITE_IMPORT_WAVES_PER_ORG_PER_DAY","DAY_MS","siteImportRateKey","orgId","AI_SITE_IMPORT_READ_MS","AI_SITE_IMPORT_WAVE_READ_MS","AI_SITE_IMPORT_PHOTOS_MS","OWN_READS_MS","AI_SITE_IMPORT_PASS_MINIMUM_MS","read","wave","photos","AI_SITE_IMPORT_MAX_PASSES","AI_SITE_IMPORT_COPY","noSite","badDomain","ownership","domain","limit","robots","nothing","blocked","unavailable","frameUnavailable","pageUnavailable","pageNotWritten","plan","role","readOwnership","claims","claim","find","entry","current","method","provenMethod","provenAt","detail","provenDetail","readSiteName","firestore","hostId","host","collection","doc","get","name","trim","aiSiteImportDomainOf","job","String","inputs","pathOf","slug","unitPlan","input","reuse","create","screens","labels","status","proposedAt","createdAt","confirmedAt","confirmedBy","createdBy","delivered","items","slot","some","row","aiSiteImportLinkedPages","state","rows","Map","map","pages","filter","page","refused","id","draftId","label","title","slice","aiSiteImportFrameJob","frame","$id","creation","kind","why","duplicateOf","fields","steps","outputs","siteImport","originJobId","businessName","siteName","index","brief","aiSiteImportPageJob","sections","length","uses","screen","layout","template","nav","seoTitle","seoDescription","record","others","url","text","assets","aiSiteImportRunMinimumMs","runnerFor","work","waveReview","message","reason","findings","withLeftOutRows","reads","note","waveSettled","slots","every","createAiJobImportStep","deps","crawl","ownershipOf","rateLimit","flagOn","releaseFlagOn","ingestOf","ingest","readRoom","siteNameOf","fileReview","pageWritten","mintId","clock","Date","now","context","signal","model","unspent","rest","failure","nowIso","toISOString","review","event","next","ledger","jobId","nowMs","getTime","undefined","closing","outcome","closed","continue","waiting","waves","gate","window","windowMs","allowed","passed","room","org","left","Math","max","used","crawled","maxPages","min","timeBudgetMs","shallowFirst","crawlSeams","stoppedBy","skipped","inventory","readAt","menu","siteImportMenu","skippedOverflow","pageLimit","existingPages","reviewId","Number","POSITIVE_INFINITY","toRead","push","set","only","siteImportWaveReads","cutShort","anyRead","values","size","started","running","runner","item","derived","runUnit","aborted","brought","reused","images","uid","deadlineAt","fetchImage","fetch","ours","error","console","spent","stopped","written","unitJobId","layoutLanguage","output","settledState","home","foundBy","paths","link","navLinks","URL","origins","includes","origin","path","pathname","replace","asked","at","options","pathKey","parsed","search","unmatched","take","indexOf","splice","pending","found","siteImportReadOrEmpty","shift","createAiSiteImportAdmission","memberRole","member","siteToCome","registerAiSiteImportJob","minimumMs","minimumMsFor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-import-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\n/*==========================================\n * THE `import` JOB: BRING MY SITE, IN REVIEWED WAVES (AGL-3976).\n *\n * One step, many passes, each one unit of work (`aiSiteImportNextWork`):\n *\n * 1. READ the proved site (no model): the crawler reads it as AglynBot,\n * main pages first, up to the plan's page count; the inventory is cut\n * into waves (`model/ai-site-import.ts`), every page gets a ledger row,\n * and Trust & Safety gets its row. The job then WAITS (`needs_review`,\n * reason `wave`): nothing is brought in until the person starts wave 1.\n * 2. START A WAVE (no model), on the person's say-so: the proof is still\n * current, the day's limit has room, the plan still has room; the\n * wave's pages are read again, fresh, and a page that became a sign-in\n * page, went away or is now off limits to AglynBot is left out.\n * 3. THE HEADER AND FOOTER, in the first wave: the layout step's own pass,\n * linking the wave's pages.\n * 4. EACH PAGE: its photos into the media library (no model, deduped by\n * source key), then the page step's own passes in the layout language,\n * told the page's text FENCED as untrusted data.\n * 5. CLOSE THE WAVE: the job waits again, with the wave's pages as drafts\n * to review, until the person starts the next one. The last wave ends\n * the job.\n *\n * Every page is an item of the job's ledger, so a page that fails on our\n * side gives back its own spend (the machine's `refundBuildCredits`) and the\n * wave goes on. Nothing here publishes: every page is a draft, and the site\n * stays unpublished until a member publishes it, which they can only reach\n * once the import, behind its ownership proof, has written something.\n *=========================================*/\n\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { pluginMediaIngest, type PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport {\n listDomainOwnershipClaims,\n normalizeOwnershipDomain,\n} from '@aglyn/tenant-data-admin/server/domain-ownership'\nimport { getOrgForUser, memberHasOrgPermission } from '@aglyn/tenant-data-admin/server/organizations'\nimport { consumeRateLimit } from '@aglyn/tenant-data-admin/server/rate-limit-store'\nimport { isServerReleaseFlagOnForOrg } from '@aglyn/tenant-data-admin/server/release-flags'\nimport type { AiBuildPlanCreate, AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobItemLedger, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport {\n AI_SITE_IMPORT_DOMAIN_INPUT,\n AI_SITE_IMPORT_FRAME_SLOT,\n AI_SITE_IMPORT_LEFT_OUT_COPY,\n AI_SITE_IMPORT_MAX_PAGES,\n AI_SITE_IMPORT_MIN_WORDS,\n AI_SITE_IMPORT_WAVE_PAGES,\n aiSiteImportInventory,\n aiSiteImportItemOpen,\n aiSiteImportLedger,\n aiSiteImportNextWork,\n aiSiteImportPageSettled,\n aiSiteImportPagesRead,\n aiSiteImportPhotosIn,\n aiSiteImportWaveClosed,\n aiSiteImportWaveMessage,\n aiSiteImportWaveStarted,\n type AiSiteImportLeftOutReason,\n type AiSiteImportPage,\n type AiSiteImportPageRead,\n type AiSiteImportState,\n type AiSiteImportWave,\n} from '../model/ai-site-import'\nimport { aiModelForStep } from '../providers/routing'\nimport { SITE_IMPORT_RELEASE_FLAG } from '../site-import/site-import-flag'\nimport { crawlSiteForImport, type SiteImportCrawlSeams, type SiteImportInventory } from '../site-import/site-import-crawl'\nimport { bringInSiteImportPhotos, type SiteImportPhotosDeps } from '../site-import/site-import-images'\nimport { siteImportLeftOutReason, siteImportListedPage, siteImportPageRead, siteImportPageWords } from '../site-import/site-import-page'\nimport { fileSiteImportReview } from '../site-import/site-import-review'\nimport { aiUnitErrorRetryable, aiUnitFailure, aiUnitSpend } from './ai-build-unit-outcome'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { AI_JOB_STEP_OVERHEAD_MS } from './ai-job-budget'\nimport { readAiScreenRoom } from './ai-job-drafts'\nimport { aiUnspentOutcome } from './ai-job-generation'\nimport { AI_LAYOUT_PAGE_PHOTOS_INPUT } from './ai-layout-owner-photos'\nimport { AI_LAYOUT_SITE_PAGES_INPUT, AI_LAYOUT_SITE_PAGES_MAX, type AiLayoutSitePage } from './ai-job-layout-site-pages'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport { AI_LAYOUT_IMPORTED_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT } from './ai-job-page-language'\nimport { aiSitePageWritten } from './ai-job-site-step'\nimport { AI_STOCK_PHOTO_PAGES_INPUT } from './ai-layout-stock-photos'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobItemOutcome, type AiJobStepContext, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step'\nimport {\n aiApplyJobItemRecord,\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/** Waves (the read included) one workspace may start in a day. */\nexport const SITE_IMPORT_WAVES_PER_ORG_PER_DAY = 12\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/** The rate-limit key a workspace's imports share. */\nexport function siteImportRateKey(orgId: string): string {\n return `ai-import:${orgId}`\n}\n\n/** The crawl's clock when the import reads the whole site. */\nexport const AI_SITE_IMPORT_READ_MS = 150_000\n\n/** The crawl's clock when a wave reads its own pages again. */\nexport const AI_SITE_IMPORT_WAVE_READ_MS = 90_000\n\n/** The clock for one page's photos: each is fetched and stored through the library's door. */\nexport const AI_SITE_IMPORT_PHOTOS_MS = 90_000\n\n/** What the step's own reads and writes around a pass take: the claims, the room, the row. */\nconst OWN_READS_MS = 10_000\n\n/** The least time each pass that asks no model needs. */\nexport const AI_SITE_IMPORT_PASS_MINIMUM_MS = {\n read: AI_SITE_IMPORT_READ_MS + OWN_READS_MS + AI_JOB_STEP_OVERHEAD_MS,\n wave: AI_SITE_IMPORT_WAVE_READ_MS + OWN_READS_MS + AI_JOB_STEP_OVERHEAD_MS,\n photos: AI_SITE_IMPORT_PHOTOS_MS + OWN_READS_MS + AI_JOB_STEP_OVERHEAD_MS,\n} as const\n\n/**\n * The passes one import may take: a read, then for every page its photos,\n * its answer and its listing, every wave's start and the header and footer,\n * with room for re-asks.\n */\nexport const AI_SITE_IMPORT_MAX_PASSES = AI_SITE_IMPORT_MAX_PAGES * 4 + 60\n\n/** Customer-safe sentences. */\nexport const AI_SITE_IMPORT_COPY = {\n noSite: 'Open the site to bring your website into.',\n badDomain: 'This website’s address cannot be brought in.',\n ownership: (domain: string) =>\n `Verify that you own ${domain} before bringing it in. A proof is good for 30 days; verify again, then start this wave again.`,\n limit: `This workspace has started as many site waves as it can today (${SITE_IMPORT_WAVES_PER_ORG_PER_DAY}). Start this wave again tomorrow.`,\n robots: (domain: string) =>\n `The robots.txt file of ${domain} could not be read just now, so nothing was read. Try again in a few minutes.`,\n nothing: (domain: string) => `No page of ${domain} could be read. Check that the site is up, then try again.`,\n blocked: (domain: string) =>\n `The bot protection or firewall of ${domain} turned AglynBot away, so no page could be read. Allow AglynBot in your site’s bot or firewall settings, then read the site again.`,\n unavailable: 'Bringing a site in is not available on this workspace right now.',\n frameUnavailable: 'The header and footer could not be built here; your pages use the site’s own layout.',\n pageUnavailable: 'Pages cannot be built here right now.',\n pageNotWritten: 'This page could not be written from its old page. Its spend is given back.',\n plan: 'Bringing an existing site in is included on every paid plan.',\n /** Names \"Bring a site in\" (`ai.import`, AGL-4047), never a role. */\n role: 'Your role does not include \"Bring a site in\" — ask an organization admin',\n} as const\n\n/*------------------------------------------\n * Seams\n *-----------------------------------------*/\n\nexport interface AiSiteImportOwnership {\n current: boolean\n method: string | null\n provenAt: number | null\n detail: string | null\n}\n\nexport interface AiSiteImportStepDeps {\n crawl?: typeof crawlSiteForImport\n crawlSeams?: SiteImportCrawlSeams\n runnerFor?: typeof aiJobStepRunnerFor\n ownership?: (orgId: string, domain: string) => Promise<AiSiteImportOwnership | null>\n rateLimit?: (key: string, options: { limit: number; windowMs: number }) => Promise<{ allowed: boolean }>\n releaseFlagOn?: (orgId: string) => Promise<boolean>\n ingest?: () => PluginMediaIngest | null\n fetchImage?: SiteImportPhotosDeps['fetch']\n readRoom?: typeof readAiScreenRoom\n readSiteName?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<string>\n fileReview?: typeof fileSiteImportReview\n pageWritten?: typeof aiSitePageWritten\n mintId?: () => string\n clock?: () => number\n}\n\nasync function readOwnership(orgId: string, domain: string): Promise<AiSiteImportOwnership | null> {\n const claims = await listDomainOwnershipClaims(orgId)\n const claim = claims.find((entry) => entry.domain === domain)\n if (!claim) return null\n return { current: claim.current, method: claim.provenMethod, provenAt: claim.provenAt, detail: claim.provenDetail }\n}\n\nasync function readSiteName(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<string> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n const name = host.get('displayName')\n return typeof name === 'string' ? name.trim() : ''\n}\n\n/** The proved domain an import job is for, or `null`. */\nexport function aiSiteImportDomainOf(job: Pick<AiJob, 'inputs'>): string | null {\n return normalizeOwnershipDomain(String(job.inputs?.[AI_SITE_IMPORT_DOMAIN_INPUT] ?? '')).domain\n}\n\n/*------------------------------------------\n * The jobs a unit is handed to\n *-----------------------------------------*/\n\nconst pathOf = (slug: string) => (slug === '/' ? '/' : `/${slug}`)\n\n/** A plan as a delegated unit reads it: confirmed, with what it builds and nothing else. */\nfunction unitPlan(job: AiJob, input: { create?: AiBuildPlanCreate[]; screens?: AiBuildPlanScreen[] }): AiJobPlan {\n return {\n reuse: [],\n create: input.create ?? [],\n screens: input.screens ?? [],\n labels: {},\n status: 'confirmed',\n proposedAt: job.createdAt,\n confirmedAt: job.createdAt,\n confirmedBy: job.createdBy,\n }\n}\n\nconst delivered = (items: readonly AiJobItemLedger[], slot: string) =>\n items.some((row) => row.slot === slot && (row.status === 'succeeded' || row.status === 'degraded'))\n\n/**\n * The pages the header and a page may link: the ones of this wave and those\n * before it that are in or still coming, home first, at most the header's\n * seven. A page of a later wave is not linked: it may never be brought in.\n */\nexport function aiSiteImportLinkedPages(\n state: AiSiteImportState,\n items: readonly AiJobItemLedger[],\n wave: number,\n): AiLayoutSitePage[] {\n const rows = new Map(items.map((row) => [row.slot, row]))\n const pages = state.pages\n .filter((page) => page.wave <= wave && !page.refused)\n .filter((page) => {\n const row = rows.get(page.slot)\n return !row || row.status === 'pending' || row.status === 'running' || row.status === 'succeeded' || row.status === 'degraded'\n })\n .map((page) => ({ id: page.draftId, label: page.title, slug: pathOf(page.slug) }))\n return [...pages.filter((page) => page.slug === '/'), ...pages.filter((page) => page.slug !== '/')].slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/** The header and footer's job: a layout of the site's name, linking the wave's pages, in the layout language. */\nexport function aiSiteImportFrameJob(job: AiJob, state: AiSiteImportState, wave: AiSiteImportWave, items: readonly AiJobItemLedger[]): AiJob {\n const draftId = state.frame?.draftId ?? `${job.$id}-${AI_SITE_IMPORT_FRAME_SLOT}`\n const creation: AiBuildPlanCreate = {\n kind: 'layout',\n name: 'Header and footer',\n why: `The header and footer every page brought in from ${state.domain} renders inside.`,\n duplicateOf: null,\n fields: ['header', 'nav', 'main', 'footer'],\n id: draftId,\n }\n return {\n ...job,\n $id: draftId,\n kind: 'layout',\n steps: [],\n outputs: [],\n items: null,\n siteImport: null,\n plan: unitPlan(job, { create: [creation] }),\n inputs: {\n originJobId: job.$id,\n businessName: state.siteName,\n [AI_LAYOUT_LANGUAGE_INPUT]: true,\n [AI_LAYOUT_SITE_PAGES_INPUT]: aiSiteImportLinkedPages(state, items, wave.index),\n },\n brief: `Build the header and footer of “${state.siteName}”, a website brought in from ${state.domain}.`.slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/** One page's job: a page job's plan of one screen, its sections from the old page's headings, its text fenced. */\nexport function aiSiteImportPageJob(\n job: AiJob,\n state: AiSiteImportState,\n page: AiSiteImportPage,\n items: readonly AiJobItemLedger[],\n): AiJob {\n const sections = (page.read?.sections.length ? page.read.sections : ['Introduction']).map((name) => ({\n name,\n uses: [],\n items: 0,\n }))\n const screen: AiBuildPlanScreen = {\n title: page.title,\n slug: pathOf(page.slug),\n // Inside the header and footer this import built, once it is in.\n layout: state.frame && delivered(items, AI_SITE_IMPORT_FRAME_SLOT) ? state.frame.draftId : null,\n template: null,\n duplicateOf: null,\n nav: false,\n seoTitle: page.title,\n seoDescription: '',\n sections,\n record: null,\n id: page.draftId,\n }\n // The other pages already in, whose stock photos this page does not repeat.\n const others = state.pages.filter((entry) => entry.slot !== page.slot && delivered(items, entry.slot)).map((entry) => entry.draftId)\n return {\n ...job,\n $id: page.draftId,\n kind: 'page',\n steps: [],\n outputs: [],\n items: null,\n siteImport: null,\n plan: unitPlan(job, { screens: [screen] }),\n inputs: {\n originJobId: job.$id,\n businessName: state.siteName,\n [AI_LAYOUT_LANGUAGE_INPUT]: true,\n [AI_LAYOUT_SITE_PAGES_INPUT]: aiSiteImportLinkedPages(state, items, page.wave),\n [AI_LAYOUT_IMPORTED_PAGE_INPUT]: { url: page.url, text: page.read?.text ?? '' },\n [AI_LAYOUT_PAGE_PHOTOS_INPUT]: page.photos?.assets ?? [],\n ...(others.length ? { [AI_STOCK_PHOTO_PAGES_INPUT]: others } : {}),\n },\n brief: `Bring in the page “${page.title}” of ${state.domain} as a page of “${state.siteName}”.`.slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/** The least time the import's next pass needs: a read's, a photo pass's, or the time of the step its unit is handed to. */\nexport function aiSiteImportRunMinimumMs(job: AiJob, runnerFor: typeof aiJobStepRunnerFor = aiJobStepRunnerFor): number {\n const state = job.siteImport ?? null\n const items = job.items ?? []\n const work = aiSiteImportNextWork(state, items)\n switch (work.kind) {\n case 'inventory':\n return AI_SITE_IMPORT_PASS_MINIMUM_MS.read\n case 'start-wave':\n case 'read':\n return AI_SITE_IMPORT_PASS_MINIMUM_MS.wave\n case 'images':\n return AI_SITE_IMPORT_PASS_MINIMUM_MS.photos\n case 'frame':\n return runnerFor('layout') && state ? aiJobStepRunMinimumMs(aiSiteImportFrameJob(job, state, work.wave, items)) : 0\n case 'page':\n return runnerFor('page') && state ? aiJobStepRunMinimumMs(aiSiteImportPageJob(job, state, work.page, items)) : 0\n default:\n return 0\n }\n}\n\n/*------------------------------------------\n * The step\n *-----------------------------------------*/\n\nconst waveReview = (message: string): AiJobReview => ({ reason: 'wave', message, findings: [] })\n\n/** The rows a wave's read left out, settled as not brought in, with the reason. */\nfunction withLeftOutRows(\n items: readonly AiJobItemLedger[],\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiJobItemLedger[] {\n return items.map((row) => {\n const read = reads.get(row.slot)\n return typeof read === 'string' && (row.status === 'pending' || row.status === 'running')\n ? { ...row, status: 'skipped', note: AI_SITE_IMPORT_LEFT_OUT_COPY[read] }\n : row\n })\n}\n\n/** Whether every row of the wave is settled, its header and footer included. */\nfunction waveSettled(state: AiSiteImportState, wave: AiSiteImportWave, items: readonly AiJobItemLedger[]): boolean {\n if (wave.frame && state.frame && aiSiteImportItemOpen(items, AI_SITE_IMPORT_FRAME_SLOT)) return false\n return wave.slots.every((slot) => !aiSiteImportItemOpen(items, slot))\n}\n\nexport function createAiJobImportStep(deps: AiSiteImportStepDeps = {}): AiJobStepRunner {\n const crawl = deps.crawl ?? crawlSiteForImport\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n const ownershipOf = deps.ownership ?? readOwnership\n const rateLimit = deps.rateLimit ?? consumeRateLimit\n const flagOn = deps.releaseFlagOn ?? ((orgId: string) => isServerReleaseFlagOnForOrg(SITE_IMPORT_RELEASE_FLAG, orgId))\n const ingestOf = deps.ingest ?? pluginMediaIngest\n const readRoom = deps.readRoom ?? readAiScreenRoom\n const siteNameOf = deps.readSiteName ?? readSiteName\n const fileReview = deps.fileReview ?? fileSiteImportReview\n const pageWritten = deps.pageWritten ?? aiSitePageWritten\n const mintId = deps.mintId ?? createResourceUid\n const clock = deps.clock ?? Date.now\n\n return async (context: AiJobStepContext): Promise<AiJobStepOutcome> => {\n const { job, firestore, now, signal } = context\n const model = aiModelForStep('job.page')\n const unspent = (rest: Partial<AiJobStepOutcome> = {}): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), ...rest })\n const hostId = job.hostId\n if (!hostId) return unspent({ failure: AI_SITE_IMPORT_COPY.noSite })\n const domain = aiSiteImportDomainOf(job)\n if (!domain) return unspent({ failure: AI_SITE_IMPORT_COPY.badDomain })\n const state = job.siteImport ?? null\n const items = job.items ?? []\n const work = aiSiteImportNextWork(state, items)\n const nowIso = now.toISOString()\n const review = (event: 'read' | 'wave-started' | 'wave-finished', next: AiSiteImportState, ledger: readonly AiJobItemLedger[], wave?: number) =>\n fileReview(firestore, { orgId: job.orgId, hostId, jobId: job.$id, state: next, items: ledger, event, nowMs: now.getTime(), ...(wave !== undefined ? { wave } : {}) })\n\n /** Closes the wave when this pass settled its last row: the review, or the job's end after the last wave. */\n const closing = async (\n next: AiSiteImportState,\n ledger: readonly AiJobItemLedger[],\n wave: AiSiteImportWave,\n outcome: AiJobStepOutcome,\n ): Promise<AiJobStepOutcome> => {\n if (!waveSettled(next, wave, ledger)) return { ...outcome, siteImport: next, continue: true }\n const closed = aiSiteImportWaveClosed(next, wave.index, nowIso)\n await review('wave-finished', closed, ledger, wave.index)\n const waiting = closed.waves.find((entry) => entry.index === wave.index)?.status === 'review'\n return waiting\n ? { ...outcome, siteImport: closed, review: waveReview(aiSiteImportWaveMessage(closed, ledger)) }\n : { ...outcome, siteImport: closed }\n }\n\n /** The proof, the flag and the day's limit, asked before anything is read. */\n const gate = async (): Promise<{ ownership: AiSiteImportOwnership } | AiJobStepOutcome> => {\n if (!(await flagOn(job.orgId))) return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.unavailable) })\n const ownership = await ownershipOf(job.orgId, domain)\n if (!ownership?.current) {\n return unspent({ review: { reason: 'ownership', message: AI_SITE_IMPORT_COPY.ownership(domain), findings: [] } })\n }\n const window = await rateLimit(siteImportRateKey(job.orgId), { limit: SITE_IMPORT_WAVES_PER_ORG_PER_DAY, windowMs: DAY_MS })\n if (!window.allowed) return unspent({ review: { reason: 'limit', message: AI_SITE_IMPORT_COPY.limit, findings: [] } })\n return { ownership }\n }\n\n // ── 1. Read the site ──\n if (work.kind === 'inventory') {\n const passed = await gate()\n if (!('ownership' in passed)) return passed\n const room = await readRoom(firestore, { hostId, org: (context.org ?? null) as never })\n const left = room.limit === null ? AI_SITE_IMPORT_MAX_PAGES : Math.max(0, room.limit - room.used)\n const crawled = await crawl(\n {\n domain,\n // A wave's worth past the room, so what the plan leaves out is said.\n maxPages: Math.min(AI_SITE_IMPORT_MAX_PAGES, left + AI_SITE_IMPORT_WAVE_PAGES),\n timeBudgetMs: AI_SITE_IMPORT_READ_MS,\n shallowFirst: true,\n ...(signal ? { signal } : {}),\n },\n deps.crawlSeams,\n )\n if (crawled.refused) return unspent({ failure: AI_SITE_IMPORT_COPY.badDomain })\n if (crawled.stoppedBy === 'robots-unreachable') return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.robots(domain)) })\n const skipped = crawled.skipped.map((entry) => ({ url: entry.url, reason: siteImportLeftOutReason(entry.reason, entry.status) }))\n if (!crawled.pages.length) {\n // A site its bot protection keeps AglynBot off is up: say what lets AglynBot in, not to check that it is.\n const blocked = skipped.some((entry) => entry.reason === 'blocked')\n return unspent({ review: waveReview(AI_SITE_IMPORT_COPY[blocked ? 'blocked' : 'nothing'](domain)) })\n }\n const inventory = aiSiteImportInventory({\n domain,\n siteName: await siteNameOf(firestore, hostId),\n ownership: { method: passed.ownership.method, provenAt: passed.ownership.provenAt, detail: passed.ownership.detail },\n readAt: nowIso,\n pages: crawled.pages.map(siteImportListedPage),\n menu: siteImportMenu(crawled),\n skipped,\n skippedOverflow: crawled.skippedOverflow,\n stoppedBy: crawled.stoppedBy,\n pageLimit: room.limit,\n existingPages: room.used,\n mintId,\n })\n const ledger = aiSiteImportLedger(inventory)\n const reviewId = await review('read', inventory, ledger)\n const next = { ...inventory, reviewId }\n return unspent({ items: ledger, siteImport: next, review: waveReview(aiSiteImportWaveMessage(next, ledger)) })\n }\n\n if (!state) return unspent({ failure: AI_SITE_IMPORT_COPY.badDomain })\n if (work.kind === 'done') return unspent()\n\n // ── 2. Start a wave ──\n if (work.kind === 'start-wave') {\n const passed = await gate()\n if (!('ownership' in passed)) return passed\n const wave = work.wave\n const pages = wave.slots\n .map((slot) => state.pages.find((page) => page.slot === slot))\n .filter((page): page is AiSiteImportPage => !!page)\n const reads = new Map<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>()\n // Never past the plan: the room is read again, since pages may have been added since the site was read.\n const room = await readRoom(firestore, { hostId, org: (context.org ?? null) as never })\n let left = room.limit === null ? Number.POSITIVE_INFINITY : Math.max(0, room.limit - room.used)\n const toRead: AiSiteImportPage[] = []\n for (const page of pages) {\n if (left > 0) {\n toRead.push(page)\n left -= 1\n } else {\n reads.set(page.slot, 'plan-limit')\n }\n }\n if (toRead.length) {\n const crawled = await crawl(\n {\n domain,\n maxPages: toRead.length,\n only: toRead.map((page) => page.url),\n timeBudgetMs: AI_SITE_IMPORT_WAVE_READ_MS,\n ...(signal ? { signal } : {}),\n },\n deps.crawlSeams,\n )\n if (crawled.stoppedBy === 'robots-unreachable') return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.robots(domain)) })\n // A read the clock cut short leaves its other pages for the next pass, never \"unreadable\".\n for (const [slot, read] of siteImportWaveReads(toRead, crawled, nowIso, { cutShort: crawled.stoppedBy === 'time' })) {\n reads.set(slot, read)\n }\n }\n let ledger = withLeftOutRows(items, reads)\n // A wave none of whose pages could be read builds no header for them.\n const anyRead = [...reads.values()].some((read) => typeof read !== 'string')\n if (wave.frame && !anyRead && reads.size === pages.length) {\n ledger = ledger.map((row) =>\n row.slot === AI_SITE_IMPORT_FRAME_SLOT && (row.status === 'pending' || row.status === 'running')\n ? { ...row, status: 'skipped', note: 'Not built: no page of this wave could be read.' }\n : row,\n )\n }\n const started = aiSiteImportWaveStarted(state, wave.index, nowIso, reads)\n await review('wave-started', started, ledger, wave.index)\n const running = started.waves.find((entry) => entry.index === wave.index) as AiSiteImportWave\n return closing(started, ledger, running, unspent({ items: ledger }))\n }\n\n const wave = work.wave\n\n // ── 2b. The rest of a wave's read, where the clock cut it short ──\n if (work.kind === 'read') {\n const crawled = await crawl(\n {\n domain,\n maxPages: work.pages.length,\n only: work.pages.map((page) => page.url),\n timeBudgetMs: AI_SITE_IMPORT_WAVE_READ_MS,\n ...(signal ? { signal } : {}),\n },\n deps.crawlSeams,\n )\n if (crawled.stoppedBy === 'robots-unreachable') return unspent({ review: waveReview(AI_SITE_IMPORT_COPY.robots(domain)) })\n const reads = siteImportWaveReads(work.pages, crawled, nowIso, { cutShort: crawled.stoppedBy === 'time' })\n const ledger = withLeftOutRows(items, reads)\n return closing(aiSiteImportPagesRead(state, reads), ledger, wave, unspent({ items: ledger }))\n }\n\n // ── 3. The header and footer ──\n if (work.kind === 'frame') {\n const runner = runnerFor('layout')\n if (!runner) {\n const item: AiJobItemOutcome = { slot: AI_SITE_IMPORT_FRAME_SLOT, status: 'skipped', note: AI_SITE_IMPORT_COPY.frameUnavailable }\n return closing(state, aiApplyJobItemRecord(items, item, 0, now), wave, unspent({ item }))\n }\n const derived = aiSiteImportFrameJob(job, state, wave, items)\n return runUnit(AI_SITE_IMPORT_FRAME_SLOT, runner, derived, state, null)\n }\n\n // ── 4a. A page's photos ──\n if (work.kind === 'images') {\n const photos = signal?.aborted\n ? { brought: 0, reused: 0, skipped: work.page.read?.images.length ?? 0, assets: [] }\n : await bringInSiteImportPhotos(\n {\n hostId,\n uid: job.createdBy,\n domain,\n images: work.page.read?.images ?? [],\n deadlineAt: clock() + AI_SITE_IMPORT_PHOTOS_MS,\n ...(signal ? { signal } : {}),\n },\n { ingest: ingestOf(), ...(deps.fetchImage ? { fetch: deps.fetchImage } : {}) },\n )\n return unspent({\n item: { slot: work.page.slot, status: 'running' },\n siteImport: aiSiteImportPhotosIn(state, work.page.slot, photos),\n continue: true,\n })\n }\n\n // ── 4b. The page ──\n if (work.kind === 'page') {\n const runner = runnerFor('page')\n if (!runner) {\n const item: AiJobItemOutcome = {\n slot: work.page.slot,\n status: 'failed',\n failure: { ours: true, reason: 'unavailable', message: AI_SITE_IMPORT_COPY.pageUnavailable },\n }\n return closing(state, aiApplyJobItemRecord(items, item, 0, now), wave, unspent({ item }))\n }\n return runUnit(work.page.slot, runner, aiSiteImportPageJob(job, state, work.page, items), state, work.page)\n }\n\n // ── 5. Close the wave ──\n return closing(state, items, wave, unspent())\n\n /** One delegated pass: the unit's spend is the pass's, what it came to is its row. */\n async function runUnit(\n slot: string,\n runner: AiJobStepRunner,\n derived: AiJob,\n current: AiSiteImportState,\n page: AiSiteImportPage | null,\n ): Promise<AiJobStepOutcome> {\n let outcome: AiJobStepOutcome\n try {\n outcome = await runner({ ...context, job: derived })\n } catch (error) {\n // A provider that will answer later, a budget that ended, a cancel: the machine's.\n if (aiUnitErrorRetryable(error) || (signal?.aborted ?? false)) throw error\n console.error('ai site import unit threw', { orgId: job.orgId, jobId: job.$id, slot, error })\n const item: AiJobItemOutcome = {\n slot,\n status: 'failed',\n failure: { ours: true, reason: 'provider', message: AI_SITE_IMPORT_COPY.pageNotWritten },\n }\n return closing(current, aiApplyJobItemRecord(items, item, 0, now), wave, unspent({ item }))\n }\n const spent = aiUnitSpend(outcome)\n const stopped = aiUnitFailure(slot, outcome)\n if (stopped?.status === 'running') return { ...spent, outputs: [], item: stopped, siteImport: current, continue: true }\n let item: AiJobItemOutcome\n if (stopped) {\n item = stopped\n } else if (page && derived.plan) {\n // A page counts as in only when its sections are in it (AGL-3596).\n const screen = derived.plan.screens[0]\n const written = await pageWritten(firestore, {\n hostId: hostId as string,\n unitJobId: derived.$id,\n screen,\n draftId: page.draftId,\n layoutLanguage: true,\n })\n item = written\n ? { slot, status: 'succeeded', outputs: spent.outputs.map((output) => output.id) }\n : { slot, status: 'failed', failure: { ours: true, reason: 'step-failure', message: AI_SITE_IMPORT_COPY.pageNotWritten } }\n } else {\n item = { slot, status: 'succeeded', outputs: spent.outputs.map((output) => output.id) }\n }\n const settledState = page ? aiSiteImportPageSettled(current, slot) : current\n return closing(\n settledState,\n aiApplyJobItemRecord(items, item, 0, now),\n wave,\n { ...spent, outputs: item.status === 'succeeded' ? spent.outputs : [], item },\n )\n }\n }\n}\n\n/** The home page's menu, as same-site paths in its order. */\nexport function siteImportMenu(crawled: Pick<SiteImportInventory, 'pages' | 'origins'>): string[] {\n const home = crawled.pages.find((page) => page.foundBy === 'home') ?? crawled.pages[0]\n const paths: string[] = []\n for (const link of home?.navLinks ?? []) {\n try {\n const url = new URL(link.url)\n if (!crawled.origins.includes(url.origin)) continue\n const path = url.pathname.replace(/\\/+$/, '') || '/'\n if (!paths.includes(path)) paths.push(path)\n } catch {\n // A menu entry that is not an address names no page.\n }\n }\n return paths\n}\n\n/**\n * What a wave's read came to for each page: the page read again, or why it\n * is left out now. Matched by address; a page the site sent to another of its\n * own addresses is matched in the order the wave asked for it.\n */\nexport function siteImportWaveReads(\n asked: readonly AiSiteImportPage[],\n crawled: Pick<SiteImportInventory, 'pages' | 'skipped'>,\n at: string,\n options: {\n /** The read's clock ran out: a page it did not reach is left for the next pass, not left out. */\n cutShort?: boolean\n } = {},\n): Map<string, AiSiteImportPageRead | AiSiteImportLeftOutReason> {\n const reads = new Map<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>()\n const pathKey = (url: string) => {\n try {\n const parsed = new URL(url)\n return `${parsed.pathname.replace(/\\/+$/, '') || '/'}${parsed.search}`\n } catch {\n return url\n }\n }\n const pages = new Map(crawled.pages.map((page) => [pathKey(page.url), page]))\n const skipped = new Map(crawled.skipped.map((entry) => [pathKey(entry.url), entry]))\n const unmatched = [...crawled.pages]\n const take = (page: (typeof crawled.pages)[number]) => {\n const index = unmatched.indexOf(page)\n if (index !== -1) unmatched.splice(index, 1)\n }\n const pending: AiSiteImportPage[] = []\n for (const page of asked) {\n const found = pages.get(pathKey(page.url))\n if (found) {\n take(found)\n reads.set(page.slot, siteImportReadOrEmpty(found, at))\n continue\n }\n const left = skipped.get(pathKey(page.url))\n if (left) {\n reads.set(page.slot, siteImportLeftOutReason(left.reason, left.status))\n continue\n }\n pending.push(page)\n }\n for (const page of pending) {\n const found = unmatched.shift()\n if (found) reads.set(page.slot, siteImportReadOrEmpty(found, at))\n else if (!options.cutShort) reads.set(page.slot, 'unreadable')\n }\n return reads\n}\n\nfunction siteImportReadOrEmpty(page: SiteImportInventory['pages'][number], at: string): AiSiteImportPageRead | 'empty' {\n if (siteImportPageWords(page) < AI_SITE_IMPORT_MIN_WORDS && !page.images.length) return 'empty'\n return siteImportPageRead(page, at)\n}\n\n/*------------------------------------------\n * Admission, and registration\n *-----------------------------------------*/\n\nexport interface AiSiteImportAdmissionDeps {\n releaseFlagOn?: (orgId: string) => Promise<boolean>\n ownership?: (orgId: string, domain: string) => Promise<AiSiteImportOwnership | null>\n /** \"Bring a site in\" (`ai.import`, AGL-4047) for this member; specs hand in a double. */\n holdsSiteImport?: (uid: string, orgId: string) => Promise<boolean>\n}\n\n/**\n * What an import asks before it exists and before each wave (AGL-3976): the\n * release flag, a site to bring it into, a paid plan, the owner or an admin,\n * and a CURRENT proof that the workspace owns the domain. Nothing is spent on\n * a refusal, and the day's limit is the step's to count, once a wave runs.\n */\nexport function createAiSiteImportAdmission(deps: AiSiteImportAdmissionDeps = {}): AiJobAdmission {\n const flagOn = deps.releaseFlagOn ?? ((orgId: string) => isServerReleaseFlagOnForOrg(SITE_IMPORT_RELEASE_FLAG, orgId))\n const ownershipOf = deps.ownership ?? readOwnership\n const holdsSiteImport =\n deps.holdsSiteImport ??\n (async (uid: string, orgId: string) => {\n const member = (await getOrgForUser(uid, orgId))?.member ?? null\n return Boolean(member) && (await memberHasOrgPermission(orgId, member, 'ai.import'))\n })\n return async (context) => {\n if (!(await flagOn(context.orgId))) return { status: 404, error: 'Not found' }\n if (!context.hostId && !context.siteToCome) return { status: 400, error: AI_SITE_IMPORT_COPY.noSite }\n if (resolveEffectivePlan(context.org as never) === 'free') return { status: 403, error: AI_SITE_IMPORT_COPY.plan }\n // \"Bring a site in\" (`ai.import`, AGL-4047), whose default is the owner\n // and admin this asked for.\n if (!context.uid || !(await holdsSiteImport(context.uid, context.orgId))) {\n return { status: 403, error: AI_SITE_IMPORT_COPY.role }\n }\n const { domain, error } = normalizeOwnershipDomain(String(context.inputs[AI_SITE_IMPORT_DOMAIN_INPUT] ?? ''))\n if (!domain) return { status: 400, error: error ?? AI_SITE_IMPORT_COPY.badDomain }\n const ownership = await ownershipOf(context.orgId, domain)\n if (!ownership?.current) return { status: 403, error: AI_SITE_IMPORT_COPY.ownership(domain) }\n return null\n }\n}\n\n/**\n * The import kind's registrations (AGL-3976), by a call from `server.ts`,\n * never by an import: its runner with each pass's least time, its pass bound\n * and its admission.\n */\nexport function registerAiSiteImportJob(deps: AiSiteImportStepDeps = {}): void {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n registerAiJobStep('import', createAiJobImportStep(deps), {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: (job) => aiSiteImportRunMinimumMs(job, runnerFor),\n })\n registerAiJobStepPasses('import', AI_SITE_IMPORT_MAX_PASSES)\n registerAiJobAdmission('import', createAiSiteImportAdmission())\n}\n"],"names":["resolveEffectivePlan","createResourceUid","pluginMediaIngest","listDomainOwnershipClaims","normalizeOwnershipDomain","getOrgForUser","memberHasOrgPermission","consumeRateLimit","isServerReleaseFlagOnForOrg","AI_SITE_IMPORT_DOMAIN_INPUT","AI_SITE_IMPORT_FRAME_SLOT","AI_SITE_IMPORT_LEFT_OUT_COPY","AI_SITE_IMPORT_MAX_PAGES","AI_SITE_IMPORT_MIN_WORDS","AI_SITE_IMPORT_WAVE_PAGES","aiSiteImportInventory","aiSiteImportItemOpen","aiSiteImportLedger","aiSiteImportNextWork","aiSiteImportPageSettled","aiSiteImportPagesRead","aiSiteImportPhotosIn","aiSiteImportWaveClosed","aiSiteImportWaveMessage","aiSiteImportWaveStarted","aiModelForStep","SITE_IMPORT_RELEASE_FLAG","crawlSiteForImport","bringInSiteImportPhotos","siteImportLeftOutReason","siteImportListedPage","siteImportPageRead","siteImportPageWords","fileSiteImportReview","aiUnitErrorRetryable","aiUnitFailure","aiUnitSpend","registerAiJobAdmission","AI_JOB_STEP_OVERHEAD_MS","readAiScreenRoom","aiUnspentOutcome","AI_LAYOUT_PAGE_PHOTOS_INPUT","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","AI_JOB_PAGE_STEP_MINIMUM_MS","AI_LAYOUT_IMPORTED_PAGE_INPUT","AI_LAYOUT_LANGUAGE_INPUT","aiSitePageWritten","AI_STOCK_PHOTO_PAGES_INPUT","AI_JOB_BRIEF_MAX_CHARS","aiApplyJobItemRecord","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","SITE_IMPORT_WAVES_PER_ORG_PER_DAY","DAY_MS","siteImportRateKey","orgId","AI_SITE_IMPORT_READ_MS","AI_SITE_IMPORT_WAVE_READ_MS","AI_SITE_IMPORT_PHOTOS_MS","OWN_READS_MS","AI_SITE_IMPORT_PASS_MINIMUM_MS","read","wave","photos","AI_SITE_IMPORT_MAX_PASSES","AI_SITE_IMPORT_COPY","noSite","badDomain","ownership","domain","limit","robots","nothing","blocked","unavailable","frameUnavailable","pageUnavailable","pageNotWritten","plan","role","readOwnership","claims","claim","find","entry","current","method","provenMethod","provenAt","detail","provenDetail","readSiteName","firestore","hostId","host","collection","doc","get","name","trim","aiSiteImportDomainOf","job","String","inputs","pathOf","slug","unitPlan","input","reuse","create","screens","labels","status","proposedAt","createdAt","confirmedAt","confirmedBy","createdBy","delivered","items","slot","some","row","aiSiteImportLinkedPages","state","rows","Map","map","pages","filter","page","refused","id","draftId","label","title","slice","aiSiteImportFrameJob","frame","$id","creation","kind","why","duplicateOf","fields","steps","outputs","siteImport","originJobId","businessName","siteName","index","brief","aiSiteImportPageJob","sections","length","uses","screen","layout","template","nav","seoTitle","seoDescription","record","others","url","text","assets","aiSiteImportRunMinimumMs","runnerFor","work","waveReview","message","reason","findings","withLeftOutRows","reads","note","waveSettled","slots","every","createAiJobImportStep","deps","crawl","ownershipOf","rateLimit","flagOn","releaseFlagOn","ingestOf","ingest","readRoom","siteNameOf","fileReview","pageWritten","mintId","clock","Date","now","context","signal","model","unspent","rest","failure","nowIso","toISOString","review","event","next","ledger","jobId","nowMs","getTime","undefined","closing","outcome","closed","continue","waiting","waves","gate","window","windowMs","allowed","passed","room","org","left","Math","max","used","crawled","maxPages","min","timeBudgetMs","shallowFirst","crawlSeams","stoppedBy","skipped","inventory","readAt","menu","siteImportMenu","skippedOverflow","pageLimit","existingPages","reviewId","Number","POSITIVE_INFINITY","toRead","push","set","only","siteImportWaveReads","cutShort","anyRead","values","size","started","running","runner","item","derived","runUnit","aborted","brought","reused","images","uid","deadlineAt","fetchImage","fetch","ours","error","console","spent","stopped","written","unitJobId","layoutLanguage","output","settledState","home","foundBy","paths","link","navLinks","URL","origins","includes","origin","path","pathname","replace","asked","at","options","pathKey","parsed","search","unmatched","take","indexOf","splice","pending","found","siteImportReadOrEmpty","shift","createAiSiteImportAdmission","holdsSiteImport","member","Boolean","siteToCome","registerAiSiteImportJob","minimumMs","minimumMsFor"],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures';
|
|
18
18
|
import { type AiInsightTable } from '../model/ai-insight';
|
|
19
19
|
import { type AiSystemBlock } from '../runtime/ai-runtime';
|
|
20
|
+
import type { AiTool } from '../providers/contract';
|
|
20
21
|
import { type AiInsightCheck } from '../runtime/ai-insight-check';
|
|
21
22
|
import { type AiJobAdmissionContext, type AiJobAdmissionRefusal } from './ai-job-admission';
|
|
22
23
|
import type { AiJobStepRunner } from './ai-job-text-step';
|
|
@@ -31,6 +32,15 @@ export declare const AI_INSIGHT_DIGEST_QUESTION = "What changed on this site las
|
|
|
31
32
|
export declare const AI_INSIGHT_RULES: string;
|
|
32
33
|
/** The system blocks both calls send: the rules and the platform's acceptable-use rules. */
|
|
33
34
|
export declare const AI_JOB_INSIGHT_SYSTEM: AiSystemBlock[];
|
|
35
|
+
/**
|
|
36
|
+
* The tools BOTH calls send, in this order (AGL-4076). The provider's cached
|
|
37
|
+
* prefix opens with the tools, then the system blocks, so a call that sends
|
|
38
|
+
* other tools than the call before it reads none of the system block that
|
|
39
|
+
* call wrote: the answer call, sent the answer tool alone after a read call
|
|
40
|
+
* sent the read tool alone, read nothing on every insight. Each call names
|
|
41
|
+
* the one it is for (`toolChoice`), and its user turn says so.
|
|
42
|
+
*/
|
|
43
|
+
export declare function aiInsightTools(): AiTool[];
|
|
34
44
|
/** The read call's user turn: the question, the window asked for, the readers and, for datasets, the datasets. */
|
|
35
45
|
export declare function aiInsightReadPrompt(input: {
|
|
36
46
|
question: string;
|
|
@@ -58,6 +58,19 @@ export const AI_INSIGHT_UNREADABLE_COPY = 'The AI did not answer with insights.
|
|
|
58
58
|
cacheBreakpoint: true
|
|
59
59
|
}
|
|
60
60
|
];
|
|
61
|
+
/**
|
|
62
|
+
* The tools BOTH calls send, in this order (AGL-4076). The provider's cached
|
|
63
|
+
* prefix opens with the tools, then the system blocks, so a call that sends
|
|
64
|
+
* other tools than the call before it reads none of the system block that
|
|
65
|
+
* call wrote: the answer call, sent the answer tool alone after a read call
|
|
66
|
+
* sent the read tool alone, read nothing on every insight. Each call names
|
|
67
|
+
* the one it is for (`toolChoice`), and its user turn says so.
|
|
68
|
+
*/ export function aiInsightTools() {
|
|
69
|
+
return [
|
|
70
|
+
aiInsightReadTool(),
|
|
71
|
+
aiInsightAnswerTool()
|
|
72
|
+
];
|
|
73
|
+
}
|
|
61
74
|
const ZERO_USAGE = {
|
|
62
75
|
inputTokens: 0,
|
|
63
76
|
outputTokens: 0,
|
|
@@ -102,7 +115,9 @@ const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
|
102
115
|
'',
|
|
103
116
|
'Datasets you may read, with their fields:',
|
|
104
117
|
input.datasets
|
|
105
|
-
]
|
|
118
|
+
],
|
|
119
|
+
'',
|
|
120
|
+
`Call ${AI_INSIGHT_READ_TOOL_NAME} now.`
|
|
106
121
|
].join('\n');
|
|
107
122
|
}
|
|
108
123
|
/** The answer call's user turn: the question and every table. */ export function aiInsightAnswerPrompt(question, tables) {
|
|
@@ -111,7 +126,9 @@ const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
|
111
126
|
'',
|
|
112
127
|
'Tables:',
|
|
113
128
|
'',
|
|
114
|
-
tables.map(aiInsightTableText).join('\n\n')
|
|
129
|
+
tables.map(aiInsightTableText).join('\n\n'),
|
|
130
|
+
'',
|
|
131
|
+
`These are all the tables there are. Call ${AI_INSIGHT_ANSWER_TOOL_NAME} now.`
|
|
115
132
|
].join('\n');
|
|
116
133
|
}
|
|
117
134
|
/** What a re-ask tells the model: why nothing it wrote could be kept. */ export function aiInsightReaskMessage(check) {
|
|
@@ -197,13 +214,14 @@ export const runAiJobInsightStep = async (context)=>{
|
|
|
197
214
|
];
|
|
198
215
|
// A development run's effort A/B arm (AGL-3781), else the row's.
|
|
199
216
|
const effort = (_aiDevEffortFor = aiDevEffortFor('job.insight')) != null ? _aiDevEffortFor : AI_ROUTING_TABLE['job.insight'].effort;
|
|
217
|
+
// Both tools on both calls, so the answer call reads the prefix the read call wrote (AGL-4076).
|
|
218
|
+
const tools = aiInsightTools();
|
|
200
219
|
const ask = (messages, tool, ceiling)=>runAiRequest(_extends({
|
|
201
220
|
model,
|
|
202
221
|
system,
|
|
203
222
|
messages,
|
|
204
|
-
tools
|
|
205
|
-
|
|
206
|
-
],
|
|
223
|
+
tools,
|
|
224
|
+
toolChoice: tool === 'read' ? AI_INSIGHT_READ_TOOL_NAME : AI_INSIGHT_ANSWER_TOOL_NAME,
|
|
207
225
|
maxTokens: Math.max(1, Math.min(ceiling, left()))
|
|
208
226
|
}, AI_ROUTING_TABLE['job.insight'].thinking ? {
|
|
209
227
|
thinking: AI_ROUTING_TABLE['job.insight'].thinking
|