@aglyn/plugins-ai 1.0.0-beta.284 → 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/model/ai-job-notice.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 type { AglynNotification } from '@aglyn/aglyn/app-utils/notifications'\nimport { aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport { aiJobKindNoun } from './ai-job-activity'\nimport { aiCustomerSafeCopy, aiJobRefundCopy } from './ai-job-failure-copy'\nimport type { AiJobItemLedger, AiJobKind, AiJobOutput, AiJobReviewReason, AiJobSitePublish } from './ai-jobs.types'\n\n/** The besigner segment each versioned resource lives under. */\nexport const AI_JOB_BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'screens',\n reusableComponent: 'components',\n layout: 'layouts',\n template: 'templates',\n // An email design is a screen: it opens in the screen besigner, as the\n // Emails page's own Edit design does.\n emailScreen: 'screens',\n}\n\n/**\n * The query parameter a console address carries to open AI jobs on one job\n * (AGL-3593): the Assist panel reads it on arrival.\n */\nexport const AI_JOB_LINK_PARAM = 'aiJob'\n\n/** Where a site's \"Building your site\" page lives, under the site (AGL-3594). */\nexport const AI_SITE_BUILD_HREF = '/ai-jobs'\n\n/**\n * What a job the meter paused says comes next (AGL-3660), on its notice and\n * its page alike: nothing it built is lost, and it carries on from where it\n * stopped — by Resume once there are credits, or by itself when they return.\n */\nexport const AI_JOB_PAUSED_NEXT_COPY =\n 'Nothing is lost: what it built so far is saved. Once you have AI credits again — from Billing, ' +\n 'or when your monthly credits reset — choose Resume, or it carries on by itself within about an hour.'\n\n/** A paused job's heading, by what it builds. */\nexport function aiJobPausedTitle(kind: AiJobKind): string {\n const noun = aiJobKindNoun(kind)\n return noun === 'AI job' ? 'Your AI job is paused' : `Your ${noun} ${noun.endsWith('s') ? 'are' : 'is'} paused`\n}\n\n/** A change the person who started a job is told about. */\nexport type AiJobNoticeTransition = 'needs-review' | 'done' | 'failed' | 'paused'\n\n/** The fields of a stored job a notice is written from. */\nexport interface AiJobNoticeSource {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n review?: { reason: AiJobReviewReason; message?: string | null } | null\n outputs?: readonly AiJobOutput[] | null\n error?: string | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: Pick<AiJobSitePublish, 'published' | 'drafts'> | null\n /** What the job spent, and gave back for a failure on our side (AGL-3596). */\n creditsSpent?: number\n refundedCredits?: number\n /** A build's or a guided start's items (AGL-3616), so a finished one says what was not built. */\n items?: AiJobItemLedger[] | null\n /** What a job waiting in `needs_input` asks the person (AGL-3978). */\n input?: { message: string } | null\n}\n\n/** A stopped job's sentence with what became of its credits, from the job's recorded give-back (AGL-3596). */\nfunction withRefund(job: AiJobNoticeSource, text: string, status: 'failed' | 'needs_review'): string {\n const refund = aiJobRefundCopy({ status, refundedCredits: job.refundedCredits, creditsSpent: job.creditsSpent })\n return refund ? `${text} ${refund}` : text\n}\n\nexport type AiJobNotice = Pick<\n AglynNotification,\n 'type' | 'title' | 'body' | 'link' | 'orgId' | 'hostId' | 'level'\n>\n\n/**\n * Where a notice's link goes, in the stored form the console rewrites when it\n * is followed (`normalizeNotificationLink`): `/{hostId}/…` for the job's site,\n * `/org…` for a job of the workspace's.\n *\n * A job that is done leads to what it built (AGL-3593): several pages of the\n * job's site open that site's Pages list, one versioned output opens in the\n * editor on the version the job wrote. Everything else — a plan to confirm, a\n * job that stopped, a done job with nothing of its own to open — opens AI\n * jobs on the job.\n */\nexport function aiJobNoticeLink(job: AiJobNoticeSource, to: AiJobNoticeTransition): string {\n const base = job.hostId ? `/${job.hostId}` : '/org'\n // A site job's every notice opens its \"Building your site\" page (AGL-3594),\n // which leads with the next step whatever the job's state.\n // A site import's too (AGL-3976): its waves are reviewed and started there.\n if ((job.kind === 'site' || job.kind === 'import') && job.hostId) {\n return `${base}${AI_SITE_BUILD_HREF}/${encodeURIComponent(job.$id)}`\n }\n if (to === 'done' && job.hostId) {\n const own = (job.outputs ?? []).filter((output) => output.hostId === job.hostId)\n const screens = own.filter((output) => output.resource === 'screen')\n if (screens.length > 1) return `${base}/screens`\n const versioned = own.find(\n (output) => AI_JOB_BESIGNER_SEGMENT[output.resource] && output.versionId,\n )\n if (own.length === 1 && versioned) {\n const segment = AI_JOB_BESIGNER_SEGMENT[versioned.resource]\n return `${base}/${segment}/${versioned.id}/versions/${versioned.versionId}/besigner`\n }\n }\n return `${base}?${AI_JOB_LINK_PARAM}=${encodeURIComponent(job.$id)}`\n}\n\n/**\n * The in-app notification for one change of one job (AGL-3593), for the\n * person who created it. Customer-safe throughout: a stopped job's sentence is\n * the job's own `error`, which the machine writes as a fixed sentence.\n */\n/** The site a job worked on, as its notice names it: its name, and where it is live. */\nexport interface AiJobNoticeSite {\n name: string\n /** The site's live address, absolute; `null` when it has none yet. */\n url: string | null\n}\n\nexport function aiJobNotice(\n job: AiJobNoticeSource,\n to: AiJobNoticeTransition,\n site: AiJobNoticeSite | null = null,\n): AiJobNotice {\n const noun = aiJobKindNoun(job.kind)\n const scope = { orgId: job.orgId, ...(job.hostId ? { hostId: job.hostId } : {}) }\n const link = aiJobNoticeLink(job, to)\n if (to === 'needs-review') {\n // A plan that needs an ID only the person has first (AGL-3978).\n if (job.input?.message) {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : noun} needs one detail from you`,\n body: `${job.input.message} Nothing is built until you answer it and confirm the plan. It waits in AI jobs.`,\n link,\n ...scope,\n }\n }\n // A site import's wave is in, or its site is read (AGL-3976): a decision\n // waiting, said as one, never as a job that stopped.\n if (job.review?.reason === 'wave') {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'info',\n title: 'Your site import is ready for you',\n body: job.review.message || 'Review the pages brought in, then start the next wave when you are ready.',\n link,\n ...scope,\n }\n }\n if (job.review?.reason === 'plan') {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title:\n noun === 'AI job'\n ? 'Your plan is ready: confirm it to build'\n : `Your ${noun} plan is ready: confirm it to build`,\n body: 'Nothing is built until you confirm the plan. It waits in AI jobs.',\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} needs you`,\n // The doctrine's own words never reach a customer (AGL-3596).\n body: withRefund(\n job,\n aiCustomerSafeCopy(job.review?.message || job.error || 'It stopped for your decision. It waits in AI jobs.', { page: job.kind === 'page' }),\n 'needs_review',\n ),\n link,\n ...scope,\n }\n }\n if (to === 'paused') {\n // Paused by the meter (AGL-3660): out of credits or at a cap. Nothing is\n // lost and the job is not over, so it is a warning that names the way on.\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: aiJobPausedTitle(job.kind),\n body: [job.error, AI_JOB_PAUSED_NEXT_COPY].filter(Boolean).join(' '),\n link,\n ...scope,\n }\n }\n if (to === 'done') {\n // A job that finished with part of it unbuilt (AGL-3596) says so in the\n // words its page uses, as a warning: \"done\" alone hid a page that failed.\n const items = job.items ?? []\n const unbuilt = items.some((row) => row.status === 'failed' || row.status === 'skipped')\n const level = unbuilt ? 'warning' : 'success'\n const partial =\n job.kind === 'site'\n ? aiSitePartialCopy({ items, status: 'done' })\n : unbuilt\n ? aiBuildOutcomeLine({ items, status: 'done' })\n : null\n // A guided site start publishes its pages when it finishes (AGL-3596):\n // with one live, the site is, and the build page lists any that stayed\n // drafts with the reason for each.\n const published = job.kind === 'site' ? (job.sitePublish?.published.length ?? 0) : 0\n if (published > 0) {\n const drafts = job.sitePublish?.drafts.length ?? 0\n const stayed = drafts === 1 ? 'one that stayed a draft' : `${drafts} that stayed drafts`\n // Named, with its address (AGL-3889): someone with several sites, or\n // reading the email days later, cannot tell which site went live from\n // \"Your site is live\", nor where to see it.\n const live = site ? `${site.name} is live` : 'Your site is live'\n const at = site?.url ? ` at ${site.url}` : ''\n return {\n type: 'content.aiJobDone',\n level,\n title: partial ? `${live}, but part of it wasn’t built` : live,\n body: partial\n ? `${partial} ${drafts > 0 ? `What was built is published${at}, except ${stayed}.` : `What was built is published${at}.`}`\n : drafts > 0\n ? `Your pages are published${at}, except ${stayed}. Open the build page to see why.`\n : at\n ? `Your pages are published${at}. Visit your site there, or open it in the console to edit your pages.`\n : 'Your pages are published. Open it to view your site or edit your pages.',\n link,\n ...scope,\n }\n }\n const pages = (job.outputs ?? []).filter((output) => output.resource === 'screen').length\n const title =\n job.kind === 'site' || pages > 1\n ? 'Your site’s draft pages are ready'\n : job.kind === 'page'\n ? 'Your draft page is ready'\n : noun === 'AI job'\n ? 'Your AI job finished'\n : `Your ${noun} is ready`\n return {\n type: 'content.aiJobDone',\n level,\n title,\n body:\n job.kind === 'insight' || job.kind === 'text'\n ? 'Open AI jobs to read it.'\n : [partial, 'Everything it built is an unpublished draft until you publish it.'].filter(Boolean).join(' '),\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobFailed',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} stopped`,\n body: withRefund(job, aiCustomerSafeCopy(job.error || 'It stopped before it finished.', { page: job.kind === 'page' }), 'failed'),\n link,\n ...scope,\n }\n}\n"],"names":["aiBuildOutcomeLine","aiSitePartialCopy","aiJobKindNoun","aiCustomerSafeCopy","aiJobRefundCopy","AI_JOB_BESIGNER_SEGMENT","screen","reusableComponent","layout","template","emailScreen","AI_JOB_LINK_PARAM","AI_SITE_BUILD_HREF","AI_JOB_PAUSED_NEXT_COPY","aiJobPausedTitle","kind","noun","endsWith","withRefund","job","text","status","refund","refundedCredits","creditsSpent","aiJobNoticeLink","to","base","hostId","encodeURIComponent","$id","own","outputs","filter","output","screens","resource","length","versioned","find","versionId","segment","id","aiJobNotice","site","scope","orgId","link","input","message","type","level","title","body","review","reason","error","page","Boolean","join","items","unbuilt","some","row","partial","published","sitePublish","drafts","stayed","live","name","at","url","pages"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-notice.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 type { AglynNotification } from '@aglyn/aglyn/app-utils/notifications'\nimport { aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport { aiJobKindNoun } from './ai-job-activity'\nimport { aiCustomerSafeCopy, aiJobRefundCopy } from './ai-job-failure-copy'\nimport type { AiJobItemLedger, AiJobKind, AiJobOutput, AiJobReviewReason, AiJobSitePublish } from './ai-jobs.types'\n\n/** The besigner segment each versioned resource lives under. */\nexport const AI_JOB_BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'screens',\n reusableComponent: 'components',\n layout: 'layouts',\n template: 'templates',\n // An email design is a screen: it opens in the screen besigner, as the\n // Emails page's own Edit design does.\n emailScreen: 'screens',\n}\n\n/**\n * The query parameter a console address carries to open AI jobs on one job\n * (AGL-3593): the Assist panel reads it on arrival.\n */\nexport const AI_JOB_LINK_PARAM = 'aiJob'\n\n/** Where a site's \"Building your site\" page lives, under the site (AGL-3594). */\nexport const AI_SITE_BUILD_HREF = '/ai-jobs'\n\n/**\n * What a job the meter paused says comes next (AGL-3660), on its notice and\n * its page alike: nothing it built is lost, and it carries on from where it\n * stopped — by Resume once there are credits, or by itself when they return.\n */\nexport const AI_JOB_PAUSED_NEXT_COPY =\n 'Nothing is lost: what it built so far is saved. Once you have AI credits again — from Billing, ' +\n 'or when your monthly credits reset — choose Resume, or it carries on by itself within about an hour.'\n\n/** A paused job's heading, by what it builds. */\nexport function aiJobPausedTitle(kind: AiJobKind): string {\n const noun = aiJobKindNoun(kind)\n return noun === 'AI job' ? 'Your AI job is paused' : `Your ${noun} ${noun.endsWith('s') ? 'are' : 'is'} paused`\n}\n\n/** A change the person who started a job is told about. */\nexport type AiJobNoticeTransition = 'needs-review' | 'done' | 'failed' | 'paused'\n\n/** The fields of a stored job a notice is written from. */\nexport interface AiJobNoticeSource {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n review?: { reason: AiJobReviewReason; message?: string | null } | null\n outputs?: readonly AiJobOutput[] | null\n error?: string | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: Pick<AiJobSitePublish, 'published' | 'drafts'> | null\n /** What the job spent, and gave back for a failure on our side (AGL-3596). */\n creditsSpent?: number\n refundedCredits?: number\n /** A build's or a guided start's items (AGL-3616), so a finished one says what was not built. */\n items?: AiJobItemLedger[] | null\n /** What a job waiting in `needs_input` asks the person (AGL-3978). */\n input?: { message: string } | null\n}\n\n/** A stopped job's sentence with what became of its credits, from the job's recorded give-back (AGL-3596). */\nfunction withRefund(job: AiJobNoticeSource, text: string, status: 'failed' | 'needs_review'): string {\n const refund = aiJobRefundCopy({ status, refundedCredits: job.refundedCredits, creditsSpent: job.creditsSpent })\n return refund ? `${text} ${refund}` : text\n}\n\nexport type AiJobNotice = Pick<\n AglynNotification,\n 'type' | 'title' | 'body' | 'link' | 'orgId' | 'hostId' | 'level'\n>\n\n/**\n * Where a notice's link goes, in the stored form the console rewrites when it\n * is followed (`normalizeNotificationLink`): `/{hostId}/…` for the job's site,\n * `/org…` for a job of the workspace's.\n *\n * A job that is done leads to what it built (AGL-3593): several pages of the\n * job's site open that site's Pages list, one versioned output opens in the\n * editor on the version the job wrote. Everything else — a plan to confirm, a\n * job that stopped, a done job with nothing of its own to open — opens AI\n * jobs on the job.\n */\nexport function aiJobNoticeLink(job: AiJobNoticeSource, to: AiJobNoticeTransition): string {\n const base = job.hostId ? `/${job.hostId}` : '/org'\n // A site job's every notice opens its \"Building your site\" page (AGL-3594),\n // which leads with the next step whatever the job's state.\n // A site import's too (AGL-3976): its waves are reviewed and started there.\n if ((job.kind === 'site' || job.kind === 'import') && job.hostId) {\n return `${base}${AI_SITE_BUILD_HREF}/${encodeURIComponent(job.$id)}`\n }\n if (to === 'done' && job.hostId) {\n const own = (job.outputs ?? []).filter((output) => output.hostId === job.hostId)\n const screens = own.filter((output) => output.resource === 'screen')\n if (screens.length > 1) return `${base}/screens`\n const versioned = own.find(\n (output) => AI_JOB_BESIGNER_SEGMENT[output.resource] && output.versionId,\n )\n if (own.length === 1 && versioned) {\n const segment = AI_JOB_BESIGNER_SEGMENT[versioned.resource]\n return `${base}/${segment}/${versioned.id}/versions/${versioned.versionId}/besigner`\n }\n }\n return `${base}?${AI_JOB_LINK_PARAM}=${encodeURIComponent(job.$id)}`\n}\n\n/**\n * The in-app notification for one change of one job (AGL-3593), for the\n * person who created it. Customer-safe throughout: a stopped job's sentence is\n * the job's own `error`, which the machine writes as a fixed sentence.\n */\n/** The site a job worked on, as its notice names it: its name, and where it is live. */\nexport interface AiJobNoticeSite {\n name: string\n /** The site's live address, absolute; `null` when it has none yet. */\n url: string | null\n}\n\nexport function aiJobNotice(\n job: AiJobNoticeSource,\n to: AiJobNoticeTransition,\n site: AiJobNoticeSite | null = null,\n): AiJobNotice {\n const noun = aiJobKindNoun(job.kind)\n const scope = { orgId: job.orgId, ...(job.hostId ? { hostId: job.hostId } : {}) }\n const link = aiJobNoticeLink(job, to)\n if (to === 'needs-review') {\n // A plan that needs an ID only the person has first (AGL-3978).\n if (job.input?.message) {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : noun} needs one detail from you`,\n body: `${job.input.message} Nothing is built until you answer it and confirm the plan. It waits in AI jobs.`,\n link,\n ...scope,\n }\n }\n // A site import's wave is in, or its site is read (AGL-3976): a decision\n // waiting, said as one, never as a job that stopped.\n if (job.review?.reason === 'wave') {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'info',\n title: 'Your site import is ready for you',\n body: job.review.message || 'Review the pages brought in, then start the next wave when you are ready.',\n link,\n ...scope,\n }\n }\n if (job.review?.reason === 'plan') {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title:\n noun === 'AI job'\n ? 'Your plan is ready: confirm it to build'\n : `Your ${noun} plan is ready: confirm it to build`,\n body: 'Nothing is built until you confirm the plan. It waits in AI jobs.',\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} needs you`,\n // The doctrine's own words never reach a customer (AGL-3596).\n body: withRefund(\n job,\n aiCustomerSafeCopy(job.review?.message || job.error || 'It stopped for your decision. It waits in AI jobs.', { page: job.kind === 'page' }),\n 'needs_review',\n ),\n link,\n ...scope,\n }\n }\n if (to === 'paused') {\n // Paused by the meter (AGL-3660): out of credits or at a cap. Nothing is\n // lost and the job is not over, so it is a warning that names the way on.\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: aiJobPausedTitle(job.kind),\n body: [job.error, AI_JOB_PAUSED_NEXT_COPY].filter(Boolean).join(' '),\n link,\n ...scope,\n }\n }\n if (to === 'done') {\n // A job that finished with part of it unbuilt (AGL-3596) says so in the\n // words its page uses, as a warning: \"done\" alone hid a page that failed.\n const items = job.items ?? []\n const unbuilt = items.some((row) => row.status === 'failed' || row.status === 'skipped')\n const level = unbuilt ? 'warning' : 'success'\n const partial =\n job.kind === 'site'\n ? aiSitePartialCopy({ items, status: 'done' })\n : unbuilt\n ? aiBuildOutcomeLine({ items, status: 'done' })\n : null\n // A guided site start publishes its pages when it finishes (AGL-3596):\n // with one live, the site is, and the build page lists any that stayed\n // drafts with the reason for each.\n const published = job.kind === 'site' ? (job.sitePublish?.published.length ?? 0) : 0\n if (published > 0) {\n const drafts = job.sitePublish?.drafts.length ?? 0\n const stayed = drafts === 1 ? 'one that stayed a draft' : `${drafts} that stayed drafts`\n // Named, with its address (AGL-3889): someone with several sites, or\n // reading the email days later, cannot tell which site went live from\n // \"Your site is live\", nor where to see it.\n const live = site ? `${site.name} is live` : 'Your site is live'\n const at = site?.url ? ` at ${site.url}` : ''\n return {\n type: 'content.aiJobDone',\n level,\n title: partial ? `${live}, but part of it wasn’t built` : live,\n body: partial\n ? `${partial} ${drafts > 0 ? `What was built is published${at}, except ${stayed}.` : `What was built is published${at}.`}`\n : drafts > 0\n ? `Your pages are published${at}, except ${stayed}. Open the build page to see why.`\n : at\n ? `Your pages are published${at}. Visit your site there, or open it in the console to edit your pages.`\n : 'Your pages are published. Open it to view your site or edit your pages.',\n link,\n ...scope,\n }\n }\n const pages = (job.outputs ?? []).filter((output) => output.resource === 'screen').length\n const title =\n job.kind === 'site' || pages > 1\n ? 'Your site’s draft pages are ready'\n : job.kind === 'page'\n ? 'Your draft page is ready'\n : noun === 'AI job'\n ? 'Your AI job finished'\n : `Your ${noun} is ready`\n return {\n type: 'content.aiJobDone',\n level,\n title,\n body:\n job.kind === 'insight' || job.kind === 'text'\n ? 'Open AI jobs to read it.'\n : [partial, 'Everything it built is an unpublished draft until you publish it.'].filter(Boolean).join(' '),\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobFailed',\n level: 'warning',\n title: `Your ${noun === 'AI job' ? 'AI job' : `${noun} job`} stopped`,\n body: withRefund(job, aiCustomerSafeCopy(job.error || 'It stopped before it finished.', { page: job.kind === 'page' }), 'failed'),\n link,\n ...scope,\n }\n}\n\n/**\n * The one reminder a plan that waits for its confirmation gets (AGL-4076),\n * a day after it was ready. The same kind of notice as the first one\n * (`content.aiJobNeedsYou`), so the same switch in the person's notification\n * settings turns both off, and it opens the same place: AI jobs on the job, or\n * a site start's build page. It says it is the only one, because it is.\n */\nexport function aiJobPlanReminderNotice(job: AiJobNoticeSource, site: AiJobNoticeSite | null = null): AiJobNotice {\n const noun = aiJobKindNoun(job.kind)\n const scope = { orgId: job.orgId, ...(job.hostId ? { hostId: job.hostId } : {}) }\n const link = aiJobNoticeLink(job, 'needs-review')\n const on = site ? ` for ${site.name}` : ''\n if (job.input?.message) {\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: `Your ${noun}${on} still needs one detail from you`,\n body: `${job.input.message} Nothing is built until you answer it and confirm the plan. It waits in AI jobs. This is the only reminder.`,\n link,\n ...scope,\n }\n }\n return {\n type: 'content.aiJobNeedsYou',\n level: 'warning',\n title: noun === 'AI job' ? `Your plan${on} is still waiting for you` : `Your ${noun} plan${on} is still waiting for you`,\n body: 'Nothing is built until you confirm the plan. It waits in AI jobs. This is the only reminder.',\n link,\n ...scope,\n }\n}\n"],"names":["aiBuildOutcomeLine","aiSitePartialCopy","aiJobKindNoun","aiCustomerSafeCopy","aiJobRefundCopy","AI_JOB_BESIGNER_SEGMENT","screen","reusableComponent","layout","template","emailScreen","AI_JOB_LINK_PARAM","AI_SITE_BUILD_HREF","AI_JOB_PAUSED_NEXT_COPY","aiJobPausedTitle","kind","noun","endsWith","withRefund","job","text","status","refund","refundedCredits","creditsSpent","aiJobNoticeLink","to","base","hostId","encodeURIComponent","$id","own","outputs","filter","output","screens","resource","length","versioned","find","versionId","segment","id","aiJobNotice","site","scope","orgId","link","input","message","type","level","title","body","review","reason","error","page","Boolean","join","items","unbuilt","some","row","partial","published","sitePublish","drafts","stayed","live","name","at","url","pages","aiJobPlanReminderNotice","on"],"mappings":"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"}
|
|
@@ -19,7 +19,8 @@ import type { AiJobPlanSummary } from './ai-jobs.types';
|
|
|
19
19
|
/**
|
|
20
20
|
* A JOB'S PLAN AS THE LINES A PERSON READS BEFORE CONFIRMING IT (AGL-2935):
|
|
21
21
|
* what it reuses from the site, what it creates and why, the pages it builds,
|
|
22
|
-
* the items, the players it embeds
|
|
22
|
+
* the items, the players it embeds, the store pages the platform adds, and
|
|
23
|
+
* what the request asked for that a build leaves out (AGL-4087). The
|
|
23
24
|
* console's plan card draws these, and the native apps' follow view sends them
|
|
24
25
|
* (AGL-3854), so both say the same sentences.
|
|
25
26
|
*/
|
|
@@ -36,7 +36,7 @@ import { aiBuildStorePagesAdded } from "./ai-build-store-pages.js";
|
|
|
36
36
|
return plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan';
|
|
37
37
|
}
|
|
38
38
|
/** Every line of a plan, in the order the plan card lists them. */ export function aiJobPlanLines(plan) {
|
|
39
|
-
var _plan_items, _plan_embeds;
|
|
39
|
+
var _plan_items, _plan_embeds, _plan_leftOut;
|
|
40
40
|
const named = (ref)=>aiPlanRefName(plan, ref);
|
|
41
41
|
return [
|
|
42
42
|
...plan.reuse.map((entry, index)=>({
|
|
@@ -63,6 +63,11 @@ import { aiBuildStorePagesAdded } from "./ai-build-store-pages.js";
|
|
|
63
63
|
key: `store-${page.key}`,
|
|
64
64
|
text: `Adds the page ${page.title} at ${page.href} — added for your store automatically, uses no AI credits`,
|
|
65
65
|
platformAdded: true
|
|
66
|
+
})),
|
|
67
|
+
// Read before Confirm, so nothing asked for is dropped without a word (AGL-4087).
|
|
68
|
+
...((_plan_leftOut = plan.leftOut) != null ? _plan_leftOut : []).map((entry, index)=>({
|
|
69
|
+
key: `left-out-${index}`,
|
|
70
|
+
text: `Leaves out ${entry.asked}${entry.why ? ` — ${entry.why}` : ''}`
|
|
66
71
|
}))
|
|
67
72
|
];
|
|
68
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-plan-lines.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\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from './ai-build-plan'\nimport { aiBuildOpNoun } from './ai-build-progress'\nimport { aiBuildStorePagesAdded } from './ai-build-store-pages'\nimport type { AiJobPlanSummary } from './ai-jobs.types'\n\n/**\n * A JOB'S PLAN AS THE LINES A PERSON READS BEFORE CONFIRMING IT (AGL-2935):\n * what it reuses from the site, what it creates and why, the pages it builds,\n * the items, the players it embeds
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-job-plan-lines.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\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from './ai-build-plan'\nimport { aiBuildOpNoun } from './ai-build-progress'\nimport { aiBuildStorePagesAdded } from './ai-build-store-pages'\nimport type { AiJobPlanSummary } from './ai-jobs.types'\n\n/**\n * A JOB'S PLAN AS THE LINES A PERSON READS BEFORE CONFIRMING IT (AGL-2935):\n * what it reuses from the site, what it creates and why, the pages it builds,\n * the items, the players it embeds, the store pages the platform adds, and\n * what the request asked for that a build leaves out (AGL-4087). The\n * console's plan card draws these, and the native apps' follow view sends them\n * (AGL-3854), so both say the same sentences.\n */\n\n/** One line of a plan. */\nexport interface AiJobPlanLine {\n /** Stable within one plan, for a list's key. */\n key: string\n text: string\n /** Added by the platform rather than the model (a store's own pages, AGL-3676): it uses no AI credits. */\n platformAdded?: boolean\n}\n\n/** A reference as a person reads it: the record's name, or what the plan creates. */\nexport function aiPlanRefName(plan: Pick<AiJobPlanSummary, 'labels'> | null, ref: string | null): string {\n if (!ref) return ''\n return isAiPlanNewRef(ref) ? ref.slice('new:'.length) : (plan?.labels[ref] ?? ref)\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`\n}\n\n/** The plan's heading: whether it is still a proposal. */\nexport function aiJobPlanHeading(plan: Pick<AiJobPlanSummary, 'status'>): string {\n return plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'\n}\n\n/** Every line of a plan, in the order the plan card lists them. */\nexport function aiJobPlanLines(plan: AiJobPlanSummary): AiJobPlanLine[] {\n const named = (ref: string | null) => aiPlanRefName(plan, ref)\n return [\n ...plan.reuse.map((entry, index) => ({\n key: `reuse-${index}`,\n text: `Reuses the ${entry.kind === 'screen' ? 'page' : entry.kind} ${named(entry.id)} — ${entry.purpose}`,\n })),\n ...plan.create.map((entry, index) => ({\n key: `create-${index}`,\n text:\n `Creates the ${AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} ${entry.name}` +\n `${entry.duplicateOf ? `, from a copy of ${named(entry.duplicateOf)}` : ''} — ${entry.why}`,\n })),\n ...plan.screens.map((screen, index) => ({\n key: `screen-${index}`,\n text:\n (screen.record\n ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…`\n : `Builds the page ${screen.title} at ${screen.slug}`) +\n (screen.duplicateOf ? `, from a copy of ${named(screen.duplicateOf)}` : '') +\n (screen.layout ? ` in ${named(screen.layout)}` : '') +\n (screen.sections.length ? `: ${screen.sections.map((section) => section.name).join(', ')}` : ''),\n })),\n ...(plan.items ?? []).map((item) => ({\n key: `item-${item.slot}`,\n text: `Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}${item.why ? ` — ${item.why}` : ''}`,\n })),\n ...(plan.embeds ?? []).map((embed, index) => ({\n key: `embed-${index}`,\n text: aiPlanEmbedLine(embed, named),\n })),\n ...aiBuildStorePagesAdded(plan).map((page) => ({\n key: `store-${page.key}`,\n text: `Adds the page ${page.title} at ${page.href} — added for your store automatically, uses no AI credits`,\n platformAdded: true,\n })),\n // Read before Confirm, so nothing asked for is dropped without a word (AGL-4087).\n ...(plan.leftOut ?? []).map((entry, index) => ({\n key: `left-out-${index}`,\n text: `Leaves out ${entry.asked}${entry.why ? ` — ${entry.why}` : ''}`,\n })),\n ]\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiBuildOpNoun","aiBuildStorePagesAdded","aiPlanRefName","plan","ref","slice","length","labels","aiPlanEmbedLine","embed","named","host","where","link","url","asked","aiJobPlanHeading","status","aiJobPlanLines","reuse","map","entry","index","key","text","kind","id","purpose","create","noun","name","duplicateOf","why","screens","screen","record","title","dataset","base","slug","layout","sections","section","join","items","item","slot","op","toLowerCase","embeds","page","href","platformAdded","leftOut"],"mappings":"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"}
|
|
@@ -557,6 +557,14 @@ export interface AiJob {
|
|
|
557
557
|
* other job.
|
|
558
558
|
*/
|
|
559
559
|
siteRemoved?: AiJobSiteRemoved | null;
|
|
560
|
+
/**
|
|
561
|
+
* When the one reminder about a plan waiting for its confirmation went out
|
|
562
|
+
* (AGL-4076), a day after the plan was ready. Written once, by the AI jobs
|
|
563
|
+
* beat, in the transaction that decides to send it, and never cleared: a
|
|
564
|
+
* job is reminded at most once, however many plans it waits on. Absent on
|
|
565
|
+
* every job that was not reminded.
|
|
566
|
+
*/
|
|
567
|
+
remindedAt?: ITimestamp | null;
|
|
560
568
|
/**
|
|
561
569
|
* An `import` job's site, read and cut into waves (AGL-3976): what was
|
|
562
570
|
* found, what is brought in in which wave, what was left out and why, and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.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 type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\nimport type { AiFreeCreditsLeft } from './ai-site-job'\nimport type { AiJobCreditsConfirmation } from './ai-credit-estimate'\nimport type { AiSiteImportState } from './ai-site-import'\nimport type { AiSitePhotos } from './ai-site-photos'\nimport type { AiSiteInspiration } from '../jobs/ai-site-inspiration-signals'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n /** A change to a site's header or footer menu, made by code (AGL-3728). */\n | 'navigation'\n /** A copy of something a site already has, made by core's duplicate (AGL-3740). */\n | 'duplicate'\n /** A change to a site's settings, made by code and saved live (AGL-3748). */\n | 'settings'\n /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */\n | 'media'\n /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */\n | 'tracking'\n /** A change to who can see a site — maintenance mode, search indexing, page visibility — made by code and saved live (AGL-4033). */\n | 'privacy'\n /** A person's own website brought in, page by page, in reviewed waves (AGL-3976). */\n | 'import'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n 'navigation',\n 'duplicate',\n 'settings',\n 'media',\n 'tracking',\n 'privacy',\n 'import',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed — or, carrying `input`, for an answer only the person can\n * give (AGL-3978), which the beat leaves alone; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n /** A site's settings a build changed (AGL-3748): saved live, on the host document. */\n | 'settings'\n /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */\n | 'media'\n /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */\n | 'tracking'\n /** Who can see a site, as a build changed it (AGL-4033): saved live, on the host and page documents. */\n | 'privacy'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n /**\n * A site import's wave is in (AGL-3976): its pages are drafts for the\n * person to review, and the next wave starts only when they say so. Or the\n * import has read the site and waits for the first wave to be started.\n */\n | 'wave'\n /**\n * A site import's next wave needs a current proof that the workspace owns\n * the site (AGL-3976): the proof is older than its 30 days, or was never\n * made. Verifying again and starting the wave runs it.\n */\n | 'ownership'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n /**\n * What a Free workspace had left when a build's plan was kept (AGL-3722),\n * so its plan card says, before Confirm, whether the build fits and offers\n * the go-ahead or a smaller first build when it does not. The confirm door\n * reads what is left again; this is what the card shows.\n */\n freeCredits?: AiFreeCreditsLeft\n}\n\n/**\n * One answer a job waits for (AGL-3978): an id only the person has, which the\n * operation that needs it describes — a tracking change's Meta pixel ID.\n */\nexport interface AiJobInputField {\n /** The plan item the answer fills, by its name in the plan. */\n item: string\n /** The item's argument the answer is written to. */\n arg: string\n /** The field's label, naming the provider: \"Meta pixel ID\". */\n label: string\n /** What an answer looks like, in the words of the card that sets it: \"8–20 digits\". */\n shape: string\n /** The pattern an answer is held to, as a RegExp's source. */\n pattern: string\n}\n\n/** What a job parked `needs_input` for a person asks (AGL-3978). */\nexport interface AiJobInputRequest {\n /** Customer-safe: the question, in the operation's own words. */\n message: string\n fields: AiJobInputField[]\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n /**\n * What a site start read of the sites the person likes (AGL-3975): signals\n * only, each from a closed set or a hex color, which the plan was told the\n * structure of and the look pins the style of. Absent without them.\n */\n inspiration?: AiSiteInspiration\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What the job waits for while it is `needs_input` for a person rather than\n * the meter (AGL-3978): the fields only they can fill, an id the planner\n * must never make up. Its plan and the plan's review are kept beside it,\n * and the answer returns the job to that review.\n */\n input?: AiJobInputRequest | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n /**\n * A Free job started past what was left (AGL-3722): the person chose to\n * build what fits, knowing it pauses where the credits run out. Who, when,\n * what was left and the range they were shown; absent on every job that fit.\n */\n creditsConfirmed?: AiJobCreditsConfirmation | null\n /**\n * A person asked to stop the job (AGL-3616): written only by the cancel\n * door, in the transaction that decides it. A job no step holds ends\n * `canceled` in that same write; a job whose step holds a live lease keeps\n * running until the step finishes or aborts, and the machine ends it\n * `canceled` at its next write. Absent on a job nobody canceled.\n */\n cancelRequested?: AiJobCancelRequest | null\n /**\n * The empty site this guided start was for was removed (AGL-3936): the\n * job was cancelled or failed before it built anything, nobody chose what\n * to do next within the grace period, and the AI jobs beat deleted the\n * site. Written once, by the beat, after the site is gone. Absent on every\n * other job.\n */\n siteRemoved?: AiJobSiteRemoved | null\n /**\n * An `import` job's site, read and cut into waves (AGL-3976): what was\n * found, what is brought in in which wave, what was left out and why, and\n * each wave's state. Written only by the machine's `recordStep`, from what\n * the import step returns; absent on every other kind.\n */\n siteImport?: AiSiteImportState | null\n /**\n * A `site` job's pictures left on a starter photo (AGL-3988): each page\n * picture and dataset record its library could not fill in its own pass,\n * with the picks the image hosts refused. Written only by the machine's\n * `recordStep`, from what the site step returns; emptied by its late photo\n * pass before the site is published. Never on the wire. Absent on every\n * other kind.\n */\n sitePhotos?: AiSitePhotos | null\n}\n\n/** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */\nexport interface AiJobSiteRemoved {\n at: ITimestamp\n /** The site's name as it was, for a sentence that names it. */\n name: string | null\n}\n\n/** Who asked to cancel a job, and when (AGL-3616). */\nexport interface AiJobCancelRequest {\n at: ITimestamp\n /** The member or staff account that asked; `null` when nobody was named. */\n by: string | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** The fields a `needs_input` job waits for a person to fill (AGL-3978); absent otherwise. */\n input?: AiJobInputRequest | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n /**\n * A cancel was asked and the job has not ended yet (AGL-3616): the step in\n * flight is finishing or aborting, and nothing after it runs. Absent once\n * the job is terminal, and on every job nobody canceled.\n */\n cancelRequested?: boolean\n /**\n * The empty site this guided start was for was removed (AGL-3936): its\n * page is gone, and Start again opens Create site with the same answers.\n * Absent on every other job.\n */\n siteRemoved?: boolean\n /**\n * An `import` job's waves (AGL-3976), without the page text a wave read\n * holds for the model; absent on every other job.\n */\n siteImport?: AiSiteImportState | null\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAoFD,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAqBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-jobs.types.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 type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type { AiEffort } from '../providers/contract'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiBuildPlan } from './ai-build-plan'\nimport type { AiFreeCreditsLeft } from './ai-site-job'\nimport type { AiJobCreditsConfirmation } from './ai-credit-estimate'\nimport type { AiSiteImportState } from './ai-site-import'\nimport type { AiSitePhotos } from './ai-site-photos'\nimport type { AiSiteInspiration } from '../jobs/ai-site-inspiration-signals'\n\n/**\n * AI generation jobs (AGL-2904).\n *\n * A job is one customer brief carried through one or more model steps by a\n * Firestore state machine, so that work longer than a request can survive\n * the request: the console route runs what it can inline, and the platform\n * job beat resumes whatever is left. The document is the whole state — no\n * process holds anything the next beat cannot read back.\n *\n * ── Outputs are drafts, never a publish ──────────────────────────────────\n *\n * Every output a job produces is a NEW draft or a new unpublished version of\n * an existing resource. A job never flips a version pointer, never registers\n * a route and never sends anything; a person opens the draft the job names\n * and publishes it through the door that already exists for that resource.\n * That is what lets a job run with no visitor, no session and no confirm\n * gate — the thing it writes cannot be seen by anyone but the workspace\n * until a member chooses otherwise.\n *\n * ── The lease ────────────────────────────────────────────────────────────\n *\n * A step runs under a lease: an owner id and an `until` instant. Two beats\n * fire a minute apart against a 120 s function timeout, and a console route\n * may still be running the first step when the first beat looks, so the\n * lease is what stops the same step being run — and metered — twice. A\n * claim is a transaction that refuses while another owner's lease is live;\n * an expired lease is simply claimable, which is also how a step abandoned\n * by a frozen process is recovered.\n */\n\n/**\n * What a job produces. `text` and `theme` have runners; every other kind is\n * registered and refuses with \"not available yet\" until its own issue lands,\n * so a kind's presence in the union says the job model accepts it, not that\n * a runner exists.\n */\nexport type AiJobKind =\n | 'page'\n | 'component'\n | 'layout'\n | 'template'\n | 'edit'\n | 'seo'\n | 'site'\n | 'email'\n | 'campaign'\n | 'form'\n | 'experiment'\n | 'insight'\n | 'products'\n | 'crm'\n | 'onboarding'\n | 'workflow'\n | 'logic'\n | 'text'\n | 'theme'\n | 'build'\n /** A change to a site's header or footer menu, made by code (AGL-3728). */\n | 'navigation'\n /** A copy of something a site already has, made by core's duplicate (AGL-3740). */\n | 'duplicate'\n /** A change to a site's settings, made by code and saved live (AGL-3748). */\n | 'settings'\n /** Folders, tags, alt text and names in a site's media library, made by code and saved live (AGL-3752). */\n | 'media'\n /** A change to a site's analytics and advertising tags, made by code and saved live (AGL-3751). */\n | 'tracking'\n /** A change to who can see a site — maintenance mode, search indexing, page visibility — made by code and saved live (AGL-4033). */\n | 'privacy'\n /** A person's own website brought in, page by page, in reviewed waves (AGL-3976). */\n | 'import'\n\n/** The union as a value, so a route can validate a body against it. */\nexport const AI_JOB_KINDS: readonly AiJobKind[] = [\n 'page',\n 'component',\n 'layout',\n 'template',\n 'edit',\n 'seo',\n 'site',\n 'email',\n 'campaign',\n 'form',\n 'experiment',\n 'insight',\n 'products',\n 'crm',\n 'onboarding',\n 'workflow',\n 'logic',\n 'text',\n 'theme',\n 'build',\n 'navigation',\n 'duplicate',\n 'settings',\n 'media',\n 'tracking',\n 'privacy',\n 'import',\n]\n\n/**\n * `queued` — waiting for a runner; `running` — a step holds the lease;\n * `needs_input` — the job stopped for something only the workspace can\n * change (credits, a cap, a switch), and the beat tries it again once that\n * may have changed — or, carrying `input`, for an answer only the person can\n * give (AGL-3978), which the beat leaves alone; `needs_review` — the job stopped for a person's decision\n * (AGL-2935): its plan is ready to confirm, or an answer broke a building\n * rule on its re-ask. No timer can make that decision, so neither the beat\n * nor anything but that person resumes it. The last three are terminal.\n */\nexport type AiJobStatus =\n | 'queued'\n | 'running'\n | 'needs_input'\n | 'needs_review'\n | 'done'\n | 'failed'\n | 'canceled'\n\nexport const AI_JOB_TERMINAL_STATUSES: readonly AiJobStatus[] = [\n 'done',\n 'failed',\n 'canceled',\n]\n\nexport type AiJobStepStatus = 'pending' | 'running' | 'done' | 'failed'\n\n/** The step a planned kind runs first (AGL-2935): its plan, before anything is built. */\nexport const AI_JOB_PLAN_STEP = 'plan'\n\nexport interface AiJobStep {\n /** Stable within the job (`plan`, `draft`, `generate`), unique per job. */\n name: string\n status: AiJobStepStatus\n startedAt?: ITimestamp | null\n endedAt?: ITimestamp | null\n /** Credits this step has actually cost, at the plan's credit rate. */\n creditsSpent: number\n /** Customer-safe only; provider detail goes to the server log. */\n error?: string | null\n /**\n * How many times a runner has been handed this step. A retryable provider\n * failure hands the step back as `pending`; the count is what stops a\n * provider outage re-running a step forever.\n */\n attempts?: number\n /**\n * Passes of a step that works through a list (AGL-2910): each one a\n * recorded, metered exchange that asked to continue. Attempts count again\n * from zero after a pass; this count is what bounds the step.\n */\n passes?: number\n /**\n * What the step's model calls cost in tokens and time (AGL-2937), summed\n * over every run that reached the provider. Absent on a step none has.\n */\n tokens?: AiJobStepTokens\n /**\n * The ids the step writes its drafts under, by draft (AGL-3079): minted\n * when the job is created, so every run of the step names the same drafts.\n * Absent on a step that writes none; a step that records none names its\n * drafts by the job's own id.\n */\n draftIds?: Partial<Record<AiJobDraftSlot, string>>\n}\n\n/**\n * A draft a job's kind always writes (AGL-3079): a screen, layout, template,\n * form or component; an email design; a campaign; an automation.\n */\nexport type AiJobDraftSlot =\n | 'screen'\n | 'layout'\n | 'template'\n | 'form'\n | 'component'\n | 'email'\n | 'campaign'\n | 'workflow'\n\n/**\n * A step's measure (AGL-2937): the four token counts the meter prices, the\n * time spent in the runner, and the model and thinking effort of the last\n * run. The machine writes it beside `creditsSpent`, so what a kind of step\n * costs in tokens is read off the job rather than inferred from the month.\n */\nexport interface AiJobStepTokens {\n /** Prompt tokens sent uncached. */\n input: number\n /** Prompt tokens read from the prompt cache. */\n cachedRead: number\n /** Prompt tokens written to the prompt cache. */\n cacheWrite: number\n /** Tokens generated, thinking included. */\n output: number\n /** The model the last run was served by. */\n model: string | null\n /** The thinking effort the last run asked for; `null` when it named none. */\n effort: AiEffort | null\n /** Milliseconds spent in the runner, summed over runs. */\n latencyMs: number\n /** Runs that reached the provider. */\n runs: number\n /**\n * Why the step's latest runs stopped (AGL-3042), oldest first and the last\n * run last: each run's stop reason and the tokens it generated, so a pass\n * cut off at its ceiling reads off the job rather than out of the sums. At\n * most `AI_JOB_STEP_LAST_RUNS` are kept; absent on a step measured before.\n */\n lastRuns?: AiJobStepRunStop[]\n}\n\n/** One run of a step, by why it stopped (AGL-3042). */\nexport interface AiJobStepRunStop {\n /**\n * The stop reason of the run's last model call — `tool_use` or `end_turn`\n * for an answer, `max_tokens` for one cut off at its ceiling, `refusal` —\n * and `null` when the provider named none.\n */\n stopReason: string | null\n /** Tokens the run generated, over every model call it made. */\n output: number\n}\n\nexport type AiJobOutputResource =\n | 'screen'\n | 'reusableComponent'\n | 'layout'\n | 'template'\n | 'form'\n | 'emailScreen'\n | 'campaign'\n | 'product'\n | 'experiment'\n | 'workflow'\n | 'text'\n | 'theme'\n | 'seo'\n | 'insight'\n | 'crm'\n /** A draft another plugin wrote for a build (AGL-3616), named by `draftResource`. */\n | 'draft'\n | 'logic'\n | 'orgAutomation'\n /** A post a guided start wrote into the site's blog (AGL-3676): a content collection entry. */\n | 'entry'\n /** A site's settings a build changed (AGL-3748): saved live, on the host document. */\n | 'settings'\n /** Files in a site's media library a build organized (AGL-3752): saved live, on their documents. */\n | 'media'\n /** A site's tracking tags a build changed (AGL-3751): saved live, on the host document. */\n | 'tracking'\n /** Who can see a site, as a build changed it (AGL-4033): saved live, on the host and page documents. */\n | 'privacy'\n\n/**\n * One thing a job wrote. Addressed by resource and id so the console can\n * build the \"open draft\" link without knowing what the runner did; `text`\n * has no document of its own, so its body rides on the output itself.\n *\n * A `theme` output is a proposal rather than a document (AGL-2938). A site's\n * theme is fields on its host document, and writing those fields is the save\n * the Theme section's editor performs, which a job never does. So the change\n * set rides on the output as `proposal`, and a person puts it in the editor\n * and saves it there, or does not.\n *\n * An `seo` output is a proposal rather than a document (AGL-2910): search\n * listing values for a page or a product, or a site audit's findings and\n * fixes. A screen's listing is served from the screen document itself, so a\n * job never writes it; the values ride on the output as `proposal`, a person\n * puts them in the editor and saves them there, and an audit's content fixes\n * become new versions only when a person applies them.\n *\n * A `crm` output is a proposal too (AGL-2917): a record's summary and next\n * step, an email draft, or an import's column matches. The CRM's own task\n * form, stage route, composer and import drawer are the writes, and a person\n * makes each of them, or does not.\n *\n * A `logic` output is a proposal too (AGL-3603): a site function or variable\n * the Functions & Variables editor opens unsaved. Saving it is the editor's\n * write, which a person makes, or does not.\n *\n * An `orgAutomation` output is a proposal too (AGL-3603): one of the\n * workspace's automations, which the Org automations editor opens unsaved and\n * switched off. `hostId` is `null`: it belongs to no one site.\n */\nexport interface AiJobOutput {\n resource: AiJobOutputResource\n id: string\n /** The unpublished version the job created, where the resource has versions. */\n versionId?: string | null\n /** `null` for an output that belongs to the org rather than one site. */\n hostId: string | null\n /**\n * The site's subdomain, which is what a console URL names a site by: the\n * `[host]` segment resolves by subdomain, so a link built from `hostId`\n * names no site the console can open.\n */\n hostSubdomain?: string | null\n label: string\n /** The body of a `text` output. Absent on every other resource. */\n text?: string\n /**\n * The change set of an output a person applies rather than opens — a\n * `theme` or `seo` proposal — in the shape its job kind's runner defines.\n */\n proposal?: Record<string, unknown>\n /**\n * What the person decides next about this output, in customer-safe words:\n * a routing the draft could not store, or part of the brief it could not\n * build. Absent when there is nothing to decide.\n */\n note?: string | null\n /**\n * What a first visit is estimated to transfer, as the doctrine measured\n * the generated document (AGL-2935) — the weight the proposal shows before\n * anything is applied. Absent where the output is not a page's document.\n */\n load?: AiLoadEstimate | null\n /**\n * For a `draft` output (AGL-3616): the resource whose owner wrote it, as\n * its writer is registered on the resource-draft seam.\n */\n draftResource?: string\n}\n\nexport interface AiJobLease {\n /** The process that holds the step: a route request id or a beat id. */\n owner: string\n until: ITimestamp\n}\n\n/** Why a job stopped for a person (AGL-2935). */\nexport type AiJobReviewReason =\n /** The plan step proposed a plan; generation waits for it to be confirmed. */\n | 'plan'\n /** An answer broke a building rule, and so did the answer to the re-ask. */\n | 'doctrine'\n /**\n * The site is at an allowance its plan sets for what the step writes, such\n * as its shared layouts or its templates (AGL-2909). Freeing one, or a plan\n * that holds more, is the member's decision; trying again runs the step\n * again.\n */\n | 'limit'\n /**\n * A site import's wave is in (AGL-3976): its pages are drafts for the\n * person to review, and the next wave starts only when they say so. Or the\n * import has read the site and waits for the first wave to be started.\n */\n | 'wave'\n /**\n * A site import's next wave needs a current proof that the workspace owns\n * the site (AGL-3976): the proof is older than its 30 days, or was never\n * made. Verifying again and starting the wave runs it.\n */\n | 'ownership'\n\n/**\n * A building rule an answer broke, as the job keeps it: the number, the code,\n * the sentence, and where in the answer it was broken (AGL-3078).\n */\nexport interface AiJobRuleFinding {\n rule: number | null\n code: string\n /** Customer-safe. */\n message: string\n /** The nodes the finding names, by the ids the refused answer gave them. Absent when it names none. */\n nodeIds?: string[]\n /** The plan entries or answer fields it names, as `screens[0].sections[2]`. Absent when it names none. */\n paths?: string[]\n}\n\n/**\n * One node of the parts of a refused answer its findings name (AGL-3078), as\n * the review keeps it: the node's place, its element and the names of what it\n * sets, and never its copy. A Grid keeps the values of its layout props as\n * they were written, so a review can tell a shape the rules refuse from one\n * the model was asked to mend and did not.\n */\nexport interface AiJobReviewOutlineNode {\n /** The node's id, as the refused answer wrote it. */\n id: string\n /** How far below a node a finding names it sits: 0 for that node. */\n depth: number\n componentId: string\n /** The props it sets, by name only. */\n props: string[]\n /** The `sx` keys it sets, by name only; absent when it sets none. */\n sx?: string[]\n /** A Grid's layout props, `container`, `size`, `direction`, `spacing` and the rest, as written. */\n grid?: Record<string, string | number | boolean | null>\n /** Its children's element ids, in order. */\n children: string[]\n}\n\nexport interface AiJobReview {\n reason: AiJobReviewReason\n /** Customer-safe: what the person is asked to decide. */\n message: string\n /** The rules the last answer broke; empty for a plan. */\n findings: AiJobRuleFinding[]\n /**\n * The parts of the refused answer its findings name, each node with the\n * nodes below it (AGL-3078); absent when they name none. Kept with the job\n * for diagnosis, and shown to staff.\n */\n outline?: AiJobReviewOutlineNode[]\n /**\n * The checks' own sentence, where `message` was written for the member\n * instead (AGL-3594): rule numbers and all, for staff. Its presence is what\n * tells the drawer the findings are staff reading too.\n */\n detail?: string\n /**\n * Why trying again cannot work yet (AGL-3594) — the Free allowance left is\n * less than the step can cost — or absent when it can. The button is drawn\n * disabled with this beside it.\n */\n retryRefusal?: string\n /**\n * What a Free workspace had left when a build's plan was kept (AGL-3722),\n * so its plan card says, before Confirm, whether the build fits and offers\n * the go-ahead or a smaller first build when it does not. The confirm door\n * reads what is left again; this is what the card shows.\n */\n freeCredits?: AiFreeCreditsLeft\n}\n\n/**\n * One answer a job waits for (AGL-3978): an id only the person has, which the\n * operation that needs it describes — a tracking change's Meta pixel ID.\n */\nexport interface AiJobInputField {\n /** The plan item the answer fills, by its name in the plan. */\n item: string\n /** The item's argument the answer is written to. */\n arg: string\n /** The field's label, naming the provider: \"Meta pixel ID\". */\n label: string\n /** What an answer looks like, in the words of the card that sets it: \"8–20 digits\". */\n shape: string\n /** The pattern an answer is held to, as a RegExp's source. */\n pattern: string\n}\n\n/** What a job parked `needs_input` for a person asks (AGL-3978). */\nexport interface AiJobInputRequest {\n /** Customer-safe: the question, in the operation's own words. */\n message: string\n fields: AiJobInputField[]\n}\n\nexport type AiJobPlanStatus = 'proposed' | 'confirmed'\n\n/** The plan a job builds from, as the job keeps it once its plan step proposed it. */\nexport interface AiJobPlan extends AiBuildPlan {\n status: AiJobPlanStatus\n /** Names for the inventory ids the plan references, read when it was made. */\n labels: Record<string, string>\n proposedAt: ITimestamp\n confirmedAt: ITimestamp | null\n /** The member who confirmed it. */\n confirmedBy: string | null\n /**\n * A digest of the whole request that produced this plan (AGL-2937): the\n * job's kind, site, brief and scalar inputs, the model that answered, and\n * the prompt as it was rendered — the site inventory included. A later job\n * whose request hashes the same reuses this plan rather than paying for the\n * same answer again. A hash of the request, never of the answer, and no\n * part of the brief is recoverable from it.\n */\n key?: string\n /** The job this plan was reused from, when it was not asked for again. */\n reusedFrom?: string\n /**\n * What a site start read of the sites the person likes (AGL-3975): signals\n * only, each from a closed set or a hex color, which the plan was told the\n * structure of and the look pins the style of. Absent without them.\n */\n inspiration?: AiSiteInspiration\n}\n\n/**\n * Where one item of a `build` job stands (AGL-3616). A build settles item by\n * item: a failed item gives back its own spend and the rest of the plan still\n * runs, so the job keeps one row per item, written only by the machine's\n * `recordStep` — in the same transaction as the spend it accounts for — and\n * by the resume door's retry.\n */\nexport type AiJobItemStatus = 'pending' | 'running' | 'succeeded' | 'degraded' | 'failed' | 'skipped'\n\n/** Why an item failed: a give-back reason when it was ours, else the model's refusal or a review. */\nexport type AiJobItemFailureReason =\n | 'plan-refused'\n | 'doctrine-refused'\n | 'provider'\n | 'timeout'\n | 'step-failure'\n | 'unavailable'\n | 'refused'\n | 'review'\n\nexport interface AiJobItemFailure {\n /** Whether the failure was on our side: only then is the item's spend given back. */\n ours: boolean\n reason: AiJobItemFailureReason\n /** Customer-safe. */\n message: string\n /**\n * The checks' own sentence behind a `doctrine-refused` failure (AGL-3596),\n * rule numbers and all: kept on the job for staff and debugging, never on\n * the wire a customer reads.\n */\n detail?: string\n}\n\nexport interface AiJobItemLedger {\n /** The unit's slot within the plan: `c0` a creation, `p0` a page, `i0` an item. */\n slot: string\n /** The capability operation that builds it: `page`, `form`, or an owner's op. */\n op: string\n /** What a person reads: the page's title, the creation's or item's name. */\n label: string\n status: AiJobItemStatus\n /** Bumped by Try again; the refund key names it, so each attempt gives back once. */\n attempt: number\n /** What this item's passes cost, every attempt. */\n creditsSpent: number\n /** What this attempt's passes cost so far: what a failure of this attempt gives back. */\n attemptCredits?: number\n /** What was given back for this item's failures. */\n creditsRefunded: number\n /** The key the latest give-back for this item was made under. */\n refundKey?: string\n failure?: AiJobItemFailure | null\n /** The ids of the outputs this item produced. */\n outputs: string[]\n /** The slots whose failure changed how this item was built. */\n degradedBy?: string[]\n /** Customer-safe: what the person should know about this item. */\n note?: string | null\n /**\n * When this attempt settled, as an ISO string (AGL-3596): the moment the\n * next item started, which is what its progress row counts from. Absent\n * while the item is open, and on a ledger written before it was kept.\n */\n settledAt?: string | null\n}\n\n/** Whether a build's own planning was charged or given back (AGL-3616). */\nexport interface AiJobOrchestration {\n /** What the plan step spent. */\n creditsSpent: number\n /** `charged` once anything was delivered; `refunded` when nothing was. */\n settled: 'pending' | 'charged' | 'refunded'\n}\n\nexport interface AiJob {\n $id: string\n orgId: string\n hostId?: string | null\n kind: AiJobKind\n status: AiJobStatus\n /** The customer's brief, verbatim. Expires with the document. */\n brief: string\n /** Kind-specific inputs the runner reads (a screen id to edit, a tone). */\n inputs: Record<string, unknown>\n /**\n * The model the creator picked for the job's steps (AGL-2942), or `null`\n * for Auto. Each step honors it only where the plan and the allotment\n * allowlists allow it, and runs on the routing table otherwise.\n */\n model?: string | null\n steps: AiJobStep[]\n outputs: AiJobOutput[]\n /**\n * The nominal credit figure held against the job while steps are\n * outstanding. The meter's real bound is the per-step message reservation\n * and the org's monthly ceiling; this is the number the console shows so\n * a running job is not read as free.\n */\n creditsReserved: number\n creditsSpent: number\n /**\n * Credits the job gave back because it failed on our side (AGL-3594),\n * summed over its give-backs; absent when it gave none. `creditsSpent`\n * stays what ran, so what the job cost is the one less the other.\n */\n refundedCredits?: number\n /** Why the latest give-back was made (`AiJobRefundReason`). */\n refundReason?: string\n /** How many give-backs the job has had: the next one's key. */\n refunds?: number\n createdBy: string\n createdAt: ITimestamp\n updatedAt: ITimestamp\n /** TTL — the brief is verbatim customer text and expires like an exchange. */\n expiresAt: ITimestamp\n /** Customer-safe only. */\n error?: string | null\n lease?: AiJobLease | null\n /** The plan the job builds from, once its plan step proposed one. */\n plan?: AiJobPlan | null\n /** What the person is asked to decide while the job is `needs_review`. */\n review?: AiJobReview | null\n /**\n * What the job waits for while it is `needs_input` for a person rather than\n * the meter (AGL-3978): the fields only they can fill, an id the planner\n * must never make up. Its plan and the plan's review are kept beside it,\n * and the answer returns the job to that review.\n */\n input?: AiJobInputRequest | null\n /**\n * What a person applied from the job's outputs (AGL-2910). Written by the\n * door that applied them, never by a step runner, and absent until then.\n */\n applied?: AiJobApplied | null\n /**\n * What a guided site start put live when it finished (AGL-3596): written\n * once by the site step's last pass, never by a person. Absent on every\n * other job, which leaves its pages as drafts.\n */\n sitePublish?: AiJobSitePublish | null\n /** A `build` job's items, one row each (AGL-3616); absent on every other kind. */\n items?: AiJobItemLedger[] | null\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration | null\n /**\n * A Free job started past what was left (AGL-3722): the person chose to\n * build what fits, knowing it pauses where the credits run out. Who, when,\n * what was left and the range they were shown; absent on every job that fit.\n */\n creditsConfirmed?: AiJobCreditsConfirmation | null\n /**\n * A person asked to stop the job (AGL-3616): written only by the cancel\n * door, in the transaction that decides it. A job no step holds ends\n * `canceled` in that same write; a job whose step holds a live lease keeps\n * running until the step finishes or aborts, and the machine ends it\n * `canceled` at its next write. Absent on a job nobody canceled.\n */\n cancelRequested?: AiJobCancelRequest | null\n /**\n * The empty site this guided start was for was removed (AGL-3936): the\n * job was cancelled or failed before it built anything, nobody chose what\n * to do next within the grace period, and the AI jobs beat deleted the\n * site. Written once, by the beat, after the site is gone. Absent on every\n * other job.\n */\n siteRemoved?: AiJobSiteRemoved | null\n /**\n * When the one reminder about a plan waiting for its confirmation went out\n * (AGL-4076), a day after the plan was ready. Written once, by the AI jobs\n * beat, in the transaction that decides to send it, and never cleared: a\n * job is reminded at most once, however many plans it waits on. Absent on\n * every job that was not reminded.\n */\n remindedAt?: ITimestamp | null\n /**\n * An `import` job's site, read and cut into waves (AGL-3976): what was\n * found, what is brought in in which wave, what was left out and why, and\n * each wave's state. Written only by the machine's `recordStep`, from what\n * the import step returns; absent on every other kind.\n */\n siteImport?: AiSiteImportState | null\n /**\n * A `site` job's pictures left on a starter photo (AGL-3988): each page\n * picture and dataset record its library could not fill in its own pass,\n * with the picks the image hosts refused. Written only by the machine's\n * `recordStep`, from what the site step returns; emptied by its late photo\n * pass before the site is published. Never on the wire. Absent on every\n * other kind.\n */\n sitePhotos?: AiSitePhotos | null\n}\n\n/** When the AI jobs beat removed a guided start's empty site, and which (AGL-3936). */\nexport interface AiJobSiteRemoved {\n at: ITimestamp\n /** The site's name as it was, for a sentence that names it. */\n name: string | null\n}\n\n/** Who asked to cancel a job, and when (AGL-3616). */\nexport interface AiJobCancelRequest {\n at: ITimestamp\n /** The member or staff account that asked; `null` when nobody was named. */\n by: string | null\n}\n\n/**\n * A guided site start's publish (AGL-3596): the pages that went live, with\n * the address each answers, and the pages that stayed drafts, each with the\n * plain sentence for why. A page that stayed a draft never fails the job.\n */\nexport interface AiJobSitePublish {\n /** The site's live address, `https://<subdomain>.aglyn.app/`; `null` when the site has no subdomain. */\n liveUrl: string | null\n published: Array<{ id: string; label: string; path: string }>\n drafts: Array<{ id: string; label: string; reason: string }>\n}\n\n/**\n * The record of an apply (AGL-2910): what became a new unpublished version,\n * and what waits in its editor for a person to save.\n */\nexport interface AiJobApplied {\n at: ITimestamp\n /** The member who applied. */\n by: string\n /** The new version opened for each resource, by resource id. */\n versions: Record<string, string>\n /** Resource ids whose proposed values wait in their editor, unsaved. */\n staged: string[]\n}\n\n/** The plan on the wire: every instant an ISO string. */\nexport interface AiJobPlanSummary extends AiBuildPlan {\n status: AiJobPlanStatus\n labels: Record<string, string>\n proposedAt: string | null\n confirmedAt: string | null\n confirmedBy: string | null\n key?: string\n reusedFrom?: string\n}\n\n/**\n * The wire form the console reads: every instant an ISO string, the lease\n * reduced to whether a runner holds the job. The brief and the outputs\n * travel as they are — the reader is a member of the org that wrote them.\n */\nexport interface AiJobSummary {\n id: string\n orgId: string\n hostId: string | null\n kind: AiJobKind\n status: AiJobStatus\n brief: string\n /**\n * The batch a door created this job as one of (AGL-2911), read off the\n * job's inputs; `null` for a job created on its own. The console groups a\n * run of jobs by it, which is the one thing it cannot do from a list whose\n * rows carry no inputs — and the only input the wire form carries, because\n * a batch id names nothing the member wrote.\n */\n batch: string | null\n steps: Array<{\n name: string\n status: AiJobStepStatus\n startedAt: string | null\n endedAt: string | null\n creditsSpent: number\n error: string | null\n }>\n outputs: AiJobOutput[]\n creditsReserved: number\n creditsSpent: number\n /** Credits given back for a failure on our side (AGL-3594); 0 when none. */\n refundedCredits?: number\n createdBy: string\n createdAt: string\n updatedAt: string\n error: string | null\n running: boolean\n plan: AiJobPlanSummary | null\n review: AiJobReview | null\n /** The fields a `needs_input` job waits for a person to fill (AGL-3978); absent otherwise. */\n input?: AiJobInputRequest | null\n /** What a person applied from the outputs (AGL-2910); instants as ISO strings. */\n applied?: {\n at: string | null\n by: string\n versions: Record<string, string>\n staged: string[]\n } | null\n /** What a guided site start put live (AGL-3596); absent on every other job. */\n sitePublish?: AiJobSitePublish | null\n /**\n * A site job's answers (AGL-3596): the guided start's questions as the person\n * answered them, so a failed start's Try again reopens them filled in. The\n * scalar site inputs only, never anything else a job's inputs carry; absent\n * on every other kind.\n */\n siteInputs?: Record<string, unknown> | null\n /**\n * Whether the machine confirms this job's plan itself (AGL-3594): a guided\n * site start. Its plan parks for the instant the confirmation takes, and a\n * surface reads that park as the build going on (AGL-3596). Absent on every\n * other job.\n */\n autoConfirm?: boolean\n /** A `build` job's items (AGL-3616); absent on every other job. */\n items?: AiJobItemLedger[]\n /**\n * Whether a build's request asked to publish (AGL-3616), so its plan card\n * offers the box that confirms it. Absent on every other job.\n */\n publishAsked?: boolean\n /** A `build` job's planning spend and how it settled (AGL-3616). */\n orchestration?: AiJobOrchestration\n /**\n * A cancel was asked and the job has not ended yet (AGL-3616): the step in\n * flight is finishing or aborting, and nothing after it runs. Absent once\n * the job is terminal, and on every job nobody canceled.\n */\n cancelRequested?: boolean\n /**\n * The empty site this guided start was for was removed (AGL-3936): its\n * page is gone, and Start again opens Create site with the same answers.\n * Absent on every other job.\n */\n siteRemoved?: boolean\n /**\n * An `import` job's waves (AGL-3976), without the page text a wave read\n * holds for the model; absent on every other job.\n */\n siteImport?: AiSiteImportState | null\n}\n"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAoFD,qEAAqE,GACrE,OAAO,MAAMA,eAAqC;IAChD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAqBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
@@ -41,8 +41,21 @@ export type AiPermissionVerdict = Record<AiPermission, boolean>;
|
|
|
41
41
|
* asking the assistant nothing: a viewer spends none of the workspace's AI
|
|
42
42
|
* credits. A site collaborator holds both on a site where their host role
|
|
43
43
|
* may change content.
|
|
44
|
+
*
|
|
45
|
+
* A site can narrow both (AGL-4047), and by default neither does: an editor
|
|
46
|
+
* of the whole workspace given Viewer or Author access to one site keeps AI
|
|
47
|
+
* there, as before, unless a custom role or their own permissions choose
|
|
48
|
+
* "Follow the site role" for the key, in which case that site's access
|
|
49
|
+
* decides it (`CONTENT_ROLES` below: Author keeps it, Viewer does not).
|
|
44
50
|
*/
|
|
45
51
|
export declare const AI_ORG_PERMISSIONS: readonly PluginOrgPermissionDeclaration[];
|
|
52
|
+
/**
|
|
53
|
+
* The AI plugin's workspace keys (AGL-4047), decided by the workspace role,
|
|
54
|
+
* the custom role and the member's own permissions, never per site.
|
|
55
|
+
* "Bring a site in" asked whether the member was the workspace's owner or an
|
|
56
|
+
* admin; its default is exactly those roles.
|
|
57
|
+
*/
|
|
58
|
+
export declare const AI_WORKSPACE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[];
|
|
46
59
|
/** What each host role grants of the AI keys, before any per-site toggle. */
|
|
47
60
|
export declare const HOST_ROLE_AI_PERMISSIONS: Readonly<Record<HostAccessRole, AiPermissionVerdict>>;
|
|
48
61
|
/** The AI keys of a verdict map the shell resolved; an absent key is refused. */
|
|
@@ -45,6 +45,12 @@ export function isAiPermission(value) {
|
|
|
45
45
|
* asking the assistant nothing: a viewer spends none of the workspace's AI
|
|
46
46
|
* credits. A site collaborator holds both on a site where their host role
|
|
47
47
|
* may change content.
|
|
48
|
+
*
|
|
49
|
+
* A site can narrow both (AGL-4047), and by default neither does: an editor
|
|
50
|
+
* of the whole workspace given Viewer or Author access to one site keeps AI
|
|
51
|
+
* there, as before, unless a custom role or their own permissions choose
|
|
52
|
+
* "Follow the site role" for the key, in which case that site's access
|
|
53
|
+
* decides it (`CONTENT_ROLES` below: Author keeps it, Viewer does not).
|
|
48
54
|
*/ export const AI_ORG_PERMISSIONS = [
|
|
49
55
|
{
|
|
50
56
|
key: 'ai.use',
|
|
@@ -58,7 +64,8 @@ export function isAiPermission(value) {
|
|
|
58
64
|
},
|
|
59
65
|
hostRoleDefaults: CONTENT_ROLES,
|
|
60
66
|
group: 'ai',
|
|
61
|
-
groupLabel: 'AI'
|
|
67
|
+
groupLabel: 'AI',
|
|
68
|
+
narrowsPerSite: false
|
|
62
69
|
},
|
|
63
70
|
{
|
|
64
71
|
key: 'ai.generate',
|
|
@@ -72,6 +79,27 @@ export function isAiPermission(value) {
|
|
|
72
79
|
},
|
|
73
80
|
hostRoleDefaults: CONTENT_ROLES,
|
|
74
81
|
group: 'ai',
|
|
82
|
+
groupLabel: 'AI',
|
|
83
|
+
narrowsPerSite: false
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* The AI plugin's workspace keys (AGL-4047), decided by the workspace role,
|
|
88
|
+
* the custom role and the member's own permissions, never per site.
|
|
89
|
+
* "Bring a site in" asked whether the member was the workspace's owner or an
|
|
90
|
+
* admin; its default is exactly those roles.
|
|
91
|
+
*/ export const AI_WORKSPACE_PERMISSIONS = [
|
|
92
|
+
{
|
|
93
|
+
key: 'ai.import',
|
|
94
|
+
label: 'Bring a site in',
|
|
95
|
+
description: 'Import an existing website into the workspace with Bring my site.',
|
|
96
|
+
roleDefaults: {
|
|
97
|
+
owner: true,
|
|
98
|
+
admin: true,
|
|
99
|
+
editor: false,
|
|
100
|
+
viewer: false
|
|
101
|
+
},
|
|
102
|
+
group: 'ai',
|
|
75
103
|
groupLabel: 'AI'
|
|
76
104
|
}
|
|
77
105
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-permissions.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 type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport type { PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\n\n/**\n * The two AI keys (AGL-2927): `ai.use` for the assistants, `ai.generate` for\n * generation jobs and AI edits.\n */\nexport type AiPermission = 'ai.use' | 'ai.generate'\n\n/** The AI keys, in the order the editors render them. */\nexport const AI_PERMISSION_KEYS: readonly AiPermission[] = ['ai.use', 'ai.generate']\n\nexport function isAiPermission(value: unknown): value is AiPermission {\n return value === 'ai.use' || value === 'ai.generate'\n}\n\n/** One verdict per AI key. */\nexport type AiPermissionVerdict = Record<AiPermission, boolean>\n\n/**\n * The host roles that may change a site's content may also ask a model to\n * change it. A viewer who cannot edit has nothing for a generation to land\n * on, and would spend the workspace's credits producing it anyway.\n */\nconst CONTENT_ROLES: Readonly<Record<HostAccessRole, boolean>> = {\n admin: true,\n editor: true,\n author: true,\n viewer: false,\n}\n\n/**\n * The AI keys as this plugin declares them into the org permission catalog\n * (AGL-2927, AGL-2984): listed on the role editor, stored on custom roles and\n * per-member overrides, and decided per site for a collaborator.\n *\n * Both are enforced at the AI doors themselves — the chat and copy\n * assistant doors through `memberHasPermissionOnHost`, and every door built\n * on `aiGateLadder` through its `permission` option — so a role that unticks\n * one closes the door, not only the button.\n *\n * Owners, admins and editors hold both by default: editors work on content,\n * the AI doors included. A viewer holds neither, because read-only includes\n * asking the assistant nothing: a viewer spends none of the workspace's AI\n * credits. A site collaborator holds both on a site where their host role\n * may change content.\n */\nexport const AI_ORG_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'ai.use',\n label: 'Use AI assistance',\n description: 'Ask the assistant, rewrite copy with AI, and generate a section.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: CONTENT_ROLES,\n group: 'ai',\n groupLabel: 'AI',\n },\n {\n key: 'ai.generate',\n label: 'Generate with AI',\n description:\n 'Run AI generation jobs and AI edits: pages, components, emails, campaigns, products, CRM, insights, workflows.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: CONTENT_ROLES,\n group: 'ai',\n groupLabel: 'AI',\n },\n]\n\nconst HOST_ROLES: readonly HostAccessRole[] = ['admin', 'editor', 'author', 'viewer']\n\n/** What each host role grants of the AI keys, before any per-site toggle. */\nexport const HOST_ROLE_AI_PERMISSIONS: Readonly<Record<HostAccessRole, AiPermissionVerdict>> =\n Object.fromEntries(\n HOST_ROLES.map((role) => [\n role,\n Object.fromEntries(\n AI_ORG_PERMISSIONS.map((permission) => [\n permission.key,\n permission.hostRoleDefaults?.[role] === true,\n ]),\n ) as AiPermissionVerdict,\n ]),\n ) as Record<HostAccessRole, AiPermissionVerdict>\n\n/** The AI keys of a verdict map the shell resolved; an absent key is refused. */\nexport function aiPermissionsOf(\n granted: Readonly<Record<string, boolean>> | null | undefined,\n): AiPermissionVerdict {\n return {\n 'ai.use': granted?.['ai.use'] === true,\n 'ai.generate': granted?.['ai.generate'] === true,\n }\n}\n"],"names":["AI_PERMISSION_KEYS","isAiPermission","value","CONTENT_ROLES","admin","editor","author","viewer","AI_ORG_PERMISSIONS","key","label","description","roleDefaults","owner","hostRoleDefaults","group","groupLabel","HOST_ROLES","HOST_ROLE_AI_PERMISSIONS","Object","fromEntries","map","role","permission","aiPermissionsOf","granted"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAWD,uDAAuD,GACvD,OAAO,MAAMA,qBAA8C;IAAC;IAAU;CAAc,CAAA;AAEpF,OAAO,SAASC,eAAeC,KAAc;IAC3C,OAAOA,UAAU,YAAYA,UAAU;AACzC;AAKA;;;;CAIC,GACD,MAAMC,gBAA2D;IAC/DC,OAAO;IACPC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV;AAEA
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-permissions.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 type { HostAccessRole } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport type { PluginOrgPermissionDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\n\n/**\n * The two AI keys (AGL-2927): `ai.use` for the assistants, `ai.generate` for\n * generation jobs and AI edits.\n */\nexport type AiPermission = 'ai.use' | 'ai.generate'\n\n/** The AI keys, in the order the editors render them. */\nexport const AI_PERMISSION_KEYS: readonly AiPermission[] = ['ai.use', 'ai.generate']\n\nexport function isAiPermission(value: unknown): value is AiPermission {\n return value === 'ai.use' || value === 'ai.generate'\n}\n\n/** One verdict per AI key. */\nexport type AiPermissionVerdict = Record<AiPermission, boolean>\n\n/**\n * The host roles that may change a site's content may also ask a model to\n * change it. A viewer who cannot edit has nothing for a generation to land\n * on, and would spend the workspace's credits producing it anyway.\n */\nconst CONTENT_ROLES: Readonly<Record<HostAccessRole, boolean>> = {\n admin: true,\n editor: true,\n author: true,\n viewer: false,\n}\n\n/**\n * The AI keys as this plugin declares them into the org permission catalog\n * (AGL-2927, AGL-2984): listed on the role editor, stored on custom roles and\n * per-member overrides, and decided per site for a collaborator.\n *\n * Both are enforced at the AI doors themselves — the chat and copy\n * assistant doors through `memberHasPermissionOnHost`, and every door built\n * on `aiGateLadder` through its `permission` option — so a role that unticks\n * one closes the door, not only the button.\n *\n * Owners, admins and editors hold both by default: editors work on content,\n * the AI doors included. A viewer holds neither, because read-only includes\n * asking the assistant nothing: a viewer spends none of the workspace's AI\n * credits. A site collaborator holds both on a site where their host role\n * may change content.\n *\n * A site can narrow both (AGL-4047), and by default neither does: an editor\n * of the whole workspace given Viewer or Author access to one site keeps AI\n * there, as before, unless a custom role or their own permissions choose\n * \"Follow the site role\" for the key, in which case that site's access\n * decides it (`CONTENT_ROLES` below: Author keeps it, Viewer does not).\n */\nexport const AI_ORG_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'ai.use',\n label: 'Use AI assistance',\n description: 'Ask the assistant, rewrite copy with AI, and generate a section.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: CONTENT_ROLES,\n group: 'ai',\n groupLabel: 'AI',\n narrowsPerSite: false,\n },\n {\n key: 'ai.generate',\n label: 'Generate with AI',\n description:\n 'Run AI generation jobs and AI edits: pages, components, emails, campaigns, products, CRM, insights, workflows.',\n roleDefaults: { owner: true, admin: true, editor: true, viewer: false },\n hostRoleDefaults: CONTENT_ROLES,\n group: 'ai',\n groupLabel: 'AI',\n narrowsPerSite: false,\n },\n]\n\n/**\n * The AI plugin's workspace keys (AGL-4047), decided by the workspace role,\n * the custom role and the member's own permissions, never per site.\n * \"Bring a site in\" asked whether the member was the workspace's owner or an\n * admin; its default is exactly those roles.\n */\nexport const AI_WORKSPACE_PERMISSIONS: readonly PluginOrgPermissionDeclaration[] = [\n {\n key: 'ai.import',\n label: 'Bring a site in',\n description: 'Import an existing website into the workspace with Bring my site.',\n roleDefaults: { owner: true, admin: true, editor: false, viewer: false },\n group: 'ai',\n groupLabel: 'AI',\n },\n]\n\nconst HOST_ROLES: readonly HostAccessRole[] = ['admin', 'editor', 'author', 'viewer']\n\n/** What each host role grants of the AI keys, before any per-site toggle. */\nexport const HOST_ROLE_AI_PERMISSIONS: Readonly<Record<HostAccessRole, AiPermissionVerdict>> =\n Object.fromEntries(\n HOST_ROLES.map((role) => [\n role,\n Object.fromEntries(\n AI_ORG_PERMISSIONS.map((permission) => [\n permission.key,\n permission.hostRoleDefaults?.[role] === true,\n ]),\n ) as AiPermissionVerdict,\n ]),\n ) as Record<HostAccessRole, AiPermissionVerdict>\n\n/** The AI keys of a verdict map the shell resolved; an absent key is refused. */\nexport function aiPermissionsOf(\n granted: Readonly<Record<string, boolean>> | null | undefined,\n): AiPermissionVerdict {\n return {\n 'ai.use': granted?.['ai.use'] === true,\n 'ai.generate': granted?.['ai.generate'] === true,\n }\n}\n"],"names":["AI_PERMISSION_KEYS","isAiPermission","value","CONTENT_ROLES","admin","editor","author","viewer","AI_ORG_PERMISSIONS","key","label","description","roleDefaults","owner","hostRoleDefaults","group","groupLabel","narrowsPerSite","AI_WORKSPACE_PERMISSIONS","HOST_ROLES","HOST_ROLE_AI_PERMISSIONS","Object","fromEntries","map","role","permission","aiPermissionsOf","granted"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAWD,uDAAuD,GACvD,OAAO,MAAMA,qBAA8C;IAAC;IAAU;CAAc,CAAA;AAEpF,OAAO,SAASC,eAAeC,KAAc;IAC3C,OAAOA,UAAU,YAAYA,UAAU;AACzC;AAKA;;;;CAIC,GACD,MAAMC,gBAA2D;IAC/DC,OAAO;IACPC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV;AAEA;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,MAAMC,qBAAgE;IAC3E;QACEC,KAAK;QACLC,OAAO;QACPC,aAAa;QACbC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAME,QAAQ;QAAM;QACtEO,kBAAkBX;QAClBY,OAAO;QACPC,YAAY;QACZC,gBAAgB;IAClB;IACA;QACER,KAAK;QACLC,OAAO;QACPC,aACE;QACFC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAME,QAAQ;QAAM;QACtEO,kBAAkBX;QAClBY,OAAO;QACPC,YAAY;QACZC,gBAAgB;IAClB;CACD,CAAA;AAED;;;;;CAKC,GACD,OAAO,MAAMC,2BAAsE;IACjF;QACET,KAAK;QACLC,OAAO;QACPC,aAAa;QACbC,cAAc;YAAEC,OAAO;YAAMT,OAAO;YAAMC,QAAQ;YAAOE,QAAQ;QAAM;QACvEQ,OAAO;QACPC,YAAY;IACd;CACD,CAAA;AAED,MAAMG,aAAwC;IAAC;IAAS;IAAU;IAAU;CAAS;AAErF,2EAA2E,GAC3E,OAAO,MAAMC,2BACXC,OAAOC,WAAW,CAChBH,WAAWI,GAAG,CAAC,CAACC,OAAS;QACvBA;QACAH,OAAOC,WAAW,CAChBd,mBAAmBe,GAAG,CAAC,CAACE;gBAEtBA;mBAFqC;gBACrCA,WAAWhB,GAAG;gBACdgB,EAAAA,+BAAAA,WAAWX,gBAAgB,qBAA3BW,4BAA6B,CAACD,KAAK,MAAK;aACzC;;KAEJ,GAC6C;AAElD,+EAA+E,GAC/E,OAAO,SAASE,gBACdC,OAA6D;IAE7D,OAAO;QACL,UAAUA,CAAAA,2BAAAA,OAAS,CAAC,SAAS,MAAK;QAClC,eAAeA,CAAAA,2BAAAA,OAAS,CAAC,cAAc,MAAK;IAC9C;AACF"}
|
|
@@ -233,6 +233,24 @@ export interface AiCatalogProposal {
|
|
|
233
233
|
products: AiCatalogProduct[];
|
|
234
234
|
notes: string[];
|
|
235
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* The re-ask a catalog whose names are kinds of product is answered with
|
|
238
|
+
* (AGL-4065). The 2026-10-25 Berm & Bolt Moto Parts start listed "Riding Gear
|
|
239
|
+
* Item", "OEM ATV Parts Order" and "Aftermarket Dirt Bike Part" at suggested
|
|
240
|
+
* prices, from a brief that named kinds of part and no product.
|
|
241
|
+
*/
|
|
242
|
+
export declare const AI_CATALOG_NAME_REASK = "Name real products a shopper would buy, never a category with \u201CItem\u201D or \u201COrder\u201D after it; where the brief names kinds of product and no product, name each one a plain item of one of those kinds, never a brand or a model the brief does not name.";
|
|
243
|
+
/** The code a catalog's kind-for-a-name is refused under. */
|
|
244
|
+
export declare const AI_CATALOG_NAME_CODE = "catalog-name-kind";
|
|
245
|
+
/** The make a text names that the brief does not (AGL-4065), or `null`: a sample row or a product names none. Pure. */
|
|
246
|
+
export declare function aiTextNamesBrand(text: string, brief: string): string | null;
|
|
247
|
+
/**
|
|
248
|
+
* Why a proposed product's name is not one a shopper would buy (AGL-4065), or
|
|
249
|
+
* `null`: a kind of product with a word like "Item", "Order" or "Part" after
|
|
250
|
+
* it, or a make or a model number the brief does not name. "Dirt Bike Tire"
|
|
251
|
+
* and "Motocross helmet" are products; "Riding Gear Item" is a kind. Pure.
|
|
252
|
+
*/
|
|
253
|
+
export declare function aiCatalogNameProblem(name: string, brief: string): string | null;
|
|
236
254
|
export type AiDiscountKind = 'percent' | 'fixed' | 'free_shipping';
|
|
237
255
|
export interface AiProposedDiscount {
|
|
238
256
|
name: string;
|
|
@@ -236,6 +236,38 @@ export const AI_PRODUCTS_TOO_MANY_COPY = `Pick at most ${AI_PRODUCTS_BULK_MAX} p
|
|
|
236
236
|
target: target
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
|
+
// ── A product a shopper would buy (AGL-4065) ────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* The re-ask a catalog whose names are kinds of product is answered with
|
|
242
|
+
* (AGL-4065). The 2026-10-25 Berm & Bolt Moto Parts start listed "Riding Gear
|
|
243
|
+
* Item", "OEM ATV Parts Order" and "Aftermarket Dirt Bike Part" at suggested
|
|
244
|
+
* prices, from a brief that named kinds of part and no product.
|
|
245
|
+
*/ export const AI_CATALOG_NAME_REASK = 'Name real products a shopper would buy, never a category with “Item” or “Order” after it; where the brief names kinds of product and no product, name each one a plain item of one of those kinds, never a brand or a model the brief does not name.';
|
|
246
|
+
/** The code a catalog's kind-for-a-name is refused under. */ export const AI_CATALOG_NAME_CODE = 'catalog-name-kind';
|
|
247
|
+
/** A name's last word that names a kind of thing, or a transaction, never one thing a shopper buys. */ const KIND_HEAD = /\b(?:items?|orders?|products?|parts?|things?|options?|selections?|assortments?|stuff|goods|merchandise|gear|accessor(?:y|ies)|suppl(?:y|ies)|equipment|essentials?|requests?|inquir(?:y|ies))$/i;
|
|
248
|
+
/** Makes a product name carries only where the brief names them: vehicles, apparel and electronics most of all. */ const BRANDS = /\b(?:porsche|ferrari|lamborghini|bmw|mercedes|audi|volkswagen|toyota|honda|yamaha|kawasaki|suzuki|ktm|ducati|harley(?:-davidson)?|husqvarna|gasgas|polaris|can-am|tesla|ford|chevrolet|chevy|dodge|jeep|nissan|subaru|mazda|nike|adidas|reebok|samsung|iphone|playstation|xbox|nintendo|starbucks|coca-cola|pepsi)\b/i;
|
|
249
|
+
/** A model number: letters and figures run together, as "CRF450R" or "KX250". */ const MODEL_CODE = /\b(?=[A-Z0-9-]*\d)(?=[A-Z0-9-]*[A-Z])[A-Z0-9][A-Z0-9-]{3,}\b/;
|
|
250
|
+
/** The make a text names that the brief does not (AGL-4065), or `null`: a sample row or a product names none. Pure. */ export function aiTextNamesBrand(text, brief) {
|
|
251
|
+
var _BRANDS_exec;
|
|
252
|
+
const brand = (_BRANDS_exec = BRANDS.exec(text)) == null ? void 0 : _BRANDS_exec[0];
|
|
253
|
+
return brand && !brief.toLowerCase().includes(brand.toLowerCase()) ? brand : null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Why a proposed product's name is not one a shopper would buy (AGL-4065), or
|
|
257
|
+
* `null`: a kind of product with a word like "Item", "Order" or "Part" after
|
|
258
|
+
* it, or a make or a model number the brief does not name. "Dirt Bike Tire"
|
|
259
|
+
* and "Motocross helmet" are products; "Riding Gear Item" is a kind. Pure.
|
|
260
|
+
*/ export function aiCatalogNameProblem(name, brief) {
|
|
261
|
+
var _MODEL_CODE_exec;
|
|
262
|
+
const plain = name.replace(/\s+/g, ' ').trim();
|
|
263
|
+
if (!plain) return null;
|
|
264
|
+
const words = brief.toLowerCase();
|
|
265
|
+
if (KIND_HEAD.test(plain.replace(RegExp("[^\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}]+$", "u"), ''))) return `“${plain}” names a kind of product, not one a shopper buys`;
|
|
266
|
+
if (aiTextNamesBrand(plain, brief)) return `“${plain}” names a make the brief does not`;
|
|
267
|
+
const model = (_MODEL_CODE_exec = MODEL_CODE.exec(plain)) == null ? void 0 : _MODEL_CODE_exec[0];
|
|
268
|
+
if (model && !words.includes(model.toLowerCase())) return `“${plain}” names a model the brief does not`;
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
239
271
|
/** Output ids, so a pass that runs again finds what it already proposed. */ export const AI_PRODUCTS_OUTPUT_IDS = {
|
|
240
272
|
copy: (productId)=>`copy:${productId != null ? productId : 'new'}`,
|
|
241
273
|
catalog: 'catalog',
|