@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.231
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/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +11 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +8 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
- package/src/lib/jobs/ai-job-layout-language.js +194 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +105 -5
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
- package/src/lib/jobs/ai-job-page-language.js +355 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +105 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +8 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +4 -0
- package/src/lib/jobs/ai-job-site-step.js +20 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +457 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
- package/src/lib/layout-language/ai-layout-language.js +446 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
- package/src/lib/layout-language/ai-layout-store.js +69 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +48 -6
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +22 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +19 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +5 -1
- package/src/lib/model/ai-site-build-progress.js +27 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +8 -0
- package/src/lib/model/ai-site-job.js +9 -0
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +5 -2
- package/src/lib/model/ai-site-start.js +5 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/plugin.js +79 -47
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +1 -1
- package/src/lib/providers/catalog.js +3 -3
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +12 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
|
@@ -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'\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\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]\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; `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\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/**\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\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\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\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\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 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\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 /** 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 /** 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"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAiED,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;CACD,CAAA;AAoBD,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'\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\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]\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; `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\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/**\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\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\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 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\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 /** 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"],"names":["AI_JOB_KINDS","AI_JOB_TERMINAL_STATUSES","AI_JOB_PLAN_STEP"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAiED,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;CACD,CAAA;AAoBD,OAAO,MAAMC,2BAAmD;IAC9D;IACA;IACA;CACD,CAAA;AAID,uFAAuF,GACvF,OAAO,MAAMC,mBAAmB,OAAM"}
|
|
@@ -45,7 +45,7 @@ export interface AiSiteBuildRow {
|
|
|
45
45
|
}
|
|
46
46
|
/** Where the whole job stands, as the page's header and actions read it. */
|
|
47
47
|
export type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled';
|
|
48
|
-
export declare function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiSiteBuildPhase;
|
|
48
|
+
export declare function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'review' | 'autoConfirm' | 'updatedAt'>>): AiSiteBuildPhase;
|
|
49
49
|
/**
|
|
50
50
|
* One row a stage: planning, then each creation the plan builds before its
|
|
51
51
|
* pages — the colors, the layout, the form — then each page (AGL-3596). A
|
|
@@ -57,6 +57,10 @@ export declare function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiS
|
|
|
57
57
|
export declare function aiSiteBuildRows(job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> & Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>): AiSiteBuildRow[];
|
|
58
58
|
/** What the planning row says while it runs. */
|
|
59
59
|
export declare const AI_SITE_PLAN_HINT = "Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.";
|
|
60
|
+
/** What the page being written says while it is the active row (AGL-3596). */
|
|
61
|
+
export declare const AI_SITE_PAGE_HINT = "Writing this page\u2019s sections from your plan. It is done once every section is in.";
|
|
62
|
+
/** What any other part being built says while it is the active row. */
|
|
63
|
+
export declare const AI_SITE_ITEM_HINT = "Building this now. The next step starts when it is done.";
|
|
60
64
|
/**
|
|
61
65
|
* How far the job is, from 0 to 1, by its rows: a finished row counts whole
|
|
62
66
|
* and the active one half. `null` before there is more than one row to count.
|
|
@@ -14,10 +14,13 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import { aiJobKindNoun } from "./ai-job-activity.js";
|
|
17
|
+
*/ import { aiJobConfirmingOwnPlan, aiJobKindNoun } from "./ai-job-activity.js";
|
|
18
18
|
import { aiJobRefundCopy } from "./ai-job-failure-copy.js";
|
|
19
19
|
import { aiBuildItemRows, aiBuildOutcomeLine, aiSitePartialCopy } from "./ai-build-progress.js";
|
|
20
20
|
export function aiSiteBuildPhase(job) {
|
|
21
|
+
// A guided start's plan parked for the instant its confirmation takes is
|
|
22
|
+
// the build going on (AGL-3596), never a stop.
|
|
23
|
+
if (aiJobConfirmingOwnPlan(job)) return 'working';
|
|
21
24
|
switch(job.status){
|
|
22
25
|
case 'done':
|
|
23
26
|
return 'done';
|
|
@@ -73,7 +76,7 @@ export function aiSiteBuildPhase(job) {
|
|
|
73
76
|
state: 'done'
|
|
74
77
|
},
|
|
75
78
|
...aiBuildItemRows(job).map((row, index)=>{
|
|
76
|
-
var _job_items, _ledger_creditsSpent, _ledger_creditsRefunded;
|
|
79
|
+
var _job_items, _ledger_creditsSpent, _ledger_creditsRefunded, _row_startedAt;
|
|
77
80
|
const ledger = ((_job_items = job.items) != null ? _job_items : [])[index];
|
|
78
81
|
// A site keeps the scaffold's own words for each stage (AGL-3596).
|
|
79
82
|
const label = !site || !ledger ? row.label : ledger.op === 'theme' ? 'Choosing your colors' : ledger.op === 'layout' ? `Building the header and footer: ${ledger.label}` : ledger.op === 'form' ? `Building the form: ${ledger.label}` : ledger.op === 'page' ? `Writing page ${pages.indexOf(ledger) + 1} of ${pages.length}: ${ledger.label}` : ledger.op === 'email' ? 'Writing your welcome email' : row.label;
|
|
@@ -84,8 +87,16 @@ export function aiSiteBuildPhase(job) {
|
|
|
84
87
|
id: row.slot,
|
|
85
88
|
label,
|
|
86
89
|
state: row.state,
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
// A part that failed while the rest is still being built says the
|
|
91
|
+
// build goes on (AGL-3596): read alone, it looked like the site had.
|
|
92
|
+
detail: row.state === 'failed' && phase === 'working' ? [
|
|
93
|
+
row.detail,
|
|
94
|
+
site ? 'The rest of your site keeps building.' : 'The rest keeps building.'
|
|
95
|
+
].filter(Boolean).join(' ') : row.detail
|
|
96
|
+
}, row.state === 'active' ? {
|
|
97
|
+
startedAt: (_row_startedAt = row.startedAt) != null ? _row_startedAt : null,
|
|
98
|
+
hint: (ledger == null ? void 0 : ledger.op) === 'page' ? AI_SITE_PAGE_HINT : AI_SITE_ITEM_HINT
|
|
99
|
+
} : {}, (screen == null ? void 0 : screen.sections.length) ? {
|
|
89
100
|
sections: screen.sections.map((section)=>section.name)
|
|
90
101
|
} : {}, finished && ledger ? {
|
|
91
102
|
credits: net
|
|
@@ -121,6 +132,16 @@ export function aiSiteBuildPhase(job) {
|
|
|
121
132
|
label: 'Building the header and footer',
|
|
122
133
|
state: 'waiting'
|
|
123
134
|
});
|
|
135
|
+
// A guided start that says where its contact form's submissions go
|
|
136
|
+
// plans that form, built after the layout and before the pages (AGL-3596).
|
|
137
|
+
const submissions = job.siteInputs['submissions'];
|
|
138
|
+
if (submissions === 'inbox' || submissions === 'lead') {
|
|
139
|
+
rows.push({
|
|
140
|
+
id: 'form',
|
|
141
|
+
label: 'Building your contact form',
|
|
142
|
+
state: 'waiting'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
124
145
|
rows.push({
|
|
125
146
|
id: 'pages',
|
|
126
147
|
label: pagesAsked > 1 ? `Writing your ${pagesAsked} pages` : 'Writing your pages',
|
|
@@ -176,6 +197,8 @@ export function aiSiteBuildPhase(job) {
|
|
|
176
197
|
return rows;
|
|
177
198
|
}
|
|
178
199
|
/** What the planning row says while it runs. */ export const AI_SITE_PLAN_HINT = 'Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.';
|
|
200
|
+
/** What the page being written says while it is the active row (AGL-3596). */ export const AI_SITE_PAGE_HINT = 'Writing this page’s sections from your plan. It is done once every section is in.';
|
|
201
|
+
/** What any other part being built says while it is the active row. */ export const AI_SITE_ITEM_HINT = 'Building this now. The next step starts when it is done.';
|
|
179
202
|
/** The plan's page an item builds: by its title, else by its place among the pages. */ function aiPlanScreenFor(plan, title, index) {
|
|
180
203
|
var _ref, _screens_find, _screens_index;
|
|
181
204
|
const screens = (_ref = plan == null ? void 0 : plan.screens) != null ? _ref : [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-build-progress.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 { aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport { aiBuildItemRows, aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport type { AiJobSummary } from './ai-jobs.types'\n\n/**\n * What the \"Building your site\" page shows of a site job (AGL-3594): one row\n * a stage — planning the pages, then each page — with its state, the state\n * the whole job is in, and the job's one price.\n *\n * Read off the job summary every surface already follows: the plan step's\n * status, the plan's pages, and the pages the build has reported as outputs.\n * A scaffold builds its units in plan order and reports each page when it is\n * written, so the pages written so far are the first pages of the plan.\n *\n * Pure: no React, no request. The page and its spec read the same rows.\n */\n\nexport type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiSiteBuildRow {\n id: string\n label: string\n state: AiSiteBuildRowState\n /** What the person should know about this row (AGL-3616): a build item's failure, note or refund. */\n detail?: string | null\n /** What the row is doing while it is the active one, in a sentence. */\n hint?: string | null\n /** A page's planned sections, in order, so the person sees what is being written. */\n sections?: string[]\n /** The credits this row's work used, once it has finished; absent while it runs. */\n credits?: number\n /** When this row's work started, for the elapsed time beside the active row. */\n startedAt?: string | null\n}\n\n/** Where the whole job stands, as the page's header and actions read it. */\nexport type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled'\n\nexport function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiSiteBuildPhase {\n switch (job.status) {\n case 'done':\n return 'done'\n case 'failed':\n return 'failed'\n case 'canceled':\n return 'canceled'\n case 'needs_review':\n case 'needs_input':\n return 'stopped'\n default:\n return 'working'\n }\n}\n\n/** The creations a scaffold builds before its pages, in its build order, and what each row says. */\nconst CREATION_ROWS: ReadonlyArray<{ kind: string; resource: string; label: (name: string) => string }> = [\n { kind: 'theme-change', resource: 'theme', label: () => 'Choosing your colors' },\n { kind: 'layout', resource: 'layout', label: (name) => `Building the header and footer: ${name}` },\n { kind: 'form', resource: 'form', label: (name) => `Building the form: ${name}` },\n]\n\n/**\n * One row a stage: planning, then each creation the plan builds before its\n * pages — the colors, the layout, the form — then each page (AGL-3596). A\n * scaffold builds them in that order and reports each as an output when it\n * is written, so the first row without its output is where the build is, and\n * where a failure stopped it: a layout that broke is the layout's row, never\n * the first page's.\n */\nexport function aiSiteBuildRows(\n job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> &\n Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>,\n): AiSiteBuildRow[] {\n const phase = aiSiteBuildPhase(job)\n // A build is read off its item ledger (AGL-3616): one row an item, each with\n // its own state and what became of its credits.\n if (job.items?.length) {\n const site = job.kind !== 'build'\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n return [\n { id: 'plan', label: site ? 'Planning your pages' : 'Planning what to build', state: 'done' },\n ...aiBuildItemRows(job).map((row, index) => {\n const ledger = (job.items ?? [])[index]\n // A site keeps the scaffold's own words for each stage (AGL-3596).\n const label =\n !site || !ledger\n ? row.label\n : ledger.op === 'theme'\n ? 'Choosing your colors'\n : ledger.op === 'layout'\n ? `Building the header and footer: ${ledger.label}`\n : ledger.op === 'form'\n ? `Building the form: ${ledger.label}`\n : ledger.op === 'page'\n ? `Writing page ${pages.indexOf(ledger) + 1} of ${pages.length}: ${ledger.label}`\n : ledger.op === 'email'\n ? 'Writing your welcome email'\n : row.label\n const screen = ledger?.op === 'page' ? aiPlanScreenFor(job.plan, ledger.label, pages.indexOf(ledger)) : null\n const finished = row.state === 'done' || row.state === 'failed'\n const net = ledger ? Math.max(0, Math.floor((ledger.creditsSpent ?? 0) - (ledger.creditsRefunded ?? 0))) : 0\n return {\n id: row.slot,\n label,\n state: row.state,\n detail: row.detail,\n ...(screen?.sections.length ? { sections: screen.sections.map((section) => section.name) } : {}),\n ...(finished && ledger ? { credits: net } : {}),\n }\n }),\n ...aiSitePublishRow(job, phase),\n ]\n }\n const planStep = job.steps.find((step) => step.name === 'plan')\n const planDone = planStep?.status === 'done' || Boolean(job.plan)\n const stopped = phase === 'failed' || phase === 'stopped'\n const planState: AiSiteBuildRowState = planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting'\n const rows: AiSiteBuildRow[] = [\n {\n id: 'plan',\n label: 'Planning your pages',\n state: planState,\n startedAt: planStep?.startedAt ?? null,\n hint: planState === 'active' ? AI_SITE_PLAN_HINT : null,\n ...(planDone && planStep ? { credits: Math.max(0, Math.floor(planStep.creditsSpent ?? 0)) } : {}),\n },\n ]\n // Before there is a plan, the stages a guided start always goes through\n // are named from its answers, so the page shows the whole way from the\n // start rather than one row.\n if (!job.plan && job.kind !== 'build') {\n const pagesAsked = typeof job.siteInputs?.['pages'] === 'number' ? Math.round(job.siteInputs['pages'] as number) : 0\n if (job.siteInputs) {\n rows.push({ id: 'layout', label: 'Building the header and footer', state: 'waiting' })\n rows.push({\n id: 'pages',\n label: pagesAsked > 1 ? `Writing your ${pagesAsked} pages` : 'Writing your pages',\n state: 'waiting',\n })\n rows.push(...aiSitePublishRow(job, phase))\n }\n return rows\n }\n const built = new Map<string, number>()\n for (const output of job.outputs) built.set(output.resource, (built.get(output.resource) ?? 0) + 1)\n const take = (resource: string): boolean => {\n const left = built.get(resource) ?? 0\n if (left <= 0) return false\n built.set(resource, left - 1)\n return true\n }\n const stages: Array<{ id: string; label: string; resource: string; sections?: string[] }> = []\n for (const row of CREATION_ROWS) {\n const creation = job.plan?.create.find((entry) => entry.kind === row.kind)\n if (creation) stages.push({ id: row.resource, label: row.label(creation.name), resource: row.resource })\n }\n const pages = job.plan?.screens ?? []\n pages.forEach((page, index) => {\n stages.push({\n id: `page-${index}`,\n label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`,\n resource: 'screen',\n sections: page.sections.map((section) => section.name),\n })\n })\n let reached = false\n for (const stage of stages) {\n const done = phase === 'done' || take(stage.resource)\n const current = !done && !reached && planDone\n if (current) reached = true\n rows.push({\n id: stage.id,\n label: stage.label,\n state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting',\n ...(stage.sections?.length ? { sections: stage.sections } : {}),\n })\n }\n rows.push(...aiSitePublishRow(job, phase))\n return rows\n}\n\n/** What the planning row says while it runs. */\nexport const AI_SITE_PLAN_HINT =\n 'Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.'\n\n/** The plan's page an item builds: by its title, else by its place among the pages. */\nfunction aiPlanScreenFor(\n plan: AiJobSummary['plan'],\n title: string,\n index: number,\n): NonNullable<AiJobSummary['plan']>['screens'][number] | null {\n const screens = plan?.screens ?? []\n return screens.find((screen) => screen.title === title) ?? (index >= 0 ? (screens[index] ?? null) : null)\n}\n\n/**\n * A guided start ends by putting its pages live (AGL-3596): the last row, so\n * the person sees that step coming and when it is done. Only a site job\n * publishes on its own; a build publishes when asked, and says so in its plan.\n */\nfunction aiSitePublishRow(\n job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'kind' | 'sitePublish'>>,\n phase: AiSiteBuildPhase,\n): AiSiteBuildRow[] {\n if (job.kind !== 'site') return []\n const published = (job.sitePublish?.published.length ?? 0) > 0\n return [\n {\n id: 'publish',\n label: 'Publishing your site',\n // A job that stopped or failed publishes nothing, so the row is passed over.\n state: phase === 'done' ? (published ? 'done' : 'skipped') : phase === 'working' ? 'waiting' : 'skipped',\n },\n ]\n}\n\n/**\n * How far the job is, from 0 to 1, by its rows: a finished row counts whole\n * and the active one half. `null` before there is more than one row to count.\n */\nexport function aiSiteBuildFraction(rows: readonly AiSiteBuildRow[]): number | null {\n if (rows.length < 2) return null\n const counted = rows.reduce(\n (sum, row) => sum + (row.state === 'active' ? 0.5 : row.state === 'waiting' ? 0 : 1),\n 0,\n )\n return Math.min(1, counted / rows.length)\n}\n\n/**\n * The job's one price (AGL-3594), never a plan's on its own: what it has used\n * so far while it works, what it used once done, and, on a failure we gave\n * back, that nothing was charged.\n */\nexport function aiSiteBuildCreditsLine(\n job: Pick<AiJobSummary, 'status' | 'creditsSpent' | 'refundedCredits'> & Partial<Pick<AiJobSummary, 'kind'>>,\n): string | null {\n const spent = Math.max(0, job.creditsSpent ?? 0)\n const refunded = Math.max(0, job.refundedCredits ?? 0)\n const net = Math.max(0, spent - refunded)\n const phase = aiSiteBuildPhase(job)\n // What the job made, by its kind; a job that names none is a site's.\n const subject = !job.kind || job.kind === 'site' ? 'This site' : aiJobKindNoun(job.kind) === 'AI job' ? 'This AI job' : `This ${aiJobKindNoun(job.kind)}`\n // A failure on our side says so in the words every surface uses (AGL-3596),\n // from the job's recorded give-back.\n const refund = phase === 'working' || phase === 'done' ? null : aiJobRefundCopy(job)\n if (refund && refunded > 0) return refund\n if (phase === 'done') return `${subject} used ${net} credits.`\n if (phase === 'working') return `Credits used so far: ${net}`\n if (refund) return `${subject} used ${net} credits. ${refund}`\n return net > 0 ? `${subject} used ${net} credits.` : null\n}\n\n/** The heading and the sentence under it, for one job's page in its current phase. */\nexport interface AiJobPageCopy {\n heading: string\n lede: string\n}\n\n/**\n * What one job's page says at the top (AGL-3594, AGL-3596). A site job reads\n * as \"Building your site\"; any other kind names what it makes — \"Building\n * your page\" — or, for a kind with no noun, the job itself.\n *\n * A guided site start publishes its pages when it finishes, and says so\n * (`sitePublish`): with a page live the heading is \"Your site is live\". A\n * done site job without that record — an older job, or one that was not a\n * guided start — built drafts, and says that instead.\n */\nexport function aiJobPageCopy(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish' | 'items'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n // A build is what the person asked for, item by item (AGL-3616).\n if (job.kind === 'build') return aiBuildPageCopy(job, brand)\n const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind)\n const generic = noun === 'AI job'\n // `products` is the one plural noun.\n const isAre = noun.endsWith('s') ? 'are' : 'is'\n const why = job.review?.message ?? job.error ?? null\n if (phase === 'working') {\n return {\n heading: generic ? 'Your AI job is running' : `Building your ${noun}`,\n lede:\n job.kind === 'site'\n ? `${brand} AI is planning your pages and writing each one. You can leave this page; it keeps going.`\n : `${brand} AI is working on it. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'done') {\n const published = job.sitePublish?.published.length ?? 0\n // Part of the site was built (AGL-3616): what was, what was not, and its credits.\n const partial = job.kind === 'site' ? aiSitePartialCopy({ items: job.items, status: job.status }) : null\n if (job.kind === 'site' && published > 0) {\n return {\n heading: 'Your site is live',\n lede: [\n partial,\n job.sitePublish?.drafts.length\n ? 'Your pages are published, except the ones listed below, which stayed drafts.'\n : 'Your pages are published, and anyone can visit your site now.',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (partial) return { heading: 'Your site is ready', lede: `${partial} Your new pages are drafts. Publish them when you’re happy.` }\n if (job.kind === 'site' && job.sitePublish) {\n return {\n heading: 'Your site is ready',\n lede: 'None of your new pages could be published, so they are drafts. The reasons are below.',\n }\n }\n return {\n heading: generic ? 'Your AI job finished' : `Your ${noun} ${isAre} ready`,\n lede:\n job.kind === 'site'\n ? 'Your new pages are drafts. Publish them when you’re happy.'\n : 'Everything it built is an unpublished draft until you publish it.',\n }\n }\n if (phase === 'canceled') {\n return { heading: generic ? 'Your AI job was canceled' : `Your ${noun} was not built`, lede: 'The job was canceled.' }\n }\n return {\n heading: generic ? 'Your AI job stopped' : `Your ${noun} ${noun.endsWith('s') ? 'were' : 'was'} not built`,\n lede: why ?? (generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`),\n }\n}\n\n/** A build's page heading and sentence (AGL-3616). */\nfunction aiBuildPageCopy(\n job: Pick<AiJobSummary, 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'items' | 'sitePublish'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n const outcome = aiBuildOutcomeLine({ items: job.items ?? [], status: job.status })\n if (phase === 'working') {\n return {\n heading: 'Building what you asked for',\n lede: `${brand} AI is building each part in turn. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'stopped') {\n return {\n heading: job.review?.reason === 'plan' ? 'Your plan is ready' : 'Your build needs you',\n lede: job.review?.message ?? job.error ?? 'It waits for your decision.',\n }\n }\n if (phase === 'done') {\n const failed = (job.items ?? []).some((row) => row.status === 'failed' || row.status === 'skipped')\n const live = job.sitePublish?.published.length ?? 0\n return {\n heading: failed ? 'Most of it is ready' : 'Everything you asked for is ready',\n lede: [\n outcome,\n live ? 'Your new pages are published.' : 'Everything it built is an unpublished draft until you publish it.',\n failed ? 'Try again builds only what failed.' : '',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (phase === 'canceled') return { heading: 'Your build was canceled', lede: 'The job was canceled.' }\n return {\n heading: 'Nothing could be built',\n lede: [job.error ?? 'Something went wrong building what you asked for.', outcome].filter(Boolean).join(' '),\n }\n}\n"],"names":["aiJobKindNoun","aiJobRefundCopy","aiBuildItemRows","aiBuildOutcomeLine","aiSitePartialCopy","aiSiteBuildPhase","job","status","CREATION_ROWS","kind","resource","label","name","aiSiteBuildRows","planStep","built","phase","items","length","site","pages","filter","row","op","id","state","map","index","ledger","indexOf","screen","aiPlanScreenFor","plan","finished","net","Math","max","floor","creditsSpent","creditsRefunded","slot","detail","sections","section","credits","aiSitePublishRow","steps","find","step","planDone","Boolean","stopped","planState","rows","startedAt","hint","AI_SITE_PLAN_HINT","pagesAsked","siteInputs","round","push","Map","output","outputs","set","get","take","left","stages","creation","create","entry","screens","forEach","page","title","reached","stage","done","current","published","sitePublish","aiSiteBuildFraction","counted","reduce","sum","min","aiSiteBuildCreditsLine","spent","refunded","refundedCredits","subject","refund","aiJobPageCopy","brand","aiBuildPageCopy","noun","generic","isAre","endsWith","why","review","message","error","heading","lede","partial","drafts","join","outcome","reason","failed","some","live"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-build-progress.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 { aiJobConfirmingOwnPlan, aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport { aiBuildItemRows, aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport type { AiJobSummary } from './ai-jobs.types'\n\n/**\n * What the \"Building your site\" page shows of a site job (AGL-3594): one row\n * a stage — planning the pages, then each page — with its state, the state\n * the whole job is in, and the job's one price.\n *\n * Read off the job summary every surface already follows: the plan step's\n * status, the plan's pages, and the pages the build has reported as outputs.\n * A scaffold builds its units in plan order and reports each page when it is\n * written, so the pages written so far are the first pages of the plan.\n *\n * Pure: no React, no request. The page and its spec read the same rows.\n */\n\nexport type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiSiteBuildRow {\n id: string\n label: string\n state: AiSiteBuildRowState\n /** What the person should know about this row (AGL-3616): a build item's failure, note or refund. */\n detail?: string | null\n /** What the row is doing while it is the active one, in a sentence. */\n hint?: string | null\n /** A page's planned sections, in order, so the person sees what is being written. */\n sections?: string[]\n /** The credits this row's work used, once it has finished; absent while it runs. */\n credits?: number\n /** When this row's work started, for the elapsed time beside the active row. */\n startedAt?: string | null\n}\n\n/** Where the whole job stands, as the page's header and actions read it. */\nexport type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled'\n\nexport function aiSiteBuildPhase(\n job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'review' | 'autoConfirm' | 'updatedAt'>>,\n): AiSiteBuildPhase {\n // A guided start's plan parked for the instant its confirmation takes is\n // the build going on (AGL-3596), never a stop.\n if (aiJobConfirmingOwnPlan(job)) return 'working'\n switch (job.status) {\n case 'done':\n return 'done'\n case 'failed':\n return 'failed'\n case 'canceled':\n return 'canceled'\n case 'needs_review':\n case 'needs_input':\n return 'stopped'\n default:\n return 'working'\n }\n}\n\n/** The creations a scaffold builds before its pages, in its build order, and what each row says. */\nconst CREATION_ROWS: ReadonlyArray<{ kind: string; resource: string; label: (name: string) => string }> = [\n { kind: 'theme-change', resource: 'theme', label: () => 'Choosing your colors' },\n { kind: 'layout', resource: 'layout', label: (name) => `Building the header and footer: ${name}` },\n { kind: 'form', resource: 'form', label: (name) => `Building the form: ${name}` },\n]\n\n/**\n * One row a stage: planning, then each creation the plan builds before its\n * pages — the colors, the layout, the form — then each page (AGL-3596). A\n * scaffold builds them in that order and reports each as an output when it\n * is written, so the first row without its output is where the build is, and\n * where a failure stopped it: a layout that broke is the layout's row, never\n * the first page's.\n */\nexport function aiSiteBuildRows(\n job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> &\n Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>,\n): AiSiteBuildRow[] {\n const phase = aiSiteBuildPhase(job)\n // A build is read off its item ledger (AGL-3616): one row an item, each with\n // its own state and what became of its credits.\n if (job.items?.length) {\n const site = job.kind !== 'build'\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n return [\n { id: 'plan', label: site ? 'Planning your pages' : 'Planning what to build', state: 'done' },\n ...aiBuildItemRows(job).map((row, index) => {\n const ledger = (job.items ?? [])[index]\n // A site keeps the scaffold's own words for each stage (AGL-3596).\n const label =\n !site || !ledger\n ? row.label\n : ledger.op === 'theme'\n ? 'Choosing your colors'\n : ledger.op === 'layout'\n ? `Building the header and footer: ${ledger.label}`\n : ledger.op === 'form'\n ? `Building the form: ${ledger.label}`\n : ledger.op === 'page'\n ? `Writing page ${pages.indexOf(ledger) + 1} of ${pages.length}: ${ledger.label}`\n : ledger.op === 'email'\n ? 'Writing your welcome email'\n : row.label\n const screen = ledger?.op === 'page' ? aiPlanScreenFor(job.plan, ledger.label, pages.indexOf(ledger)) : null\n const finished = row.state === 'done' || row.state === 'failed'\n const net = ledger ? Math.max(0, Math.floor((ledger.creditsSpent ?? 0) - (ledger.creditsRefunded ?? 0))) : 0\n return {\n id: row.slot,\n label,\n state: row.state,\n // A part that failed while the rest is still being built says the\n // build goes on (AGL-3596): read alone, it looked like the site had.\n detail:\n row.state === 'failed' && phase === 'working'\n ? [row.detail, site ? 'The rest of your site keeps building.' : 'The rest keeps building.'].filter(Boolean).join(' ')\n : row.detail,\n ...(row.state === 'active'\n ? { startedAt: row.startedAt ?? null, hint: ledger?.op === 'page' ? AI_SITE_PAGE_HINT : AI_SITE_ITEM_HINT }\n : {}),\n ...(screen?.sections.length ? { sections: screen.sections.map((section) => section.name) } : {}),\n ...(finished && ledger ? { credits: net } : {}),\n }\n }),\n ...aiSitePublishRow(job, phase),\n ]\n }\n const planStep = job.steps.find((step) => step.name === 'plan')\n const planDone = planStep?.status === 'done' || Boolean(job.plan)\n const stopped = phase === 'failed' || phase === 'stopped'\n const planState: AiSiteBuildRowState = planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting'\n const rows: AiSiteBuildRow[] = [\n {\n id: 'plan',\n label: 'Planning your pages',\n state: planState,\n startedAt: planStep?.startedAt ?? null,\n hint: planState === 'active' ? AI_SITE_PLAN_HINT : null,\n ...(planDone && planStep ? { credits: Math.max(0, Math.floor(planStep.creditsSpent ?? 0)) } : {}),\n },\n ]\n // Before there is a plan, the stages a guided start always goes through\n // are named from its answers, so the page shows the whole way from the\n // start rather than one row.\n if (!job.plan && job.kind !== 'build') {\n const pagesAsked = typeof job.siteInputs?.['pages'] === 'number' ? Math.round(job.siteInputs['pages'] as number) : 0\n if (job.siteInputs) {\n rows.push({ id: 'layout', label: 'Building the header and footer', state: 'waiting' })\n // A guided start that says where its contact form's submissions go\n // plans that form, built after the layout and before the pages (AGL-3596).\n const submissions = job.siteInputs['submissions']\n if (submissions === 'inbox' || submissions === 'lead') {\n rows.push({ id: 'form', label: 'Building your contact form', state: 'waiting' })\n }\n rows.push({\n id: 'pages',\n label: pagesAsked > 1 ? `Writing your ${pagesAsked} pages` : 'Writing your pages',\n state: 'waiting',\n })\n rows.push(...aiSitePublishRow(job, phase))\n }\n return rows\n }\n const built = new Map<string, number>()\n for (const output of job.outputs) built.set(output.resource, (built.get(output.resource) ?? 0) + 1)\n const take = (resource: string): boolean => {\n const left = built.get(resource) ?? 0\n if (left <= 0) return false\n built.set(resource, left - 1)\n return true\n }\n const stages: Array<{ id: string; label: string; resource: string; sections?: string[] }> = []\n for (const row of CREATION_ROWS) {\n const creation = job.plan?.create.find((entry) => entry.kind === row.kind)\n if (creation) stages.push({ id: row.resource, label: row.label(creation.name), resource: row.resource })\n }\n const pages = job.plan?.screens ?? []\n pages.forEach((page, index) => {\n stages.push({\n id: `page-${index}`,\n label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`,\n resource: 'screen',\n sections: page.sections.map((section) => section.name),\n })\n })\n let reached = false\n for (const stage of stages) {\n const done = phase === 'done' || take(stage.resource)\n const current = !done && !reached && planDone\n if (current) reached = true\n rows.push({\n id: stage.id,\n label: stage.label,\n state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting',\n ...(stage.sections?.length ? { sections: stage.sections } : {}),\n })\n }\n rows.push(...aiSitePublishRow(job, phase))\n return rows\n}\n\n/** What the planning row says while it runs. */\nexport const AI_SITE_PLAN_HINT =\n 'Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.'\n\n/** What the page being written says while it is the active row (AGL-3596). */\nexport const AI_SITE_PAGE_HINT =\n 'Writing this page’s sections from your plan. It is done once every section is in.'\n\n/** What any other part being built says while it is the active row. */\nexport const AI_SITE_ITEM_HINT = 'Building this now. The next step starts when it is done.'\n\n/** The plan's page an item builds: by its title, else by its place among the pages. */\nfunction aiPlanScreenFor(\n plan: AiJobSummary['plan'],\n title: string,\n index: number,\n): NonNullable<AiJobSummary['plan']>['screens'][number] | null {\n const screens = plan?.screens ?? []\n return screens.find((screen) => screen.title === title) ?? (index >= 0 ? (screens[index] ?? null) : null)\n}\n\n/**\n * A guided start ends by putting its pages live (AGL-3596): the last row, so\n * the person sees that step coming and when it is done. Only a site job\n * publishes on its own; a build publishes when asked, and says so in its plan.\n */\nfunction aiSitePublishRow(\n job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'kind' | 'sitePublish'>>,\n phase: AiSiteBuildPhase,\n): AiSiteBuildRow[] {\n if (job.kind !== 'site') return []\n const published = (job.sitePublish?.published.length ?? 0) > 0\n return [\n {\n id: 'publish',\n label: 'Publishing your site',\n // A job that stopped or failed publishes nothing, so the row is passed over.\n state: phase === 'done' ? (published ? 'done' : 'skipped') : phase === 'working' ? 'waiting' : 'skipped',\n },\n ]\n}\n\n/**\n * How far the job is, from 0 to 1, by its rows: a finished row counts whole\n * and the active one half. `null` before there is more than one row to count.\n */\nexport function aiSiteBuildFraction(rows: readonly AiSiteBuildRow[]): number | null {\n if (rows.length < 2) return null\n const counted = rows.reduce(\n (sum, row) => sum + (row.state === 'active' ? 0.5 : row.state === 'waiting' ? 0 : 1),\n 0,\n )\n return Math.min(1, counted / rows.length)\n}\n\n/**\n * The job's one price (AGL-3594), never a plan's on its own: what it has used\n * so far while it works, what it used once done, and, on a failure we gave\n * back, that nothing was charged.\n */\nexport function aiSiteBuildCreditsLine(\n job: Pick<AiJobSummary, 'status' | 'creditsSpent' | 'refundedCredits'> & Partial<Pick<AiJobSummary, 'kind'>>,\n): string | null {\n const spent = Math.max(0, job.creditsSpent ?? 0)\n const refunded = Math.max(0, job.refundedCredits ?? 0)\n const net = Math.max(0, spent - refunded)\n const phase = aiSiteBuildPhase(job)\n // What the job made, by its kind; a job that names none is a site's.\n const subject = !job.kind || job.kind === 'site' ? 'This site' : aiJobKindNoun(job.kind) === 'AI job' ? 'This AI job' : `This ${aiJobKindNoun(job.kind)}`\n // A failure on our side says so in the words every surface uses (AGL-3596),\n // from the job's recorded give-back.\n const refund = phase === 'working' || phase === 'done' ? null : aiJobRefundCopy(job)\n if (refund && refunded > 0) return refund\n if (phase === 'done') return `${subject} used ${net} credits.`\n if (phase === 'working') return `Credits used so far: ${net}`\n if (refund) return `${subject} used ${net} credits. ${refund}`\n return net > 0 ? `${subject} used ${net} credits.` : null\n}\n\n/** The heading and the sentence under it, for one job's page in its current phase. */\nexport interface AiJobPageCopy {\n heading: string\n lede: string\n}\n\n/**\n * What one job's page says at the top (AGL-3594, AGL-3596). A site job reads\n * as \"Building your site\"; any other kind names what it makes — \"Building\n * your page\" — or, for a kind with no noun, the job itself.\n *\n * A guided site start publishes its pages when it finishes, and says so\n * (`sitePublish`): with a page live the heading is \"Your site is live\". A\n * done site job without that record — an older job, or one that was not a\n * guided start — built drafts, and says that instead.\n */\nexport function aiJobPageCopy(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish' | 'items'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n // A build is what the person asked for, item by item (AGL-3616).\n if (job.kind === 'build') return aiBuildPageCopy(job, brand)\n const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind)\n const generic = noun === 'AI job'\n // `products` is the one plural noun.\n const isAre = noun.endsWith('s') ? 'are' : 'is'\n const why = job.review?.message ?? job.error ?? null\n if (phase === 'working') {\n return {\n heading: generic ? 'Your AI job is running' : `Building your ${noun}`,\n lede:\n job.kind === 'site'\n ? `${brand} AI is planning your pages and writing each one. You can leave this page; it keeps going.`\n : `${brand} AI is working on it. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'done') {\n const published = job.sitePublish?.published.length ?? 0\n // Part of the site was built (AGL-3616): what was, what was not, and its credits.\n const partial = job.kind === 'site' ? aiSitePartialCopy({ items: job.items, status: job.status }) : null\n if (job.kind === 'site' && published > 0) {\n return {\n heading: 'Your site is live',\n lede: [\n partial,\n job.sitePublish?.drafts.length\n ? 'Your pages are published, except the ones listed below, which stayed drafts.'\n : 'Your pages are published, and anyone can visit your site now.',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (partial) return { heading: 'Your site is ready', lede: `${partial} Your new pages are drafts. Publish them when you’re happy.` }\n if (job.kind === 'site' && job.sitePublish) {\n return {\n heading: 'Your site is ready',\n lede: 'None of your new pages could be published, so they are drafts. The reasons are below.',\n }\n }\n return {\n heading: generic ? 'Your AI job finished' : `Your ${noun} ${isAre} ready`,\n lede:\n job.kind === 'site'\n ? 'Your new pages are drafts. Publish them when you’re happy.'\n : 'Everything it built is an unpublished draft until you publish it.',\n }\n }\n if (phase === 'canceled') {\n return { heading: generic ? 'Your AI job was canceled' : `Your ${noun} was not built`, lede: 'The job was canceled.' }\n }\n return {\n heading: generic ? 'Your AI job stopped' : `Your ${noun} ${noun.endsWith('s') ? 'were' : 'was'} not built`,\n lede: why ?? (generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`),\n }\n}\n\n/** A build's page heading and sentence (AGL-3616). */\nfunction aiBuildPageCopy(\n job: Pick<AiJobSummary, 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'items' | 'sitePublish'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n const outcome = aiBuildOutcomeLine({ items: job.items ?? [], status: job.status })\n if (phase === 'working') {\n return {\n heading: 'Building what you asked for',\n lede: `${brand} AI is building each part in turn. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'stopped') {\n return {\n heading: job.review?.reason === 'plan' ? 'Your plan is ready' : 'Your build needs you',\n lede: job.review?.message ?? job.error ?? 'It waits for your decision.',\n }\n }\n if (phase === 'done') {\n const failed = (job.items ?? []).some((row) => row.status === 'failed' || row.status === 'skipped')\n const live = job.sitePublish?.published.length ?? 0\n return {\n heading: failed ? 'Most of it is ready' : 'Everything you asked for is ready',\n lede: [\n outcome,\n live ? 'Your new pages are published.' : 'Everything it built is an unpublished draft until you publish it.',\n failed ? 'Try again builds only what failed.' : '',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (phase === 'canceled') return { heading: 'Your build was canceled', lede: 'The job was canceled.' }\n return {\n heading: 'Nothing could be built',\n lede: [job.error ?? 'Something went wrong building what you asked for.', outcome].filter(Boolean).join(' '),\n }\n}\n"],"names":["aiJobConfirmingOwnPlan","aiJobKindNoun","aiJobRefundCopy","aiBuildItemRows","aiBuildOutcomeLine","aiSitePartialCopy","aiSiteBuildPhase","job","status","CREATION_ROWS","kind","resource","label","name","aiSiteBuildRows","planStep","built","phase","items","length","site","pages","filter","row","op","id","state","map","index","ledger","indexOf","screen","aiPlanScreenFor","plan","finished","net","Math","max","floor","creditsSpent","creditsRefunded","slot","detail","Boolean","join","startedAt","hint","AI_SITE_PAGE_HINT","AI_SITE_ITEM_HINT","sections","section","credits","aiSitePublishRow","steps","find","step","planDone","stopped","planState","rows","AI_SITE_PLAN_HINT","pagesAsked","siteInputs","round","push","submissions","Map","output","outputs","set","get","take","left","stages","creation","create","entry","screens","forEach","page","title","reached","stage","done","current","published","sitePublish","aiSiteBuildFraction","counted","reduce","sum","min","aiSiteBuildCreditsLine","spent","refunded","refundedCredits","subject","refund","aiJobPageCopy","brand","aiBuildPageCopy","noun","generic","isAre","endsWith","why","review","message","error","heading","lede","partial","drafts","outcome","reason","failed","some","live"],"mappings":"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"}
|
|
@@ -236,6 +236,14 @@ export declare const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{
|
|
|
236
236
|
* because it is the first thing the model is told.
|
|
237
237
|
*/
|
|
238
238
|
export declare const AI_SITE_EMAIL_TYPE = "reply";
|
|
239
|
+
/**
|
|
240
|
+
* What a site job's plan and every unit it builds are told about the
|
|
241
|
+
* business's name (AGL-3596): that it is the name, used as written. Said on
|
|
242
|
+
* the job's own turn, never in a cached system block, because it is one
|
|
243
|
+
* site's. A model handed only what the business does named it itself —
|
|
244
|
+
* "Hillside Dog Grooming" was built as "Austin Paws Grooming".
|
|
245
|
+
*/
|
|
246
|
+
export declare function aiSiteNameSentence(name: string): string;
|
|
239
247
|
/** Where a job's inputs say submissions go, or `null` where they do not say. */
|
|
240
248
|
export declare function aiSiteSubmissions(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSiteSubmissions | null;
|
|
241
249
|
/** A scaffold's inputs, or the sentence the door refuses them with. */
|
|
@@ -179,6 +179,15 @@ import { AI_PAGE_CREATE_KINDS } from "./ai-page-job.js";
|
|
|
179
179
|
* up, and a kind that misdescribes the email is worse than an absent one
|
|
180
180
|
* because it is the first thing the model is told.
|
|
181
181
|
*/ export const AI_SITE_EMAIL_TYPE = 'reply';
|
|
182
|
+
/**
|
|
183
|
+
* What a site job's plan and every unit it builds are told about the
|
|
184
|
+
* business's name (AGL-3596): that it is the name, used as written. Said on
|
|
185
|
+
* the job's own turn, never in a cached system block, because it is one
|
|
186
|
+
* site's. A model handed only what the business does named it itself —
|
|
187
|
+
* "Hillside Dog Grooming" was built as "Austin Paws Grooming".
|
|
188
|
+
*/ export function aiSiteNameSentence(name) {
|
|
189
|
+
return `The business is named “${name}”: the site names it exactly that way, in the header, the footer, the copy and the search listing, and never by any other name.`;
|
|
190
|
+
}
|
|
182
191
|
/** Where a job's inputs say submissions go, or `null` where they do not say. */ export function aiSiteSubmissions(inputs) {
|
|
183
192
|
const value = inputs == null ? void 0 : inputs['submissions'];
|
|
184
193
|
return AI_SITE_SUBMISSIONS.includes(value) ? value : null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n aiPlanUndeclaredRefs,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\nimport { AI_JOB_CREATE_KINDS } from './ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS } from './ai-page-job'\n\n/**\n * What a site scaffold is (AGL-2911): the inputs a `site` job is admitted\n * with, the plans a scaffold can build, and what one is estimated to cost\n * before it starts.\n *\n * A scaffold is the one job kind that builds a whole site rather than one\n * record: a plan of four to eight screens, the layout they render inside,\n * the contact form they place, a palette suggestion, and — where the email\n * step is loaded — a welcome email. It builds each of those through the job\n * kind that already owns it, so this module describes what a scaffold may\n * ask for and refuses the rest at confirmation, before a credit is spent,\n * naming what to make first.\n *\n * ── The estimate is the guard rail ───────────────────────────────────────\n *\n * A scaffold is tens of model calls, where every other kind is a few, so the\n * member reads what it will cost before they confirm the plan that spends\n * it. The figure is the machine's own nominal credits per step multiplied by\n * the passes the plan implies — an estimate, and said to be one wherever it\n * is shown.\n *\n * This module imports nothing at runtime, so the doors, the step, the batch\n * card and the plan proposal read one vocabulary.\n */\n\n/** How many pages a scaffold builds: enough to be a site, few enough to read. */\nexport const AI_SITE_PAGES = { min: 4, max: 8 } as const\n\n/**\n * How many pages a Free workspace's scaffold builds (AGL-3594): its home page\n * and the one page the brief most needs. A product decision, not a figure the\n * wall derives — the wall's proof (`jobs/ai-job-free-site.spec.ts`) shows a\n * plan and a build of this many pages fit the Free taste with room for one\n * retried section.\n */\nexport const AI_SITE_FREE_PAGES = { min: 1, max: 2 } as const\n\n/** The pages a scaffold builds for a workspace on the Free taste or on a paid plan. */\nexport function aiSitePagesBand(\n freeTaste: boolean | null | undefined,\n): { readonly min: number; readonly max: number } {\n return freeTaste ? AI_SITE_FREE_PAGES : AI_SITE_PAGES\n}\n\n/**\n * The job input a guided site start sets so its plan is confirmed for it\n * (AGL-3594); `jobs/ai-job-auto-confirm.ts` says where it is honored.\n */\nexport const AI_JOB_AUTO_CONFIRM_INPUT = 'autoConfirm'\n\n/** What a Free site start's page selector says under it (AGL-3594). */\nexport const AI_SITE_FREE_PAGES_NOTE = 'Paid plans can generate more pages.'\n\n/**\n * The plan's answer ceiling for a scaffold (AGL-3594), Free and paid. A plan\n * is an outline — each page's title, address, search listing and sections by\n * name — and the build steps write everything else, so a scaffold plans with\n * no thinking and at a ceiling sized to the outline of its largest band with\n * room to spare: about 300 tokens a page. The 13,491-token plan a Free site\n * spent 227 of its 300 credits on was its thinking, at the routing table's\n * 8,000 a call.\n */\nexport const AI_SITE_PLAN_MAX_TOKENS = { free: 2_000, paid: 4_000 } as const\n\n/** What each exchange of a Free site job comes to at its worst, in credits. */\nexport interface AiFreeSiteWorstCase {\n /** The site plan: its answer and its one re-ask, each at its ceiling. */\n plan: number\n /** The one layout a Free plan includes, built first on a site with none. */\n layout: number\n /** A page's first section pass, which writes the cached prefix. */\n firstSection: number\n /** Each later section pass. */\n laterSection: number\n /** A page's listing. */\n listing: number\n}\n\n/**\n * The Free site start's wall at its worst (AGL-3594): the site plan as\n * `jobs/ai-job-free-site.spec.ts` derives it from the plan request as it\n * stands, on the model a Free site plans with, every answer at its ceiling;\n * the build's exchanges are the Free page's (`AI_FREE_PAGE_WORST_CASE_CREDITS`),\n * which the same spec holds these to. The spec fails when a figure it derives\n * moves and this does not.\n */\nexport const AI_FREE_SITE_WORST_CASE_CREDITS: Readonly<AiFreeSiteWorstCase> = {\n plan: 35,\n layout: 64,\n firstSection: 44,\n laterSection: 20,\n listing: 3,\n}\n\n/**\n * The most sections a Free site's plan fits in the Free taste at its worst,\n * across all its pages: the plan, the layouts it builds first, each page's\n * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later\n * passes as the rest pays for; none when that is already past the wall. Every\n * page's first pass is counted as a cache write, which errs dear.\n */\nexport function aiFreeSiteSectionsWithin(\n creations: { layouts: number; pages: number },\n taste: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const pages = Math.max(1, Math.floor(creations.pages))\n const before =\n credits.plan +\n creations.layouts * credits.layout +\n pages * (credits.listing + credits.firstSection) +\n credits.firstSection\n if (before > taste) return 0\n return pages + Math.floor((taste - before) / credits.laterSection)\n}\n\n/** Sections a Free site's page is assumed to hold before its plan names them. */\nexport const AI_FREE_SITE_NOMINAL_SECTIONS = 3\n\n/**\n * About what a Free site start of this many pages costs at its worst, in\n * credits, for the dialog that asks for one: the site plan, each page's\n * listing and its sections at the nominal count, from the figures the wall is\n * proven with rather than the nominal credits a paid estimate counts.\n */\nexport function aiFreeSiteCreditEstimate(\n pages: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const count = Math.max(1, Math.floor(pages))\n return (\n credits.plan +\n count * (credits.listing + credits.firstSection) +\n count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection\n )\n}\n\n/**\n * The most sections one scaffolded page may hold. The plan model allows more\n * for a page job, which builds one page; a scaffold builds eight, and each\n * section is a pass of its own.\n */\nexport const AI_SITE_MAX_SECTIONS = 8\n\n/**\n * Sections a page is assumed to hold before a plan names them, for the\n * estimate a member reads when they ask for a scaffold. A declared\n * assumption, not a measurement: the plan's own section counts replace it\n * the moment the plan exists.\n */\nexport const AI_SITE_NOMINAL_SECTIONS = 5\n\n/**\n * The credits one pass is held against: the machine's\n * `AI_JOB_STEP_RESERVE_CREDITS`, declared here so the estimate stays a pure\n * number the console computes without the Admin SDK. `ai-site-job.spec.ts`\n * holds the two equal.\n */\nexport const AI_SITE_PASS_CREDITS = 50\n\n/** The longest a scaffold's scalar inputs may run; they are names, not copy. */\nexport const AI_SITE_INPUT_MAX_CHARS = 120\n\n/** The sites one agency batch may generate for in one request. */\nexport const AI_SITE_BATCH_MAX = 25\n\n/**\n * The sites a plan must hold before the org Sites page offers the batch at\n * all: the Agency and Advanced bands, which the plan's `hostLimit` is what\n * distinguishes.\n */\nexport const AI_SITE_BATCH_MIN_HOST_LIMIT = 25\n\nexport interface AiSiteJobInputs {\n /** What the business is, in a few words: the brief's subject. */\n businessType: string\n /**\n * Who the site is for, in a few words (AGL-2918); empty when nobody said.\n * The plan step puts it in front of the model on a line of its own, as it\n * does every other scalar here, so it narrows the pages a plan proposes\n * without a prompt of its own.\n */\n audience: string\n /**\n * The starter site whose shape the person liked (AGL-2918), by its\n * `STARTER_TEMPLATES` id; empty when they picked none.\n *\n * A hint carried as the id rather than the starter, because an id is what\n * the guided start's list, this door and the plan's line all agree on\n * without any of them loading a catalog of node maps. Unrecognized ids are\n * admitted for the same reason an unrecognized business type is: it is a\n * few words in a brief, not a lookup.\n */\n starter: string\n /** How many pages the member asked for. */\n pages: number\n /** The per-site variables an agency batch varies; empty when none was given. */\n businessName: string\n city: string\n brand: string\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /**\n * Where the contact form's submissions go (AGL-2918); `null` where nobody\n * said, which is every job created before the question existed and every\n * door that does not ask it.\n *\n * The one setting a new site owner actually needs made, and the one thing\n * about a contact form a model cannot know: whether a submission is a note\n * to read or a lead to chase is a decision about how the business runs. So\n * an answer BINDS the form step rather than hinting to it, and `null`\n * leaves the model's own proposal standing, exactly as it stood before.\n */\n submissions: AiSiteSubmissions | null\n /** The batch this job was created under (AGL-2911); `null` for a single site. */\n batchId: string | null\n}\n\n/**\n * Where a site's contact form sends what a visitor writes.\n *\n * Two, not three. The form step's own vocabulary has a third — a mailing\n * list — which it can only answer with a note, because the stored routing has\n * no place for a list; offering it as a question would be asking somebody to\n * pick an outcome the platform then explains it cannot store.\n */\nexport const AI_SITE_SUBMISSIONS = ['inbox', 'lead'] as const\n\nexport type AiSiteSubmissions = (typeof AI_SITE_SUBMISSIONS)[number]\n\n/**\n * What each answer to \"where do submissions go\" is called, and what it means.\n *\n * Here rather than beside the guided start's own questions, because it is not\n * only the guided start that asks: the agency batch asks the same thing of a\n * whole run (AGL-2918), and two surfaces wording one decision two ways is two\n * decisions. `ai-site-job.spec.ts` holds the list to {@link AI_SITE_SUBMISSIONS}.\n */\nexport const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{\n id: AiSiteSubmissions\n label: string\n blurb: string\n}> = [\n {\n id: 'inbox',\n label: 'The Inbox',\n blurb: 'Every message is one to read and reply to.',\n },\n {\n id: 'lead',\n label: 'The Inbox, and CRM as a lead',\n blurb: 'Every message with an email address is also somebody to follow up.',\n },\n]\n\n/**\n * Which kind of email a scaffold's email unit is (AGL-2918), carried on the\n * derived job as `inputs.emailType`. The email step names the kind on the one\n * line it puts above the brief, and a scaffold was naming none.\n *\n * A key of the email step's own `AI_EMAIL_TYPES`, held here because neither\n * step may import the other: a scaffold delegates by job kind, and a\n * deployment that scaffolds is not necessarily one that has loaded the email\n * step. `ai-job-email-step.spec.ts` holds this key to that catalog.\n *\n * `reply` and not `welcome`. The five kinds a member picks are campaigns\n * somebody sends to an audience, and this is an answer to one person who\n * wrote in through the site; the nearest, `welcome`, is a label about signing\n * up, and a kind that misdescribes the email is worse than an absent one\n * because it is the first thing the model is told.\n */\nexport const AI_SITE_EMAIL_TYPE = 'reply'\n\n/** Where a job's inputs say submissions go, or `null` where they do not say. */\nexport function aiSiteSubmissions(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteSubmissions | null {\n const value = inputs?.['submissions']\n return (AI_SITE_SUBMISSIONS as readonly unknown[]).includes(value)\n ? (value as AiSiteSubmissions)\n : null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/\n\n/** A scaffold's inputs, or the sentence the door refuses them with. */\nexport function parseAiSiteJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteJobInputs | string {\n const text = (key: string): string | null => {\n const value = inputs?.[key]\n if (value === undefined || value === null || value === '') return ''\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > AI_SITE_INPUT_MAX_CHARS ? null : trimmed\n }\n const businessType = text('businessType')\n if (businessType === null) {\n return `businessType must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n if (!businessType) return 'Say what kind of business the site is for'\n const businessName = text('businessName')\n const city = text('city')\n const brand = text('brand')\n const audience = text('audience')\n const starter = text('starter')\n for (const [key, value] of [\n ['businessName', businessName],\n ['city', city],\n ['brand', brand],\n ['audience', audience],\n ['starter', starter],\n ] as const) {\n if (value === null)\n return `${key} must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n const rawPages = inputs?.['pages']\n const pages =\n typeof rawPages === 'number' ? rawPages : Number(rawPages ?? NaN)\n // Read here across both bands; the door holds a workspace to its own\n // (`aiSitePagesRefusal`), since only it knows the workspace's plan.\n if (\n !Number.isInteger(pages) ||\n pages < AI_SITE_FREE_PAGES.min ||\n pages > AI_SITE_PAGES.max\n ) {\n return `pages must be a whole number from ${AI_SITE_FREE_PAGES.min} to ${AI_SITE_PAGES.max}`\n }\n // Where submissions go is admitted only as one of the two the form step\n // can actually bind; anything else is nobody having said, and the model\n // proposes as it always did rather than the door refusing the whole job.\n const submissions = aiSiteSubmissions(inputs)\n const rawBatch = inputs?.['batchId']\n const batchId =\n rawBatch === undefined || rawBatch === null || rawBatch === ''\n ? null\n : typeof rawBatch === 'string' && ID_CHARS.test(rawBatch)\n ? rawBatch\n : undefined\n if (batchId === undefined) return 'batchId is not a batch id'\n return {\n businessType,\n audience: audience as string,\n starter: starter as string,\n pages,\n businessName: businessName as string,\n city: city as string,\n brand: brand as string,\n welcomeEmail: inputs?.['welcomeEmail'] !== false,\n submissions,\n batchId,\n }\n}\n\n/**\n * Why a workspace cannot ask for this many pages (AGL-3594), in a sentence a\n * member reads; `null` when it can. A Free workspace builds one or two, and a\n * paid one four to eight, as it always has. Refused rather than clamped: the\n * guided start offers only the band, so a request outside it was not made\n * there, and a job quietly building fewer pages than it was asked for is a\n * worse answer than a sentence saying why.\n */\nexport function aiSitePagesRefusal(pages: number, freeTaste: boolean | null | undefined): string | null {\n const band = aiSitePagesBand(freeTaste)\n if (pages >= band.min && pages <= band.max) return null\n return freeTaste\n ? `A Free workspace's AI site start builds ${AI_SITE_FREE_PAGES.min} or ${AI_SITE_FREE_PAGES.max} pages. ${AI_SITE_FREE_PAGES_NOTE}`\n : `A site is planned with ${band.min} to ${band.max} pages.`\n}\n\n/**\n * What the site itself is, as a step that is NOT the scaffold reads it off a\n * job's inputs (AGL-2918).\n *\n * A scaffold delegates unit by unit under a job derived from its own — a page\n * job, a form job — and the derived job carries the scaffold's inputs. So the\n * two answers that describe the site rather than one record of it are\n * readable by every delegated step, and a step that wants them does not have\n * to know it was delegated to.\n *\n * Lenient where {@link parseAiSiteJobInputs} is strict, because it reads the\n * inputs of jobs that are not scaffolds: a page job started from the Screens\n * page has no `businessType` and no `pages`, and that is not an error here —\n * it is a site nobody described, and the answer is two empty strings.\n */\nexport interface AiSiteWords {\n /** What kind of site it is; empty when the job's inputs do not say. */\n about: string\n /** Who it is for; empty when the job's inputs do not say. */\n audience: string\n}\n\n/** Whether either half of {@link AiSiteWords} says anything. */\nexport function aiSiteWordsSaidAnything(words: AiSiteWords): boolean {\n return Boolean(words.about || words.audience)\n}\n\n/** What a job's inputs say the site is, read defensively and trimmed to the input ceiling. */\nexport function aiSiteWords(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteWords {\n const read = (key: string): string => {\n const value = inputs?.[key]\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n }\n return { about: read('businessType'), audience: read('audience') }\n}\n\n/**\n * What a scaffold builds for itself: the layout its pages render inside, the\n * form they place, and the palette suggestion a member applies in the Theme\n * section. Anything else a plan asks to create is a job of its own.\n */\nexport const AI_SITE_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'layout',\n 'form',\n 'theme-change',\n]\n\n/** The creations a scaffold cannot build, in the order the plan lists them, each named once. */\nexport function aiSitePlanPrerequisites(\n plan: AiBuildPlan,\n): Array<{ kind: AiBuildPlanCreateKind; name: string }> {\n const seen = new Set<string>()\n const prerequisites: Array<{ kind: AiBuildPlanCreateKind; name: string }> = []\n const add = (kind: AiBuildPlanCreateKind, name: string) => {\n const key = `${kind}:${name.toLowerCase()}`\n if (seen.has(key)) return\n seen.add(key)\n prerequisites.push({ kind, name })\n }\n for (const entry of plan.create) {\n if (!AI_SITE_CREATE_KINDS.includes(entry.kind)) add(entry.kind, entry.name)\n }\n // A reference the create list does not carry is refused by the plan rules\n // before a plan is kept: rule 2 for a screen's layout, rule 7 for anything\n // else (`plan-creation-undeclared`, AGL-3040). A plan confirmed before that\n // rule existed can still carry one. It is named as a component: the\n // reference carries no kind, and the refusal must say where one is made.\n for (const { name } of aiPlanUndeclaredRefs(plan)) add('component', name)\n return prerequisites\n}\n\n/**\n * Why a scaffold cannot build a plan of this SHAPE — the page band, a page's\n * sections, its addresses, its navigation — in a sentence a member reads;\n * `null` when the shape is one it builds. The plan step holds a plan to it\n * with a re-ask (AGL-3030), and the doors hold a confirmed plan to it again\n * through `aiSitePlanRefusal`.\n */\nexport function aiSitePlanShapeRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const { screens } = plan\n const band = aiSitePagesBand(options.freeTaste)\n if (screens.length < band.min || screens.length > band.max) {\n return `This plan builds ${screens.length} ${screens.length === 1 ? 'page' : 'pages'}, and ${\n options.freeTaste ? \"a Free workspace's site scaffold\" : 'a site scaffold'\n } builds ${band.min} to ${band.max}. Describe the site again.`\n }\n const slugs = new Set<string>()\n for (const screen of screens) {\n if (!screen.sections.length) {\n return `The page “${screen.title}” has no sections to build. Describe the site again.`\n }\n if (screen.sections.length > AI_SITE_MAX_SECTIONS) {\n return `The page “${screen.title}” has ${screen.sections.length} sections, and a scaffolded page holds ${AI_SITE_MAX_SECTIONS}. Describe the site again.`\n }\n const slug = screen.slug.trim().toLowerCase()\n if (slugs.has(slug)) {\n return `Two pages in this plan share the address ${screen.slug}. Describe the site again.`\n }\n slugs.add(slug)\n }\n if (!screens.some((screen) => screen.nav)) {\n return 'No page in this plan is in the site navigation. Describe the site again.'\n }\n return null\n}\n\n/**\n * Why a scaffold cannot build this plan, in a sentence a member reads when\n * confirming it; `null` when it can.\n */\nexport function aiSitePlanRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const shape = aiSitePlanShapeRefusal(plan, options)\n if (shape) return shape\n const prerequisites = aiSitePlanPrerequisites(plan)\n if (!prerequisites.length) return null\n const parts = prerequisites.map(\n ({ kind, name }) =>\n `the ${AI_BUILD_PLAN_CREATION_NOUNS[kind].noun} “${name}” ${AI_BUILD_PLAN_CREATION_NOUNS[kind].where}`,\n )\n const listed =\n parts.length === 1\n ? parts[0]\n : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`\n return `This site needs what the workspace does not have yet. Create ${listed}, then describe the site again.`\n}\n\n/** What the estimate counts a plan in: whether a welcome email follows, and the creations the job builds. */\nexport interface AiPlanPassOptions {\n welcomeEmail?: boolean\n /** The creation kinds the job builds itself; a scaffold's when absent. */\n creates?: readonly AiBuildPlanCreateKind[]\n}\n\n/**\n * The passes a plan implies: one per section of every screen, one more per\n * screen for its search listing and its draft, and one for each thing the\n * plan creates that the job builds. What the estimate is counted in.\n */\nexport function aiPlanPasses(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n const screens = plan.screens.reduce(\n (total, screen) => total + screen.sections.length + 1,\n 0,\n )\n const creates = options.creates ?? AI_SITE_CREATE_KINDS\n const creations = plan.create.filter((entry) => creates.includes(entry.kind)).length\n return screens + creations + (options.welcomeEmail ? 1 : 0)\n}\n\n/**\n * About what a plan costs to build, in credits. An ESTIMATE: the nominal\n * credits the machine holds per step, times the passes the plan implies.\n * What each pass actually costs is its model's tokens, recorded on the job\n * as it runs.\n */\nexport function aiPlanCreditEstimate(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n return aiPlanPasses(plan, options) * AI_SITE_PASS_CREDITS\n}\n\n/**\n * About what a job of this kind costs to build from its plan (AGL-3031): a\n * page job counts the layout, forms and components it builds before its\n * page; a template, layout, component, form or email job counts its own\n * record and every creation it builds before it (AGL-3143 §15); and a\n * scaffold counts what it builds. The figure the proposal shows beside\n * Confirm, and the hold a confirmation takes.\n */\nexport function aiJobPlanCreditEstimate(kind: string, plan: AiBuildPlan): number {\n const creates =\n kind === 'page' ? AI_PAGE_CREATE_KINDS : AI_JOB_CREATE_KINDS[kind as keyof typeof AI_JOB_CREATE_KINDS]\n return aiPlanCreditEstimate(plan, creates ? { creates } : {})\n}\n\n/**\n * About what a scaffold of this many pages costs, before a plan names its\n * sections: the same arithmetic on the nominal section count, plus the plan\n * step that proposes it. Shown where a member asks for a scaffold.\n */\nexport function aiSiteCreditEstimate(\n pages: number,\n options: { welcomeEmail?: boolean } = {},\n): number {\n const screens = Math.max(0, Math.floor(pages))\n const passes =\n 1 +\n screens * (AI_SITE_NOMINAL_SECTIONS + 1) +\n 2 +\n (options.welcomeEmail ? 1 : 0)\n return passes * AI_SITE_PASS_CREDITS\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","aiPlanUndeclaredRefs","AI_JOB_CREATE_KINDS","AI_PAGE_CREATE_KINDS","AI_SITE_PAGES","min","max","AI_SITE_FREE_PAGES","aiSitePagesBand","freeTaste","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES_NOTE","AI_SITE_PLAN_MAX_TOKENS","free","paid","AI_FREE_SITE_WORST_CASE_CREDITS","plan","layout","firstSection","laterSection","listing","aiFreeSiteSectionsWithin","creations","taste","credits","pages","Math","floor","before","layouts","AI_FREE_SITE_NOMINAL_SECTIONS","aiFreeSiteCreditEstimate","count","AI_SITE_MAX_SECTIONS","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","AI_SITE_INPUT_MAX_CHARS","AI_SITE_BATCH_MAX","AI_SITE_BATCH_MIN_HOST_LIMIT","AI_SITE_SUBMISSIONS","AI_SITE_SUBMISSION_CHOICES","id","label","blurb","AI_SITE_EMAIL_TYPE","aiSiteSubmissions","inputs","value","includes","ID_CHARS","parseAiSiteJobInputs","text","key","undefined","trimmed","trim","length","businessType","businessName","city","brand","audience","starter","rawPages","Number","NaN","isInteger","submissions","rawBatch","batchId","test","welcomeEmail","aiSitePagesRefusal","band","aiSiteWordsSaidAnything","words","Boolean","about","aiSiteWords","read","replace","slice","AI_SITE_CREATE_KINDS","aiSitePlanPrerequisites","seen","Set","prerequisites","add","kind","name","toLowerCase","has","push","entry","create","aiSitePlanShapeRefusal","options","screens","slugs","screen","sections","title","slug","some","nav","aiSitePlanRefusal","shape","parts","map","noun","where","listed","join","aiPlanPasses","reduce","total","creates","filter","aiPlanCreditEstimate","aiJobPlanCreditEstimate","aiSiteCreditEstimate","passes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n aiPlanUndeclaredRefs,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\nimport { AI_JOB_CREATE_KINDS } from './ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS } from './ai-page-job'\n\n/**\n * What a site scaffold is (AGL-2911): the inputs a `site` job is admitted\n * with, the plans a scaffold can build, and what one is estimated to cost\n * before it starts.\n *\n * A scaffold is the one job kind that builds a whole site rather than one\n * record: a plan of four to eight screens, the layout they render inside,\n * the contact form they place, a palette suggestion, and — where the email\n * step is loaded — a welcome email. It builds each of those through the job\n * kind that already owns it, so this module describes what a scaffold may\n * ask for and refuses the rest at confirmation, before a credit is spent,\n * naming what to make first.\n *\n * ── The estimate is the guard rail ───────────────────────────────────────\n *\n * A scaffold is tens of model calls, where every other kind is a few, so the\n * member reads what it will cost before they confirm the plan that spends\n * it. The figure is the machine's own nominal credits per step multiplied by\n * the passes the plan implies — an estimate, and said to be one wherever it\n * is shown.\n *\n * This module imports nothing at runtime, so the doors, the step, the batch\n * card and the plan proposal read one vocabulary.\n */\n\n/** How many pages a scaffold builds: enough to be a site, few enough to read. */\nexport const AI_SITE_PAGES = { min: 4, max: 8 } as const\n\n/**\n * How many pages a Free workspace's scaffold builds (AGL-3594): its home page\n * and the one page the brief most needs. A product decision, not a figure the\n * wall derives — the wall's proof (`jobs/ai-job-free-site.spec.ts`) shows a\n * plan and a build of this many pages fit the Free taste with room for one\n * retried section.\n */\nexport const AI_SITE_FREE_PAGES = { min: 1, max: 2 } as const\n\n/** The pages a scaffold builds for a workspace on the Free taste or on a paid plan. */\nexport function aiSitePagesBand(\n freeTaste: boolean | null | undefined,\n): { readonly min: number; readonly max: number } {\n return freeTaste ? AI_SITE_FREE_PAGES : AI_SITE_PAGES\n}\n\n/**\n * The job input a guided site start sets so its plan is confirmed for it\n * (AGL-3594); `jobs/ai-job-auto-confirm.ts` says where it is honored.\n */\nexport const AI_JOB_AUTO_CONFIRM_INPUT = 'autoConfirm'\n\n/** What a Free site start's page selector says under it (AGL-3594). */\nexport const AI_SITE_FREE_PAGES_NOTE = 'Paid plans can generate more pages.'\n\n/**\n * The plan's answer ceiling for a scaffold (AGL-3594), Free and paid. A plan\n * is an outline — each page's title, address, search listing and sections by\n * name — and the build steps write everything else, so a scaffold plans with\n * no thinking and at a ceiling sized to the outline of its largest band with\n * room to spare: about 300 tokens a page. The 13,491-token plan a Free site\n * spent 227 of its 300 credits on was its thinking, at the routing table's\n * 8,000 a call.\n */\nexport const AI_SITE_PLAN_MAX_TOKENS = { free: 2_000, paid: 4_000 } as const\n\n/** What each exchange of a Free site job comes to at its worst, in credits. */\nexport interface AiFreeSiteWorstCase {\n /** The site plan: its answer and its one re-ask, each at its ceiling. */\n plan: number\n /** The one layout a Free plan includes, built first on a site with none. */\n layout: number\n /** A page's first section pass, which writes the cached prefix. */\n firstSection: number\n /** Each later section pass. */\n laterSection: number\n /** A page's listing. */\n listing: number\n}\n\n/**\n * The Free site start's wall at its worst (AGL-3594): the site plan as\n * `jobs/ai-job-free-site.spec.ts` derives it from the plan request as it\n * stands, on the model a Free site plans with, every answer at its ceiling;\n * the build's exchanges are the Free page's (`AI_FREE_PAGE_WORST_CASE_CREDITS`),\n * which the same spec holds these to. The spec fails when a figure it derives\n * moves and this does not.\n */\nexport const AI_FREE_SITE_WORST_CASE_CREDITS: Readonly<AiFreeSiteWorstCase> = {\n plan: 35,\n layout: 64,\n firstSection: 44,\n laterSection: 20,\n listing: 3,\n}\n\n/**\n * The most sections a Free site's plan fits in the Free taste at its worst,\n * across all its pages: the plan, the layouts it builds first, each page's\n * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later\n * passes as the rest pays for; none when that is already past the wall. Every\n * page's first pass is counted as a cache write, which errs dear.\n */\nexport function aiFreeSiteSectionsWithin(\n creations: { layouts: number; pages: number },\n taste: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const pages = Math.max(1, Math.floor(creations.pages))\n const before =\n credits.plan +\n creations.layouts * credits.layout +\n pages * (credits.listing + credits.firstSection) +\n credits.firstSection\n if (before > taste) return 0\n return pages + Math.floor((taste - before) / credits.laterSection)\n}\n\n/** Sections a Free site's page is assumed to hold before its plan names them. */\nexport const AI_FREE_SITE_NOMINAL_SECTIONS = 3\n\n/**\n * About what a Free site start of this many pages costs at its worst, in\n * credits, for the dialog that asks for one: the site plan, each page's\n * listing and its sections at the nominal count, from the figures the wall is\n * proven with rather than the nominal credits a paid estimate counts.\n */\nexport function aiFreeSiteCreditEstimate(\n pages: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const count = Math.max(1, Math.floor(pages))\n return (\n credits.plan +\n count * (credits.listing + credits.firstSection) +\n count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection\n )\n}\n\n/**\n * The most sections one scaffolded page may hold. The plan model allows more\n * for a page job, which builds one page; a scaffold builds eight, and each\n * section is a pass of its own.\n */\nexport const AI_SITE_MAX_SECTIONS = 8\n\n/**\n * Sections a page is assumed to hold before a plan names them, for the\n * estimate a member reads when they ask for a scaffold. A declared\n * assumption, not a measurement: the plan's own section counts replace it\n * the moment the plan exists.\n */\nexport const AI_SITE_NOMINAL_SECTIONS = 5\n\n/**\n * The credits one pass is held against: the machine's\n * `AI_JOB_STEP_RESERVE_CREDITS`, declared here so the estimate stays a pure\n * number the console computes without the Admin SDK. `ai-site-job.spec.ts`\n * holds the two equal.\n */\nexport const AI_SITE_PASS_CREDITS = 50\n\n/** The longest a scaffold's scalar inputs may run; they are names, not copy. */\nexport const AI_SITE_INPUT_MAX_CHARS = 120\n\n/** The sites one agency batch may generate for in one request. */\nexport const AI_SITE_BATCH_MAX = 25\n\n/**\n * The sites a plan must hold before the org Sites page offers the batch at\n * all: the Agency and Advanced bands, which the plan's `hostLimit` is what\n * distinguishes.\n */\nexport const AI_SITE_BATCH_MIN_HOST_LIMIT = 25\n\nexport interface AiSiteJobInputs {\n /** What the business is, in a few words: the brief's subject. */\n businessType: string\n /**\n * Who the site is for, in a few words (AGL-2918); empty when nobody said.\n * The plan step puts it in front of the model on a line of its own, as it\n * does every other scalar here, so it narrows the pages a plan proposes\n * without a prompt of its own.\n */\n audience: string\n /**\n * The starter site whose shape the person liked (AGL-2918), by its\n * `STARTER_TEMPLATES` id; empty when they picked none.\n *\n * A hint carried as the id rather than the starter, because an id is what\n * the guided start's list, this door and the plan's line all agree on\n * without any of them loading a catalog of node maps. Unrecognized ids are\n * admitted for the same reason an unrecognized business type is: it is a\n * few words in a brief, not a lookup.\n */\n starter: string\n /** How many pages the member asked for. */\n pages: number\n /** The per-site variables an agency batch varies; empty when none was given. */\n businessName: string\n city: string\n brand: string\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /**\n * Where the contact form's submissions go (AGL-2918); `null` where nobody\n * said, which is every job created before the question existed and every\n * door that does not ask it.\n *\n * The one setting a new site owner actually needs made, and the one thing\n * about a contact form a model cannot know: whether a submission is a note\n * to read or a lead to chase is a decision about how the business runs. So\n * an answer BINDS the form step rather than hinting to it, and `null`\n * leaves the model's own proposal standing, exactly as it stood before.\n */\n submissions: AiSiteSubmissions | null\n /** The batch this job was created under (AGL-2911); `null` for a single site. */\n batchId: string | null\n}\n\n/**\n * Where a site's contact form sends what a visitor writes.\n *\n * Two, not three. The form step's own vocabulary has a third — a mailing\n * list — which it can only answer with a note, because the stored routing has\n * no place for a list; offering it as a question would be asking somebody to\n * pick an outcome the platform then explains it cannot store.\n */\nexport const AI_SITE_SUBMISSIONS = ['inbox', 'lead'] as const\n\nexport type AiSiteSubmissions = (typeof AI_SITE_SUBMISSIONS)[number]\n\n/**\n * What each answer to \"where do submissions go\" is called, and what it means.\n *\n * Here rather than beside the guided start's own questions, because it is not\n * only the guided start that asks: the agency batch asks the same thing of a\n * whole run (AGL-2918), and two surfaces wording one decision two ways is two\n * decisions. `ai-site-job.spec.ts` holds the list to {@link AI_SITE_SUBMISSIONS}.\n */\nexport const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{\n id: AiSiteSubmissions\n label: string\n blurb: string\n}> = [\n {\n id: 'inbox',\n label: 'The Inbox',\n blurb: 'Every message is one to read and reply to.',\n },\n {\n id: 'lead',\n label: 'The Inbox, and CRM as a lead',\n blurb: 'Every message with an email address is also somebody to follow up.',\n },\n]\n\n/**\n * Which kind of email a scaffold's email unit is (AGL-2918), carried on the\n * derived job as `inputs.emailType`. The email step names the kind on the one\n * line it puts above the brief, and a scaffold was naming none.\n *\n * A key of the email step's own `AI_EMAIL_TYPES`, held here because neither\n * step may import the other: a scaffold delegates by job kind, and a\n * deployment that scaffolds is not necessarily one that has loaded the email\n * step. `ai-job-email-step.spec.ts` holds this key to that catalog.\n *\n * `reply` and not `welcome`. The five kinds a member picks are campaigns\n * somebody sends to an audience, and this is an answer to one person who\n * wrote in through the site; the nearest, `welcome`, is a label about signing\n * up, and a kind that misdescribes the email is worse than an absent one\n * because it is the first thing the model is told.\n */\nexport const AI_SITE_EMAIL_TYPE = 'reply'\n\n/**\n * What a site job's plan and every unit it builds are told about the\n * business's name (AGL-3596): that it is the name, used as written. Said on\n * the job's own turn, never in a cached system block, because it is one\n * site's. A model handed only what the business does named it itself —\n * \"Hillside Dog Grooming\" was built as \"Austin Paws Grooming\".\n */\nexport function aiSiteNameSentence(name: string): string {\n return `The business is named “${name}”: the site names it exactly that way, in the header, the footer, the copy and the search listing, and never by any other name.`\n}\n\n/** Where a job's inputs say submissions go, or `null` where they do not say. */\nexport function aiSiteSubmissions(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteSubmissions | null {\n const value = inputs?.['submissions']\n return (AI_SITE_SUBMISSIONS as readonly unknown[]).includes(value)\n ? (value as AiSiteSubmissions)\n : null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/\n\n/** A scaffold's inputs, or the sentence the door refuses them with. */\nexport function parseAiSiteJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteJobInputs | string {\n const text = (key: string): string | null => {\n const value = inputs?.[key]\n if (value === undefined || value === null || value === '') return ''\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > AI_SITE_INPUT_MAX_CHARS ? null : trimmed\n }\n const businessType = text('businessType')\n if (businessType === null) {\n return `businessType must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n if (!businessType) return 'Say what kind of business the site is for'\n const businessName = text('businessName')\n const city = text('city')\n const brand = text('brand')\n const audience = text('audience')\n const starter = text('starter')\n for (const [key, value] of [\n ['businessName', businessName],\n ['city', city],\n ['brand', brand],\n ['audience', audience],\n ['starter', starter],\n ] as const) {\n if (value === null)\n return `${key} must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n const rawPages = inputs?.['pages']\n const pages =\n typeof rawPages === 'number' ? rawPages : Number(rawPages ?? NaN)\n // Read here across both bands; the door holds a workspace to its own\n // (`aiSitePagesRefusal`), since only it knows the workspace's plan.\n if (\n !Number.isInteger(pages) ||\n pages < AI_SITE_FREE_PAGES.min ||\n pages > AI_SITE_PAGES.max\n ) {\n return `pages must be a whole number from ${AI_SITE_FREE_PAGES.min} to ${AI_SITE_PAGES.max}`\n }\n // Where submissions go is admitted only as one of the two the form step\n // can actually bind; anything else is nobody having said, and the model\n // proposes as it always did rather than the door refusing the whole job.\n const submissions = aiSiteSubmissions(inputs)\n const rawBatch = inputs?.['batchId']\n const batchId =\n rawBatch === undefined || rawBatch === null || rawBatch === ''\n ? null\n : typeof rawBatch === 'string' && ID_CHARS.test(rawBatch)\n ? rawBatch\n : undefined\n if (batchId === undefined) return 'batchId is not a batch id'\n return {\n businessType,\n audience: audience as string,\n starter: starter as string,\n pages,\n businessName: businessName as string,\n city: city as string,\n brand: brand as string,\n welcomeEmail: inputs?.['welcomeEmail'] !== false,\n submissions,\n batchId,\n }\n}\n\n/**\n * Why a workspace cannot ask for this many pages (AGL-3594), in a sentence a\n * member reads; `null` when it can. A Free workspace builds one or two, and a\n * paid one four to eight, as it always has. Refused rather than clamped: the\n * guided start offers only the band, so a request outside it was not made\n * there, and a job quietly building fewer pages than it was asked for is a\n * worse answer than a sentence saying why.\n */\nexport function aiSitePagesRefusal(pages: number, freeTaste: boolean | null | undefined): string | null {\n const band = aiSitePagesBand(freeTaste)\n if (pages >= band.min && pages <= band.max) return null\n return freeTaste\n ? `A Free workspace's AI site start builds ${AI_SITE_FREE_PAGES.min} or ${AI_SITE_FREE_PAGES.max} pages. ${AI_SITE_FREE_PAGES_NOTE}`\n : `A site is planned with ${band.min} to ${band.max} pages.`\n}\n\n/**\n * What the site itself is, as a step that is NOT the scaffold reads it off a\n * job's inputs (AGL-2918).\n *\n * A scaffold delegates unit by unit under a job derived from its own — a page\n * job, a form job — and the derived job carries the scaffold's inputs. So the\n * two answers that describe the site rather than one record of it are\n * readable by every delegated step, and a step that wants them does not have\n * to know it was delegated to.\n *\n * Lenient where {@link parseAiSiteJobInputs} is strict, because it reads the\n * inputs of jobs that are not scaffolds: a page job started from the Screens\n * page has no `businessType` and no `pages`, and that is not an error here —\n * it is a site nobody described, and the answer is two empty strings.\n */\nexport interface AiSiteWords {\n /** What kind of site it is; empty when the job's inputs do not say. */\n about: string\n /** Who it is for; empty when the job's inputs do not say. */\n audience: string\n}\n\n/** Whether either half of {@link AiSiteWords} says anything. */\nexport function aiSiteWordsSaidAnything(words: AiSiteWords): boolean {\n return Boolean(words.about || words.audience)\n}\n\n/** What a job's inputs say the site is, read defensively and trimmed to the input ceiling. */\nexport function aiSiteWords(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteWords {\n const read = (key: string): string => {\n const value = inputs?.[key]\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n }\n return { about: read('businessType'), audience: read('audience') }\n}\n\n/**\n * What a scaffold builds for itself: the layout its pages render inside, the\n * form they place, and the palette suggestion a member applies in the Theme\n * section. Anything else a plan asks to create is a job of its own.\n */\nexport const AI_SITE_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'layout',\n 'form',\n 'theme-change',\n]\n\n/** The creations a scaffold cannot build, in the order the plan lists them, each named once. */\nexport function aiSitePlanPrerequisites(\n plan: AiBuildPlan,\n): Array<{ kind: AiBuildPlanCreateKind; name: string }> {\n const seen = new Set<string>()\n const prerequisites: Array<{ kind: AiBuildPlanCreateKind; name: string }> = []\n const add = (kind: AiBuildPlanCreateKind, name: string) => {\n const key = `${kind}:${name.toLowerCase()}`\n if (seen.has(key)) return\n seen.add(key)\n prerequisites.push({ kind, name })\n }\n for (const entry of plan.create) {\n if (!AI_SITE_CREATE_KINDS.includes(entry.kind)) add(entry.kind, entry.name)\n }\n // A reference the create list does not carry is refused by the plan rules\n // before a plan is kept: rule 2 for a screen's layout, rule 7 for anything\n // else (`plan-creation-undeclared`, AGL-3040). A plan confirmed before that\n // rule existed can still carry one. It is named as a component: the\n // reference carries no kind, and the refusal must say where one is made.\n for (const { name } of aiPlanUndeclaredRefs(plan)) add('component', name)\n return prerequisites\n}\n\n/**\n * Why a scaffold cannot build a plan of this SHAPE — the page band, a page's\n * sections, its addresses, its navigation — in a sentence a member reads;\n * `null` when the shape is one it builds. The plan step holds a plan to it\n * with a re-ask (AGL-3030), and the doors hold a confirmed plan to it again\n * through `aiSitePlanRefusal`.\n */\nexport function aiSitePlanShapeRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const { screens } = plan\n const band = aiSitePagesBand(options.freeTaste)\n if (screens.length < band.min || screens.length > band.max) {\n return `This plan builds ${screens.length} ${screens.length === 1 ? 'page' : 'pages'}, and ${\n options.freeTaste ? \"a Free workspace's site scaffold\" : 'a site scaffold'\n } builds ${band.min} to ${band.max}. Describe the site again.`\n }\n const slugs = new Set<string>()\n for (const screen of screens) {\n if (!screen.sections.length) {\n return `The page “${screen.title}” has no sections to build. Describe the site again.`\n }\n if (screen.sections.length > AI_SITE_MAX_SECTIONS) {\n return `The page “${screen.title}” has ${screen.sections.length} sections, and a scaffolded page holds ${AI_SITE_MAX_SECTIONS}. Describe the site again.`\n }\n const slug = screen.slug.trim().toLowerCase()\n if (slugs.has(slug)) {\n return `Two pages in this plan share the address ${screen.slug}. Describe the site again.`\n }\n slugs.add(slug)\n }\n if (!screens.some((screen) => screen.nav)) {\n return 'No page in this plan is in the site navigation. Describe the site again.'\n }\n return null\n}\n\n/**\n * Why a scaffold cannot build this plan, in a sentence a member reads when\n * confirming it; `null` when it can.\n */\nexport function aiSitePlanRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const shape = aiSitePlanShapeRefusal(plan, options)\n if (shape) return shape\n const prerequisites = aiSitePlanPrerequisites(plan)\n if (!prerequisites.length) return null\n const parts = prerequisites.map(\n ({ kind, name }) =>\n `the ${AI_BUILD_PLAN_CREATION_NOUNS[kind].noun} “${name}” ${AI_BUILD_PLAN_CREATION_NOUNS[kind].where}`,\n )\n const listed =\n parts.length === 1\n ? parts[0]\n : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`\n return `This site needs what the workspace does not have yet. Create ${listed}, then describe the site again.`\n}\n\n/** What the estimate counts a plan in: whether a welcome email follows, and the creations the job builds. */\nexport interface AiPlanPassOptions {\n welcomeEmail?: boolean\n /** The creation kinds the job builds itself; a scaffold's when absent. */\n creates?: readonly AiBuildPlanCreateKind[]\n}\n\n/**\n * The passes a plan implies: one per section of every screen, one more per\n * screen for its search listing and its draft, and one for each thing the\n * plan creates that the job builds. What the estimate is counted in.\n */\nexport function aiPlanPasses(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n const screens = plan.screens.reduce(\n (total, screen) => total + screen.sections.length + 1,\n 0,\n )\n const creates = options.creates ?? AI_SITE_CREATE_KINDS\n const creations = plan.create.filter((entry) => creates.includes(entry.kind)).length\n return screens + creations + (options.welcomeEmail ? 1 : 0)\n}\n\n/**\n * About what a plan costs to build, in credits. An ESTIMATE: the nominal\n * credits the machine holds per step, times the passes the plan implies.\n * What each pass actually costs is its model's tokens, recorded on the job\n * as it runs.\n */\nexport function aiPlanCreditEstimate(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n return aiPlanPasses(plan, options) * AI_SITE_PASS_CREDITS\n}\n\n/**\n * About what a job of this kind costs to build from its plan (AGL-3031): a\n * page job counts the layout, forms and components it builds before its\n * page; a template, layout, component, form or email job counts its own\n * record and every creation it builds before it (AGL-3143 §15); and a\n * scaffold counts what it builds. The figure the proposal shows beside\n * Confirm, and the hold a confirmation takes.\n */\nexport function aiJobPlanCreditEstimate(kind: string, plan: AiBuildPlan): number {\n const creates =\n kind === 'page' ? AI_PAGE_CREATE_KINDS : AI_JOB_CREATE_KINDS[kind as keyof typeof AI_JOB_CREATE_KINDS]\n return aiPlanCreditEstimate(plan, creates ? { creates } : {})\n}\n\n/**\n * About what a scaffold of this many pages costs, before a plan names its\n * sections: the same arithmetic on the nominal section count, plus the plan\n * step that proposes it. Shown where a member asks for a scaffold.\n */\nexport function aiSiteCreditEstimate(\n pages: number,\n options: { welcomeEmail?: boolean } = {},\n): number {\n const screens = Math.max(0, Math.floor(pages))\n const passes =\n 1 +\n screens * (AI_SITE_NOMINAL_SECTIONS + 1) +\n 2 +\n (options.welcomeEmail ? 1 : 0)\n return passes * AI_SITE_PASS_CREDITS\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","aiPlanUndeclaredRefs","AI_JOB_CREATE_KINDS","AI_PAGE_CREATE_KINDS","AI_SITE_PAGES","min","max","AI_SITE_FREE_PAGES","aiSitePagesBand","freeTaste","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES_NOTE","AI_SITE_PLAN_MAX_TOKENS","free","paid","AI_FREE_SITE_WORST_CASE_CREDITS","plan","layout","firstSection","laterSection","listing","aiFreeSiteSectionsWithin","creations","taste","credits","pages","Math","floor","before","layouts","AI_FREE_SITE_NOMINAL_SECTIONS","aiFreeSiteCreditEstimate","count","AI_SITE_MAX_SECTIONS","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","AI_SITE_INPUT_MAX_CHARS","AI_SITE_BATCH_MAX","AI_SITE_BATCH_MIN_HOST_LIMIT","AI_SITE_SUBMISSIONS","AI_SITE_SUBMISSION_CHOICES","id","label","blurb","AI_SITE_EMAIL_TYPE","aiSiteNameSentence","name","aiSiteSubmissions","inputs","value","includes","ID_CHARS","parseAiSiteJobInputs","text","key","undefined","trimmed","trim","length","businessType","businessName","city","brand","audience","starter","rawPages","Number","NaN","isInteger","submissions","rawBatch","batchId","test","welcomeEmail","aiSitePagesRefusal","band","aiSiteWordsSaidAnything","words","Boolean","about","aiSiteWords","read","replace","slice","AI_SITE_CREATE_KINDS","aiSitePlanPrerequisites","seen","Set","prerequisites","add","kind","toLowerCase","has","push","entry","create","aiSitePlanShapeRefusal","options","screens","slugs","screen","sections","title","slug","some","nav","aiSitePlanRefusal","shape","parts","map","noun","where","listed","join","aiPlanPasses","reduce","total","creates","filter","aiPlanCreditEstimate","aiJobPlanCreditEstimate","aiSiteCreditEstimate","passes"],"mappings":"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"}
|
|
@@ -63,7 +63,7 @@ export type AiSiteSeoFormField = (typeof AI_SITE_SEO_FORM_FIELDS)[keyof typeof A
|
|
|
63
63
|
* What each value is held to: the lengths the SEO editors guide a listing to,
|
|
64
64
|
* which are the same two the host settings form counts down from.
|
|
65
65
|
*/
|
|
66
|
-
export declare const AI_SITE_SEO_LIMITS: Readonly<Record<"
|
|
66
|
+
export declare const AI_SITE_SEO_LIMITS: Readonly<Record<"description" | "title", number>>;
|
|
67
67
|
/** The kind an `seo` output carries when it is a guided start's site listing. */
|
|
68
68
|
export declare const AI_SITE_SEO_PROPOSAL_KIND = "site-listing";
|
|
69
69
|
/** The id the scaffold reports its one site listing under. */
|