@aglyn/plugins-ai 1.0.0-beta.219 → 1.0.0-beta.220

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/package.json +18 -18
  2. package/src/lib/billing/ai-month-meter.d.ts +51 -0
  3. package/src/lib/billing/ai-month-meter.js +89 -0
  4. package/src/lib/billing/ai-month-meter.js.map +1 -0
  5. package/src/lib/billing/ai-overage-close.d.ts +11 -33
  6. package/src/lib/billing/ai-overage-close.js +4 -3
  7. package/src/lib/billing/ai-overage-close.js.map +1 -1
  8. package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
  9. package/src/lib/billing/ai-overage-cutover.js +1 -28
  10. package/src/lib/billing/ai-overage-cutover.js.map +1 -1
  11. package/src/lib/billing/ai-overage-ledger.js +1 -1
  12. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  13. package/src/lib/billing/ai-overage-standing.js +1 -1
  14. package/src/lib/billing/ai-overage-standing.js.map +1 -1
  15. package/src/lib/components/ai-allotment-editor.component.js +1 -1
  16. package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
  17. package/src/lib/components/ai-assist-provider.component.js +3 -2
  18. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  19. package/src/lib/components/ai-automation-zones.d.ts +63 -0
  20. package/src/lib/components/ai-automation-zones.js +31 -0
  21. package/src/lib/components/ai-automation-zones.js.map +1 -0
  22. package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
  23. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  24. package/src/lib/components/ai-describe-button.component.d.ts +6 -3
  25. package/src/lib/components/ai-describe-button.component.js +4 -1
  26. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  27. package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
  28. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +1 -1
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-assist-overage-card.component.js +1 -1
  32. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  33. package/src/lib/constants.d.ts +5 -0
  34. package/src/lib/constants.js +4 -0
  35. package/src/lib/constants.js.map +1 -1
  36. package/src/lib/declarations.js +2 -0
  37. package/src/lib/declarations.js.map +1 -1
  38. package/src/lib/declarations.server.js +24 -10
  39. package/src/lib/declarations.server.js.map +1 -1
  40. package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
  41. package/src/lib/insights/ai-dataset-catalog.js +57 -0
  42. package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
  43. package/src/lib/insights/ai-figure-readers.d.ts +6 -39
  44. package/src/lib/insights/ai-figure-readers.js +2 -391
  45. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  46. package/src/lib/insights/ai-insight-digest.d.ts +10 -1
  47. package/src/lib/insights/ai-insight-digest.js +16 -4
  48. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  49. package/src/lib/insights/ai-insight-readers.d.ts +1 -1
  50. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  51. package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
  52. package/src/lib/jobs/ai-job-component-step.js +23 -6
  53. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-draft-ids.js +11 -0
  55. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  56. package/src/lib/jobs/ai-job-email-step.d.ts +5 -1
  57. package/src/lib/jobs/ai-job-email-step.js +10 -1
  58. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  59. package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
  60. package/src/lib/jobs/ai-job-form-step.js +60 -8
  61. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-generation.d.ts +21 -0
  63. package/src/lib/jobs/ai-job-generation.js +53 -1
  64. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  65. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  66. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
  68. package/src/lib/jobs/ai-job-layout-step.js +45 -12
  69. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
  71. package/src/lib/jobs/ai-job-plan-conformance.js +7 -1
  72. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  73. package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
  74. package/src/lib/jobs/ai-job-plan-creations.js +199 -0
  75. package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
  76. package/src/lib/jobs/ai-job-plan-step.d.ts +8 -2
  77. package/src/lib/jobs/ai-job-plan-step.js +20 -6
  78. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  79. package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
  80. package/src/lib/jobs/ai-job-site-step.js +6 -6
  81. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
  83. package/src/lib/jobs/ai-job-template-step.js +72 -15
  84. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  85. package/src/lib/jobs/ai-jobs.js +1 -1
  86. package/src/lib/jobs/ai-jobs.js.map +1 -1
  87. package/src/lib/jobs/ai-workflow-records.d.ts +5 -4
  88. package/src/lib/jobs/ai-workflow-records.js +26 -22
  89. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  90. package/src/lib/model/ai-allotments.d.ts +16 -3
  91. package/src/lib/model/ai-allotments.js +34 -11
  92. package/src/lib/model/ai-allotments.js.map +1 -1
  93. package/src/lib/model/ai-job-creations.d.ts +55 -0
  94. package/src/lib/model/ai-job-creations.js +85 -0
  95. package/src/lib/model/ai-job-creations.js.map +1 -0
  96. package/src/lib/model/ai-page-job.d.ts +3 -2
  97. package/src/lib/model/ai-page-job.js +4 -3
  98. package/src/lib/model/ai-page-job.js.map +1 -1
  99. package/src/lib/model/ai-site-job.d.ts +4 -2
  100. package/src/lib/model/ai-site-job.js +8 -4
  101. package/src/lib/model/ai-site-job.js.map +1 -1
  102. package/src/lib/model/ai-template-subjects.d.ts +10 -0
  103. package/src/lib/model/ai-template-subjects.js +14 -0
  104. package/src/lib/model/ai-template-subjects.js.map +1 -1
  105. package/src/lib/model/ai-tokens.js +1 -1
  106. package/src/lib/model/ai-tokens.js.map +1 -1
  107. package/src/lib/model/ai-usage-by-user.js +1 -1
  108. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  109. package/src/lib/providers/catalog.js +1 -1
  110. package/src/lib/providers/catalog.js.map +1 -1
  111. package/src/lib/providers/model-choice.js +1 -1
  112. package/src/lib/providers/model-choice.js.map +1 -1
  113. package/src/lib/runtime/ai-device-audit.d.ts +1 -1
  114. package/src/lib/runtime/ai-device-audit.js +1 -1
  115. package/src/lib/runtime/ai-device-audit.js.map +1 -1
  116. package/src/lib/runtime/ai-eval-live.js +1 -1
  117. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  118. package/src/lib/runtime/ai-eval.d.ts +1 -1
  119. package/src/lib/runtime/ai-eval.js.map +1 -1
  120. package/src/lib/runtime/ai-gate.js +1 -1
  121. package/src/lib/runtime/ai-gate.js.map +1 -1
  122. package/src/lib/runtime/ai-palette.d.ts +1 -1
  123. package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
  124. package/src/lib/runtime/ai-palette.generated.js +1 -1
  125. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  126. package/src/lib/runtime/ai-palette.js.map +1 -1
  127. package/src/lib/runtime/site-inventory.js +28 -22
  128. package/src/lib/runtime/site-inventory.js.map +1 -1
  129. package/src/lib/server/ai-admin-org.js +1 -1
  130. package/src/lib/server/ai-admin-org.js.map +1 -1
  131. package/src/lib/server/ai-admin-orgs-spend.js +1 -1
  132. package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
  133. package/src/lib/server/ai-admin-signals.js +1 -1
  134. package/src/lib/server/ai-admin-signals.js.map +1 -1
  135. package/src/lib/server/ai-allotments.js +1 -1
  136. package/src/lib/server/ai-allotments.js.map +1 -1
  137. package/src/lib/server/ai-assist.js +2 -2
  138. package/src/lib/server/ai-assist.js.map +1 -1
  139. package/src/lib/server/assist-chat.d.ts +1 -1
  140. package/src/lib/server/assist-chat.js +2 -2
  141. package/src/lib/server/assist-chat.js.map +1 -1
  142. package/src/lib/server/billing-credits.d.ts +5 -0
  143. package/src/lib/server/billing-credits.js +8 -2
  144. package/src/lib/server/billing-credits.js.map +1 -1
  145. package/src/lib/server/billing-overage.js +2 -2
  146. package/src/lib/server/billing-overage.js.map +1 -1
  147. package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
  148. package/src/lib/testing/stand-in-dataset-index.js +80 -0
  149. package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
  150. package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
  151. package/src/lib/usage/ai-allotment-alerts.js +31 -14
  152. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  153. package/src/lib/usage/ai-quota-checks.d.ts +37 -0
  154. package/src/lib/usage/ai-quota-checks.js +69 -0
  155. package/src/lib/usage/ai-quota-checks.js.map +1 -0
  156. package/src/lib/usage/ai-usage-alerts.js +22 -10
  157. package/src/lib/usage/ai-usage-alerts.js.map +1 -1
  158. package/src/lib/usage/ai-usage-by-user.js +1 -1
  159. package/src/lib/usage/ai-usage-by-user.js.map +1 -1
  160. package/src/lib/usage/assist-ceiling.d.ts +2 -2
  161. package/src/lib/usage/assist-ceiling.js +4 -4
  162. package/src/lib/usage/assist-ceiling.js.map +1 -1
  163. package/src/lib/usage/assist-credits.d.ts +494 -0
  164. package/src/lib/usage/assist-credits.js +524 -0
  165. package/src/lib/usage/assist-credits.js.map +1 -0
  166. package/src/lib/usage/assist-free-taste.d.ts +1 -1
  167. package/src/lib/usage/assist-free-taste.js +1 -1
  168. package/src/lib/usage/assist-free-taste.js.map +1 -1
  169. package/src/lib/usage/assist-signal-mining.js +1 -1
  170. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  171. package/src/lib/usage/assist-spend-guards.d.ts +153 -0
  172. package/src/lib/usage/assist-spend-guards.js +182 -0
  173. package/src/lib/usage/assist-spend-guards.js.map +1 -0
  174. package/src/lib/usage/assist-usage.d.ts +1 -1
  175. package/src/lib/usage/assist-usage.js +1 -1
  176. package/src/lib/usage/assist-usage.js.map +1 -1
  177. package/src/lib/usage/staff-org-ai.js +1 -1
  178. package/src/lib/usage/staff-org-ai.js.map +1 -1
  179. package/src/lib/usage-axes.d.ts +10 -0
  180. package/src/lib/usage-axes.js +40 -2
  181. package/src/lib/usage-axes.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_BUILD_PLAN_TOOL,\n aiBuildPlanToolFor,\n isAiPlanNewRef,\n type AiBuildPlan,\n} from '../model/ai-build-plan'\nimport type { AiJob, AiJobKind, AiJobPlan, AiJobStatus } from '../model/ai-jobs.types'\nimport {\n AI_TEMPLATE_SUBJECT_DEFINITIONS,\n type AiTemplateSubject,\n} from '../model/ai-template-subjects'\nimport { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from '../model/ai-page-job'\nimport {\n aiPlanCapabilitiesForJob,\n aiPlanCapabilityLines,\n type AiPlanCapabilities,\n type AiPlanJobScope,\n} from '../model/ai-plan-capabilities'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_SITE_CREATE_KINDS, aiSitePlanShapeRefusal } from '../model/ai-site-job'\nimport { aiDoctrineSystemBlocks, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n aiPlanEmbedBriefViolations,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport type { AiTool } from '../providers/contract'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { aiJobAdmissionRefusal } from './ai-job-admission'\nimport { readAiPlanCapabilities } from './ai-job-drafts'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiDoctrineReview, aiUnspentOutcome } from './ai-job-generation'\nimport { aiPlanWithDraftIds } from './ai-job-draft-ids'\nimport { AI_JOBS_COLLECTION, registerAiJobPlanStep } from './ai-jobs'\n\n/**\n * The plan step (AGL-2935): the first step of every job that builds site\n * structure. Before a node is generated, the model answers with a typed plan\n * — what the site already has that the job reuses, what it creates and why,\n * and the screens it builds from them — held to the doctrine's plan rules\n * against the site's inventory and re-asked once when it breaks one.\n *\n * A plan that passes is kept on the job, and the job stops for review: the\n * member reads what it will build before a credit is spent building it, and\n * confirms through the resume door. A plan that still breaks a rule on its\n * re-ask stops the job the same way, with the rules named. Either way the\n * step writes nothing itself; the machine records the plan, the spend and\n * the stop, as it records every step.\n *\n * ── Told what it may create, refused what it cannot build (AGL-3030) ─────\n *\n * Before it asks, the step reads what the job may create on its site: the\n * workspace's plan, the site's counts against its limits, and — for a kind\n * that builds only some plans — what the job builds itself. The user turn\n * states it, the plan rules refuse a creation outside it with the one re-ask,\n * and where the workspace keeps no reusable components they accept the page\n * built inline. A plan the confirm door would still refuse — the kind's own\n * admission, handed the plan, as the resume door hands it — is refused HERE,\n * before a member is shown a Confirm: the job fails with the door's sentence,\n * its plan is not kept, and it holds nothing.\n */\n\n/** The plan step's own instructions, cached after the doctrine. */\nexport const AI_JOB_PLAN_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text:\n 'You plan what a website builder job will build, before anything is built. You are given the kind of job and the brief from the person who owns the site. ' +\n 'Answer with submit_build_plan: what the site inventory already has that the job reuses, what it must create and why nothing listed will do, and each page it builds (the screens list) with its layout, template, slug, search title, search description and sections from top to bottom. ' +\n 'Refer to inventory records by id and to what the plan creates as new:<name>. Plan only what the brief asks for: a component, layout, template, form or email job plans no pages unless the brief asks for them, and a page job plans exactly one page.',\n },\n]\n\n/**\n * The plan step's time (AGL-3026, AGL-3036): its two inventory-lookup rounds,\n * its answer and its re-ask at the routing table's ceiling for `job.plan`, on\n * the tier that step kind is served from, with the step's reads and writes,\n * at the rates `ai-job-budget.ts` assumes — fitted to what a beat can give a\n * step, so the served tier asks a little under the routing ceiling.\n */\nexport const AI_JOB_PLAN_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.plan'],\n maxTokens: AI_ROUTING_TABLE['job.plan'].maxTokens,\n})\n\n/**\n * The least time a plan needs before it starts. Registered with the step, so\n * an inline door never starts a plan. A plan thinks before it answers and\n * routinely runs past an inline door's budget, and a provider call that\n * budget cuts off is still generated and billed upstream while the meter\n * records nothing. The beat starts a plan only with this much of its own\n * budget left, and a spec holds it inside that budget.\n */\nexport const AI_JOB_PLAN_STEP_MINIMUM_MS = AI_JOB_PLAN_STEP_BUDGET.minimumMs\n\n/**\n * The plan's answer ceiling on the model a job runs: the most whose worst\n * case fits the least time the step registered, and never more than the\n * routing table's. A model the catalog does not know is planned at the\n * slowest.\n */\nexport function aiJobPlanMaxTokens(model: string): number {\n return AI_JOB_PLAN_STEP_BUDGET.maxTokens(model)\n}\n\n/** What the member reads while a plan waits for them. */\nexport const AI_JOB_PLAN_REVIEW_COPY =\n 'The plan is ready. Review what the job will reuse and create, then confirm it to build.'\n\n/**\n * The job as the plan step's user turn: its kind, its brief, its scalar\n * inputs and, where the job read them, what it may create on its site\n * (AGL-3030). Per workspace and per site, which is why they ride here and\n * never in a system block the platform's cache entry is keyed on.\n */\nexport function aiJobPlanPrompt(\n job: Pick<AiJob, 'kind' | 'brief' | 'inputs'>,\n capabilities: AiPlanCapabilities | null = null,\n): string {\n const lines = [`Job kind: ${job.kind}`, `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`]\n for (const [key, value] of Object.entries(job.inputs ?? {})) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${key}: ${String(value)}`)\n }\n }\n if (capabilities) lines.push(...aiPlanCapabilityLines(capabilities))\n lines.push(...aiPlanTemplateTokenLines(job))\n return lines.join('\\n')\n}\n\n/**\n * What a TEMPLATE job's plan is told its creation's `fields` are (AGL-3143\n * §11): the subject's binding tokens, listed so the planner promises only\n * what the page can fill and the build can be held to it\n * (`aiPlanTemplateTokenViolations`).\n *\n * ⛔ It rides the job's own turn and NOT the `fields` description in the plan\n * tool, which every kind's request caches. Written there it read better and\n * cost 40 tokens of the shared prefix — 2,980 to 3,020 — which is one credit\n * of the Free page's 300-credit wall, taking the room it keeps for a re-asked\n * section from 45 credits to 44 and breaking `ai-job-free-page.spec.ts`. A\n * page job must not pay for a sentence about templates.\n */\nexport function aiPlanTemplateTokenLines(job: Pick<AiJob, 'kind' | 'inputs'>): string[] {\n if (job.kind !== 'template') return []\n const subject = (job.inputs ?? {})['subject']\n const definition =\n typeof subject === 'string'\n ? AI_TEMPLATE_SUBJECT_DEFINITIONS[subject as AiTemplateSubject]\n : undefined\n if (!definition) return []\n return [\n `The template's fields are the binding tokens its page shows, each one of: ${definition.tokens\n .map((entry) => entry.token)\n .join(', ')}. Promise only what the page fills.`,\n ]\n}\n\n/** A kind that builds only some plans: the creations it makes, and the shapes it refuses. */\nexport interface AiJobPlanScope extends AiPlanJobScope {\n /** Why a job of the kind cannot build a plan of this shape; `null` when it can. Pure. */\n shapeRefusal: (plan: AiBuildPlan) => string | null\n}\n\n/**\n * The kinds whose confirm door builds only some plans (AGL-3030). A page job\n * builds one screen and a scaffold four to eight; each builds only the\n * creations its list names. Every other planned kind builds its own one\n * output, and its plan is held to the workspace alone.\n */\nexport const AI_JOB_PLAN_SCOPES: Readonly<Partial<Record<AiJobKind, AiJobPlanScope>>> = {\n page: { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS, shapeRefusal: aiPagePlanShapeRefusal },\n site: { noun: 'a site scaffold', creates: AI_SITE_CREATE_KINDS, shapeRefusal: aiSitePlanShapeRefusal },\n}\n\n/**\n * What the plan step holds beyond the doctrine's plan rules, as the\n * violations the plan's one re-ask names: the players the plan lists against\n * the brief it answers (AGL-3433), which only this step reads beside the plan,\n * and a kind's shape refusal.\n */\nfunction planViolations(\n scope: AiJobPlanScope | null,\n brief: string,\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n return (plan) => {\n const message = scope?.shapeRefusal(plan) ?? null\n return [\n ...aiPlanEmbedBriefViolations(plan, brief),\n ...(message ? [{ rule: null, code: 'plan-job-shape', message }] : []),\n ]\n }\n}\n\n/**\n * The inventory names of every id the plan references, so the proposal reads\n * \"the Services layout\" rather than an id — kept on the plan because the\n * inventory is not read again when the member opens it.\n */\nexport function aiPlanLabels(\n plan: AiBuildPlan,\n inventory: AiSiteInventory | null,\n): Record<string, string> {\n const names = new Map<string, string>()\n for (const rows of [\n inventory?.components,\n inventory?.layouts,\n inventory?.templates,\n inventory?.forms,\n inventory?.datasets,\n inventory?.collections,\n inventory?.screens,\n ]) {\n for (const row of rows ?? []) names.set(row.id, row.name)\n }\n const refs = [\n ...plan.reuse.map((entry) => entry.id),\n ...plan.create.map((entry) => entry.duplicateOf),\n ...plan.screens.flatMap((screen) => [\n screen.layout,\n screen.template,\n screen.duplicateOf,\n ...screen.sections.flatMap((section) => section.uses),\n ]),\n ]\n const labels: Record<string, string> = {}\n for (const ref of refs) {\n if (!ref || isAiPlanNewRef(ref)) continue\n const name = names.get(ref)\n if (name) labels[ref] = name\n }\n return labels\n}\n\n/* ------------------------------------------------------------------------ *\n * Reusing an identical brief's plan\n * ------------------------------------------------------------------------ */\n\n/**\n * How long a plan may be reused for (AGL-2937).\n *\n * The window is what makes reuse safe rather than merely cheap. The key\n * already covers everything the request said — the brief, the inputs, the\n * model and the prompt as rendered, site inventory included — so a plan is\n * reused only when nothing the model was shown has changed. What the key\n * cannot see is the world outside the request: a page published, a component\n * renamed after the inventory was read, a member who meant something\n * different the second time. Fifteen minutes is short enough that the answer\n * is still the one the site would give, and long enough to cover what reuse\n * is actually for — the same brief run twice in a sitting, a job resubmitted\n * after a refused reservation, two members starting the same work.\n */\nexport const AI_PLAN_REUSE_WINDOW_MS = 15 * 60 * 1_000\n\n/** Jobs one lookup reads. More than one can share a key; the newest plan wins. */\nexport const AI_PLAN_REUSE_CANDIDATES = 5\n\n/** A job whose plan might be reused, as the finder reports it. */\nexport interface AiJobPlanCandidate {\n jobId: string\n status: AiJobStatus\n plan: AiJobPlan\n}\n\n/**\n * The digest a plan is reused by: the whole request, never the answer.\n *\n * It hashes what the model was asked and what it was shown — the job's kind\n * and site, the user turn (which carries the trimmed brief and every scalar\n * input), the model that would answer, every rendered system block including\n * the site inventory, and the tool's schema. Two requests that hash the same\n * would have been sent the same bytes to the same model, so the second can\n * keep the first's answer.\n *\n * The version tag is the escape hatch: anything that changes what a key\n * MEANS, rather than what it covers, bumps it and strands every old key\n * harmlessly, since a key nothing matches simply asks the model.\n */\nexport function aiJobPlanKey(input: {\n job: Pick<AiJob, 'kind' | 'hostId'>\n prompt: string\n model: string\n system: readonly AiSystemBlock[]\n /** The plan tool the request offers (AGL-3433); the plain one when absent. */\n tool?: AiTool\n}): string {\n const digest = createHash('sha256')\n for (const part of [\n 'plan.v1',\n input.job.kind,\n input.job.hostId ?? '',\n input.model,\n input.prompt,\n ...input.system.map((block) => block.text),\n JSON.stringify(input.tool ?? AI_BUILD_PLAN_TOOL),\n ]) {\n // Length-prefixed, so two different splits of the same characters cannot\n // collide by running into one another.\n digest.update(`${part.length}:${part}\\u0000`)\n }\n return digest.digest('hex')\n}\n\n/** A job that could not have produced a reusable plan, whatever its key says. */\nconst UNREUSABLE: readonly AiJobStatus[] = ['canceled', 'failed']\n\nfunction planMillis(plan: AiJobPlan): number | null {\n const at = plan.proposedAt as unknown\n if (at instanceof Date) return at.getTime()\n if (at && typeof (at as { toMillis?: unknown }).toMillis === 'function') {\n return (at as { toMillis(): number }).toMillis()\n }\n return null\n}\n\n/**\n * The newest plan worth reusing out of what the finder returned: another\n * job's, still proposed or already confirmed, on a job that was neither\n * canceled nor failed, proposed inside the window.\n *\n * A canceled or failed job is excluded even though its plan may be perfectly\n * good: those are the jobs a member walked away from, and handing their plan\n * to the next one would make a rejected answer look like a fresh one.\n */\nexport function aiReusablePlan(\n candidates: readonly AiJobPlanCandidate[],\n context: { jobId: string; now: Date },\n): AiJobPlanCandidate | null {\n const floor = context.now.getTime() - AI_PLAN_REUSE_WINDOW_MS\n let best: AiJobPlanCandidate | null = null\n let bestAt = -1\n for (const candidate of candidates) {\n if (candidate.jobId === context.jobId) continue\n if (UNREUSABLE.includes(candidate.status)) continue\n if (candidate.plan.status !== 'proposed' && candidate.plan.status !== 'confirmed') continue\n const at = planMillis(candidate.plan)\n if (at === null || at < floor || at > context.now.getTime()) continue\n if (at > bestAt) {\n best = candidate\n bestAt = at\n }\n }\n return best\n}\n\nexport type AiJobPlanFinder = (\n orgId: string,\n key: string,\n firestore?: FirebaseFirestore.Firestore,\n) => Promise<AiJobPlanCandidate[]>\n\n/**\n * The finder the step uses in production: an equality on `plan.key` inside\n * the org's own jobs.\n *\n * One equality on one field, with no ordering beside it, so Firestore's\n * automatic single-field index answers it and no composite index is deployed.\n * The window and the statuses are applied in memory instead, which is what\n * keeps it that way.\n */\nexport const findAiJobsByPlanKey: AiJobPlanFinder = async (orgId, key, firestore) => {\n if (!firestore) return []\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(AI_JOBS_COLLECTION)\n .where('plan.key', '==', key)\n .limit(AI_PLAN_REUSE_CANDIDATES)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const data = doc.data() as { status?: AiJobStatus; plan?: AiJobPlan | null }\n return data.plan\n ? [{ jobId: doc.id, status: data.status ?? 'queued', plan: data.plan }]\n : []\n })\n}\n\n/** What a job may create on its site, read for the plan step; `null` for no site. */\nexport type AiPlanCapabilitiesReader = (input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n}) => Promise<AiPlanCapabilities | null>\n\n/** The reader the step uses in production: the draft bands' own arithmetic, for the job's site. */\nexport const readAiJobPlanCapabilities: AiPlanCapabilitiesReader = async ({ job, org, firestore }) =>\n job.hostId ? readAiPlanCapabilities(firestore, { hostId: job.hostId, org }) : null\n\nexport interface AiJobPlanStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The reuse lookup; specs hand in a fake, and `null` turns reuse off. */\n findPlansByKey?: AiJobPlanFinder | null\n /** What the job may create on its site (AGL-3030); specs and the eval recorder hand in their own. */\n readCapabilities?: AiPlanCapabilitiesReader\n /** The kind's admission, asked of a plan before it is kept; the registry's otherwise. */\n admissionRefusal?: typeof aiJobAdmissionRefusal\n}\n\nexport function createAiJobPlanStep(deps: AiJobPlanStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const findPlansByKey =\n deps.findPlansByKey === undefined ? findAiJobsByPlanKey : deps.findPlansByKey\n const readCapabilities = deps.readCapabilities ?? readAiJobPlanCapabilities\n const admissionRefusal = deps.admissionRefusal ?? aiJobAdmissionRefusal\n return async ({ job, now, signal, firestore, modelFor, org: orgDocument }) => {\n const org = (orgDocument ?? null) as Partial<AglynOrgBilling> | null\n const [inventory, workspace] = await Promise.all([\n job.hostId ? readInventory(job.orgId, job.hostId, { firestore }) : Promise.resolve(null),\n readCapabilities({ job, org, firestore }),\n ])\n const scope = AI_JOB_PLAN_SCOPES[job.kind] ?? null\n const capabilities = workspace ? aiPlanCapabilitiesForJob(workspace, scope) : null\n // The model switch's answer for this job (AGL-2942): the creator's pick\n // where the plan, the org restriction and the allotment allowlists allow\n // it, and Auto held to those same lists otherwise. Without a resolver the\n // doctrine asks the routing table itself.\n const model = modelFor?.('job.plan')\n const route = AI_ROUTING_TABLE['job.plan']\n const prompt = aiJobPlanPrompt(job, capabilities)\n\n /**\n * The confirm door's answer for this plan, asked before the plan is kept\n * (AGL-3030): the kind's own admission, handed the plan as the resume\n * door hands it. A refusal fails the job with the door's sentence; a door\n * that could not answer keeps the plan, since the resume door asks again\n * before anything is built.\n */\n const refusalOnKeep = async (plan: AiJobPlan, outcome: AiJobStepOutcome): Promise<AiJobStepOutcome | null> => {\n let refusal: Awaited<ReturnType<typeof aiJobAdmissionRefusal>> = null\n try {\n refusal = await admissionRefusal(job.kind, {\n firestore,\n orgId: job.orgId,\n hostId: job.hostId ?? null,\n inputs: job.inputs ?? {},\n org,\n plan,\n uid: job.createdBy,\n })\n } catch (error) {\n console.error('ai plan admission failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n return refusal ? { ...outcome, failure: refusal.error } : null\n }\n\n // Reuse before asking (AGL-2937). The key covers the whole request, so a\n // hit is a request that would have been sent the same bytes to the same\n // model; the window covers what the key cannot see. A reused plan spends\n // nothing, so the machine's spent-nothing branch releases the reservation\n // and meters no credit, and the job still stops for the member to confirm\n // — this job's plan is proposed to this member, whatever the last one did\n // with theirs.\n const resolved = model ?? aiModelForStep('job.plan')\n // A brief that asks for a video is offered the plan's list of players (AGL-3433).\n const tool = aiBuildPlanToolFor(job.brief)\n const key = aiJobPlanKey({\n job,\n prompt,\n model: resolved,\n system: aiDoctrineSystemBlocks(inventory, { instructions: AI_JOB_PLAN_INSTRUCTIONS }),\n tool,\n })\n const reused = findPlansByKey\n ? aiReusablePlan(await findPlansByKey(job.orgId, key, firestore), {\n jobId: job.$id,\n now,\n })\n : null\n if (reused) {\n // The reused plan's draft ids name the other job's drafts; this job's are its own.\n const plan: AiJobPlan = aiPlanWithDraftIds(job.kind, {\n ...reused.plan,\n status: 'proposed',\n labels: aiPlanLabels(reused.plan, inventory),\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n reusedFrom: reused.jobId,\n })\n const unspent = aiUnspentOutcome(resolved)\n return (\n (await refusalOnKeep(plan, unspent)) ?? {\n ...unspent,\n plan,\n review: { reason: 'plan', message: AI_JOB_PLAN_REVIEW_COPY, findings: [] },\n }\n )\n }\n\n const result = await runValidatedGeneration('plan', {\n step: 'job.plan',\n ...(model ? { model } : {}),\n instructions: AI_JOB_PLAN_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: prompt }],\n tool,\n // The routing ceiling, lowered on a tier too slow to look records up,\n // answer and ask again inside the least time the step registered.\n maxTokens: aiJobPlanMaxTokens(resolved),\n ...(route.thinking ? { thinking: route.thinking } : {}),\n ...(route.effort ? { effort: route.effort } : {}),\n ...(signal ? { signal } : {}),\n capabilities,\n extend: planViolations(scope, job.brief),\n })\n const spent: AiJobStepOutcome = {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model: result.model,\n stopReason: result.stopReason,\n ...(result.effort ? { effort: result.effort } : {}),\n }\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n // Each draft the plan decides is named as the plan is kept, before anything is built (AGL-3079).\n const plan: AiJobPlan = aiPlanWithDraftIds(job.kind, {\n ...result.value,\n status: 'proposed',\n labels: aiPlanLabels(result.value, inventory),\n // A Date the Admin SDK stores as a timestamp, like every instant the machine writes.\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n })\n return (\n (await refusalOnKeep(plan, spent)) ?? {\n ...spent,\n plan,\n review: { reason: 'plan', message: AI_JOB_PLAN_REVIEW_COPY, findings: [] },\n }\n )\n }\n}\n\nexport const runAiJobPlanStep = createAiJobPlanStep()\n\n/**\n * Registers the plan step every planned kind runs first, with the least time\n * a plan needs; the plugin's console surface calls it.\n */\nexport function registerAiJobPlan(): void {\n registerAiJobPlanStep(runAiJobPlanStep, { minimumMs: AI_JOB_PLAN_STEP_MINIMUM_MS })\n}\n"],"names":["createHash","AI_BUILD_PLAN_TOOL","aiBuildPlanToolFor","isAiPlanNewRef","AI_TEMPLATE_SUBJECT_DEFINITIONS","AI_PAGE_CREATE_KINDS","aiPagePlanShapeRefusal","aiPlanCapabilitiesForJob","aiPlanCapabilityLines","AI_SITE_CREATE_KINDS","aiSitePlanShapeRefusal","aiDoctrineSystemBlocks","runValidatedGeneration","AI_STEP_TIERS","aiPlanEmbedBriefViolations","AI_ROUTING_TABLE","aiModelForStep","readSiteInventory","aiJobAdmissionRefusal","readAiPlanCapabilities","AI_JOB_BRIEF_MAX_CHARS","aiJobStepBudget","aiDoctrineReview","aiUnspentOutcome","aiPlanWithDraftIds","AI_JOBS_COLLECTION","registerAiJobPlanStep","AI_JOB_PLAN_INSTRUCTIONS","text","AI_JOB_PLAN_STEP_BUDGET","tier","maxTokens","AI_JOB_PLAN_STEP_MINIMUM_MS","minimumMs","aiJobPlanMaxTokens","model","AI_JOB_PLAN_REVIEW_COPY","aiJobPlanPrompt","job","capabilities","lines","kind","brief","slice","key","value","Object","entries","inputs","push","String","aiPlanTemplateTokenLines","join","subject","definition","undefined","tokens","map","entry","token","AI_JOB_PLAN_SCOPES","page","noun","creates","shapeRefusal","site","planViolations","scope","plan","message","rule","code","aiPlanLabels","inventory","names","Map","rows","components","layouts","templates","forms","datasets","collections","screens","row","set","id","name","refs","reuse","create","duplicateOf","flatMap","screen","layout","template","sections","section","uses","labels","ref","get","AI_PLAN_REUSE_WINDOW_MS","AI_PLAN_REUSE_CANDIDATES","aiJobPlanKey","input","digest","part","hostId","prompt","system","block","JSON","stringify","tool","update","length","UNREUSABLE","planMillis","at","proposedAt","Date","getTime","toMillis","aiReusablePlan","candidates","context","floor","now","best","bestAt","candidate","jobId","includes","status","findAiJobsByPlanKey","orgId","firestore","snapshot","collection","doc","where","limit","docs","data","readAiJobPlanCapabilities","org","createAiJobPlanStep","deps","readInventory","findPlansByKey","readCapabilities","admissionRefusal","signal","modelFor","orgDocument","workspace","Promise","all","resolve","route","refusalOnKeep","outcome","refusal","uid","createdBy","error","console","$id","failure","resolved","instructions","reused","confirmedAt","confirmedBy","reusedFrom","unspent","review","reason","findings","result","step","messages","role","content","thinking","effort","extend","spent","outputs","usage","estCostUsd","stopReason","refused","runAiJobPlanStep","registerAiJobPlan"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-plan-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_BUILD_PLAN_TOOL,\n aiBuildPlanToolFor,\n isAiPlanNewRef,\n type AiBuildPlan,\n} from '../model/ai-build-plan'\nimport type { AiJob, AiJobKind, AiJobPlan, AiJobStatus } from '../model/ai-jobs.types'\nimport {\n AI_TEMPLATE_SUBJECT_DEFINITIONS,\n aiTemplateShownTokens,\n type AiTemplateSubject,\n} from '../model/ai-template-subjects'\nimport { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS } from '../model/ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from '../model/ai-page-job'\nimport {\n aiPlanCapabilitiesForJob,\n aiPlanCapabilityLines,\n type AiPlanCapabilities,\n type AiPlanJobScope,\n} from '../model/ai-plan-capabilities'\nimport type { AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_SITE_CREATE_KINDS, aiSitePlanShapeRefusal } from '../model/ai-site-job'\nimport { aiDoctrineSystemBlocks, runValidatedGeneration } from '../runtime/ai-doctrine'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport {\n aiPlanEmbedBriefViolations,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport type { AiTool } from '../providers/contract'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { aiJobAdmissionRefusal } from './ai-job-admission'\nimport { readAiPlanCapabilities } from './ai-job-drafts'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiDoctrineReview, aiUnspentOutcome } from './ai-job-generation'\nimport { aiPlanWithDraftIds } from './ai-job-draft-ids'\nimport { AI_JOBS_COLLECTION, registerAiJobPlanStep } from './ai-jobs'\n\n/**\n * The plan step (AGL-2935): the first step of every job that builds site\n * structure. Before a node is generated, the model answers with a typed plan\n * — what the site already has that the job reuses, what it creates and why,\n * and the screens it builds from them — held to the doctrine's plan rules\n * against the site's inventory and re-asked once when it breaks one.\n *\n * A plan that passes is kept on the job, and the job stops for review: the\n * member reads what it will build before a credit is spent building it, and\n * confirms through the resume door. A plan that still breaks a rule on its\n * re-ask stops the job the same way, with the rules named. Either way the\n * step writes nothing itself; the machine records the plan, the spend and\n * the stop, as it records every step.\n *\n * ── Told what it may create, refused what it cannot build (AGL-3030) ─────\n *\n * Before it asks, the step reads what the job may create on its site: the\n * workspace's plan, the site's counts against its limits, and — for a kind\n * that builds only some plans — what the job builds itself. The user turn\n * states it, the plan rules refuse a creation outside it with the one re-ask,\n * and where the workspace keeps no reusable components they accept the page\n * built inline. A plan the confirm door would still refuse — the kind's own\n * admission, handed the plan, as the resume door hands it — is refused HERE,\n * before a member is shown a Confirm: the job fails with the door's sentence,\n * its plan is not kept, and it holds nothing.\n */\n\n/** The plan step's own instructions, cached after the doctrine. */\nexport const AI_JOB_PLAN_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text:\n 'You plan what a website builder job will build, before anything is built. You are given the kind of job and the brief from the person who owns the site. ' +\n 'Answer with submit_build_plan: what the site inventory already has that the job reuses, what it must create and why nothing listed will do, and each page it builds (the screens list) with its layout, template, slug, search title, search description and sections from top to bottom. ' +\n 'Refer to inventory records by id and to what the plan creates as new:<name>. Plan only what the brief asks for: a component, layout, template, form or email job plans no pages unless the brief asks for them, and a page job plans exactly one page.',\n },\n]\n\n/**\n * The plan step's time (AGL-3026, AGL-3036): its two inventory-lookup rounds,\n * its answer and its re-ask at the routing table's ceiling for `job.plan`, on\n * the tier that step kind is served from, with the step's reads and writes,\n * at the rates `ai-job-budget.ts` assumes — fitted to what a beat can give a\n * step, so the served tier asks a little under the routing ceiling.\n */\nexport const AI_JOB_PLAN_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.plan'],\n maxTokens: AI_ROUTING_TABLE['job.plan'].maxTokens,\n})\n\n/**\n * The least time a plan needs before it starts. Registered with the step, so\n * an inline door never starts a plan. A plan thinks before it answers and\n * routinely runs past an inline door's budget, and a provider call that\n * budget cuts off is still generated and billed upstream while the meter\n * records nothing. The beat starts a plan only with this much of its own\n * budget left, and a spec holds it inside that budget.\n */\nexport const AI_JOB_PLAN_STEP_MINIMUM_MS = AI_JOB_PLAN_STEP_BUDGET.minimumMs\n\n/**\n * The plan's answer ceiling on the model a job runs: the most whose worst\n * case fits the least time the step registered, and never more than the\n * routing table's. A model the catalog does not know is planned at the\n * slowest.\n */\nexport function aiJobPlanMaxTokens(model: string): number {\n return AI_JOB_PLAN_STEP_BUDGET.maxTokens(model)\n}\n\n/** What the member reads while a plan waits for them. */\nexport const AI_JOB_PLAN_REVIEW_COPY =\n 'The plan is ready. Review what the job will reuse and create, then confirm it to build.'\n\n/**\n * The job as the plan step's user turn: its kind, its brief, its scalar\n * inputs and, where the job read them, what it may create on its site\n * (AGL-3030). Per workspace and per site, which is why they ride here and\n * never in a system block the platform's cache entry is keyed on.\n */\nexport function aiJobPlanPrompt(\n job: Pick<AiJob, 'kind' | 'brief' | 'inputs'>,\n capabilities: AiPlanCapabilities | null = null,\n): string {\n const lines = [`Job kind: ${job.kind}`, `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`]\n for (const [key, value] of Object.entries(job.inputs ?? {})) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${key}: ${String(value)}`)\n }\n }\n if (capabilities) lines.push(...aiPlanCapabilityLines(capabilities))\n lines.push(...aiPlanTemplateTokenLines(job))\n return lines.join('\\n')\n}\n\n/**\n * What a TEMPLATE job's plan is told its creation's `fields` are (AGL-3143\n * §11): the subject's binding tokens, listed so the planner promises only\n * what the page can fill and the build can be held to it\n * (`aiPlanTemplateTokenViolations`).\n *\n * ⛔ It rides the job's own turn and NOT the `fields` description in the plan\n * tool, which every kind's request caches. Written there it read better and\n * cost 40 tokens of the shared prefix — 2,980 to 3,020 — which is one credit\n * of the Free page's 300-credit wall, taking the room it keeps for a re-asked\n * section from 45 credits to 44 and breaking `ai-job-free-page.spec.ts`. A\n * page job must not pay for a sentence about templates.\n *\n * The list is the tokens a reader sees, never the whole catalog (AGL-3143\n * §16). Offered a slug and the publish timestamp, a live plan promised them,\n * and its build kept the promise the only way a slug can be kept: it printed\n * \"entry slug: {{entry.slug}}\" on every article.\n */\nexport function aiPlanTemplateTokenLines(job: Pick<AiJob, 'kind' | 'inputs'>): string[] {\n if (job.kind !== 'template') return []\n const subject = (job.inputs ?? {})['subject']\n const definition =\n typeof subject === 'string'\n ? AI_TEMPLATE_SUBJECT_DEFINITIONS[subject as AiTemplateSubject]\n : undefined\n if (!definition) return []\n return [\n `The template's fields are the binding tokens its page shows a reader, each one of: ${aiTemplateShownTokens(\n definition,\n ).join(', ')}. A link or picture token is shown by the link or picture holding it. Promise only what the page fills.`,\n ]\n}\n\n/** A kind that builds only some plans: the creations it makes, and the shapes it refuses. */\nexport interface AiJobPlanScope extends AiPlanJobScope {\n /** Why a job of the kind cannot build a plan of this shape; `null` when it can. Pure. */\n shapeRefusal: (plan: AiBuildPlan) => string | null\n}\n\n/**\n * The kinds whose confirm door builds only some plans (AGL-3030). A page job\n * builds one screen and a scaffold four to eight; each builds only the\n * creations its list names. A template, layout, component, form or email job\n * builds its own record and what that record places (AGL-3143 §15,\n * `AI_JOB_CREATE_KINDS`), and holds no plan to a shape.\n */\nexport const AI_JOB_PLAN_SCOPES: Readonly<Partial<Record<AiJobKind, AiJobPlanScope>>> = {\n page: { noun: 'a page job', creates: AI_PAGE_CREATE_KINDS, shapeRefusal: aiPagePlanShapeRefusal },\n site: { noun: 'a site scaffold', creates: AI_SITE_CREATE_KINDS, shapeRefusal: aiSitePlanShapeRefusal },\n ...Object.fromEntries(\n Object.entries(AI_JOB_CREATE_KINDS).map(([kind, creates]) => [\n kind,\n { noun: AI_JOB_CREATE_NOUNS[kind as AiJobKind] as string, creates, shapeRefusal: () => null },\n ]),\n ),\n}\n\n/**\n * What the plan step holds beyond the doctrine's plan rules, as the\n * violations the plan's one re-ask names: the players the plan lists against\n * the brief it answers (AGL-3433), which only this step reads beside the plan,\n * and a kind's shape refusal.\n */\nfunction planViolations(\n scope: AiJobPlanScope | null,\n brief: string,\n): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n return (plan) => {\n const message = scope?.shapeRefusal(plan) ?? null\n return [\n ...aiPlanEmbedBriefViolations(plan, brief),\n ...(message ? [{ rule: null, code: 'plan-job-shape', message }] : []),\n ]\n }\n}\n\n/**\n * The inventory names of every id the plan references, so the proposal reads\n * \"the Services layout\" rather than an id — kept on the plan because the\n * inventory is not read again when the member opens it.\n */\nexport function aiPlanLabels(\n plan: AiBuildPlan,\n inventory: AiSiteInventory | null,\n): Record<string, string> {\n const names = new Map<string, string>()\n for (const rows of [\n inventory?.components,\n inventory?.layouts,\n inventory?.templates,\n inventory?.forms,\n inventory?.datasets,\n inventory?.collections,\n inventory?.screens,\n ]) {\n for (const row of rows ?? []) names.set(row.id, row.name)\n }\n const refs = [\n ...plan.reuse.map((entry) => entry.id),\n ...plan.create.map((entry) => entry.duplicateOf),\n ...plan.screens.flatMap((screen) => [\n screen.layout,\n screen.template,\n screen.duplicateOf,\n ...screen.sections.flatMap((section) => section.uses),\n ]),\n ]\n const labels: Record<string, string> = {}\n for (const ref of refs) {\n if (!ref || isAiPlanNewRef(ref)) continue\n const name = names.get(ref)\n if (name) labels[ref] = name\n }\n return labels\n}\n\n/* ------------------------------------------------------------------------ *\n * Reusing an identical brief's plan\n * ------------------------------------------------------------------------ */\n\n/**\n * How long a plan may be reused for (AGL-2937).\n *\n * The window is what makes reuse safe rather than merely cheap. The key\n * already covers everything the request said — the brief, the inputs, the\n * model and the prompt as rendered, site inventory included — so a plan is\n * reused only when nothing the model was shown has changed. What the key\n * cannot see is the world outside the request: a page published, a component\n * renamed after the inventory was read, a member who meant something\n * different the second time. Fifteen minutes is short enough that the answer\n * is still the one the site would give, and long enough to cover what reuse\n * is actually for — the same brief run twice in a sitting, a job resubmitted\n * after a refused reservation, two members starting the same work.\n */\nexport const AI_PLAN_REUSE_WINDOW_MS = 15 * 60 * 1_000\n\n/** Jobs one lookup reads. More than one can share a key; the newest plan wins. */\nexport const AI_PLAN_REUSE_CANDIDATES = 5\n\n/** A job whose plan might be reused, as the finder reports it. */\nexport interface AiJobPlanCandidate {\n jobId: string\n status: AiJobStatus\n plan: AiJobPlan\n}\n\n/**\n * The digest a plan is reused by: the whole request, never the answer.\n *\n * It hashes what the model was asked and what it was shown — the job's kind\n * and site, the user turn (which carries the trimmed brief and every scalar\n * input), the model that would answer, every rendered system block including\n * the site inventory, and the tool's schema. Two requests that hash the same\n * would have been sent the same bytes to the same model, so the second can\n * keep the first's answer.\n *\n * The version tag is the escape hatch: anything that changes what a key\n * MEANS, rather than what it covers, bumps it and strands every old key\n * harmlessly, since a key nothing matches simply asks the model.\n */\nexport function aiJobPlanKey(input: {\n job: Pick<AiJob, 'kind' | 'hostId'>\n prompt: string\n model: string\n system: readonly AiSystemBlock[]\n /** The plan tool the request offers (AGL-3433); the plain one when absent. */\n tool?: AiTool\n}): string {\n const digest = createHash('sha256')\n for (const part of [\n 'plan.v1',\n input.job.kind,\n input.job.hostId ?? '',\n input.model,\n input.prompt,\n ...input.system.map((block) => block.text),\n JSON.stringify(input.tool ?? AI_BUILD_PLAN_TOOL),\n ]) {\n // Length-prefixed, so two different splits of the same characters cannot\n // collide by running into one another.\n digest.update(`${part.length}:${part}\\u0000`)\n }\n return digest.digest('hex')\n}\n\n/** A job that could not have produced a reusable plan, whatever its key says. */\nconst UNREUSABLE: readonly AiJobStatus[] = ['canceled', 'failed']\n\nfunction planMillis(plan: AiJobPlan): number | null {\n const at = plan.proposedAt as unknown\n if (at instanceof Date) return at.getTime()\n if (at && typeof (at as { toMillis?: unknown }).toMillis === 'function') {\n return (at as { toMillis(): number }).toMillis()\n }\n return null\n}\n\n/**\n * The newest plan worth reusing out of what the finder returned: another\n * job's, still proposed or already confirmed, on a job that was neither\n * canceled nor failed, proposed inside the window.\n *\n * A canceled or failed job is excluded even though its plan may be perfectly\n * good: those are the jobs a member walked away from, and handing their plan\n * to the next one would make a rejected answer look like a fresh one.\n */\nexport function aiReusablePlan(\n candidates: readonly AiJobPlanCandidate[],\n context: { jobId: string; now: Date },\n): AiJobPlanCandidate | null {\n const floor = context.now.getTime() - AI_PLAN_REUSE_WINDOW_MS\n let best: AiJobPlanCandidate | null = null\n let bestAt = -1\n for (const candidate of candidates) {\n if (candidate.jobId === context.jobId) continue\n if (UNREUSABLE.includes(candidate.status)) continue\n if (candidate.plan.status !== 'proposed' && candidate.plan.status !== 'confirmed') continue\n const at = planMillis(candidate.plan)\n if (at === null || at < floor || at > context.now.getTime()) continue\n if (at > bestAt) {\n best = candidate\n bestAt = at\n }\n }\n return best\n}\n\nexport type AiJobPlanFinder = (\n orgId: string,\n key: string,\n firestore?: FirebaseFirestore.Firestore,\n) => Promise<AiJobPlanCandidate[]>\n\n/**\n * The finder the step uses in production: an equality on `plan.key` inside\n * the org's own jobs.\n *\n * One equality on one field, with no ordering beside it, so Firestore's\n * automatic single-field index answers it and no composite index is deployed.\n * The window and the statuses are applied in memory instead, which is what\n * keeps it that way.\n */\nexport const findAiJobsByPlanKey: AiJobPlanFinder = async (orgId, key, firestore) => {\n if (!firestore) return []\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(AI_JOBS_COLLECTION)\n .where('plan.key', '==', key)\n .limit(AI_PLAN_REUSE_CANDIDATES)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const data = doc.data() as { status?: AiJobStatus; plan?: AiJobPlan | null }\n return data.plan\n ? [{ jobId: doc.id, status: data.status ?? 'queued', plan: data.plan }]\n : []\n })\n}\n\n/** What a job may create on its site, read for the plan step; `null` for no site. */\nexport type AiPlanCapabilitiesReader = (input: {\n job: AiJob\n org: Partial<AglynOrgBilling> | null\n firestore: FirebaseFirestore.Firestore\n}) => Promise<AiPlanCapabilities | null>\n\n/** The reader the step uses in production: the draft bands' own arithmetic, for the job's site. */\nexport const readAiJobPlanCapabilities: AiPlanCapabilitiesReader = async ({ job, org, firestore }) =>\n job.hostId ? readAiPlanCapabilities(firestore, { hostId: job.hostId, org }) : null\n\nexport interface AiJobPlanStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The reuse lookup; specs hand in a fake, and `null` turns reuse off. */\n findPlansByKey?: AiJobPlanFinder | null\n /** What the job may create on its site (AGL-3030); specs and the eval recorder hand in their own. */\n readCapabilities?: AiPlanCapabilitiesReader\n /** The kind's admission, asked of a plan before it is kept; the registry's otherwise. */\n admissionRefusal?: typeof aiJobAdmissionRefusal\n}\n\nexport function createAiJobPlanStep(deps: AiJobPlanStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const findPlansByKey =\n deps.findPlansByKey === undefined ? findAiJobsByPlanKey : deps.findPlansByKey\n const readCapabilities = deps.readCapabilities ?? readAiJobPlanCapabilities\n const admissionRefusal = deps.admissionRefusal ?? aiJobAdmissionRefusal\n return async ({ job, now, signal, firestore, modelFor, org: orgDocument }) => {\n const org = (orgDocument ?? null) as Partial<AglynOrgBilling> | null\n const [inventory, workspace] = await Promise.all([\n job.hostId ? readInventory(job.orgId, job.hostId, { firestore }) : Promise.resolve(null),\n readCapabilities({ job, org, firestore }),\n ])\n const scope = AI_JOB_PLAN_SCOPES[job.kind] ?? null\n const capabilities = workspace ? aiPlanCapabilitiesForJob(workspace, scope) : null\n // The model switch's answer for this job (AGL-2942): the creator's pick\n // where the plan, the org restriction and the allotment allowlists allow\n // it, and Auto held to those same lists otherwise. Without a resolver the\n // doctrine asks the routing table itself.\n const model = modelFor?.('job.plan')\n const route = AI_ROUTING_TABLE['job.plan']\n const prompt = aiJobPlanPrompt(job, capabilities)\n\n /**\n * The confirm door's answer for this plan, asked before the plan is kept\n * (AGL-3030): the kind's own admission, handed the plan as the resume\n * door hands it. A refusal fails the job with the door's sentence; a door\n * that could not answer keeps the plan, since the resume door asks again\n * before anything is built.\n */\n const refusalOnKeep = async (plan: AiJobPlan, outcome: AiJobStepOutcome): Promise<AiJobStepOutcome | null> => {\n let refusal: Awaited<ReturnType<typeof aiJobAdmissionRefusal>> = null\n try {\n refusal = await admissionRefusal(job.kind, {\n firestore,\n orgId: job.orgId,\n hostId: job.hostId ?? null,\n inputs: job.inputs ?? {},\n org,\n plan,\n uid: job.createdBy,\n })\n } catch (error) {\n console.error('ai plan admission failed', { orgId: job.orgId, jobId: job.$id, error })\n }\n return refusal ? { ...outcome, failure: refusal.error } : null\n }\n\n // Reuse before asking (AGL-2937). The key covers the whole request, so a\n // hit is a request that would have been sent the same bytes to the same\n // model; the window covers what the key cannot see. A reused plan spends\n // nothing, so the machine's spent-nothing branch releases the reservation\n // and meters no credit, and the job still stops for the member to confirm\n // — this job's plan is proposed to this member, whatever the last one did\n // with theirs.\n const resolved = model ?? aiModelForStep('job.plan')\n // A brief that asks for a video is offered the plan's list of players (AGL-3433).\n const tool = aiBuildPlanToolFor(job.brief)\n const key = aiJobPlanKey({\n job,\n prompt,\n model: resolved,\n system: aiDoctrineSystemBlocks(inventory, { instructions: AI_JOB_PLAN_INSTRUCTIONS }),\n tool,\n })\n const reused = findPlansByKey\n ? aiReusablePlan(await findPlansByKey(job.orgId, key, firestore), {\n jobId: job.$id,\n now,\n })\n : null\n if (reused) {\n // The reused plan's draft ids name the other job's drafts; this job's are its own.\n const plan: AiJobPlan = aiPlanWithDraftIds(job.kind, {\n ...reused.plan,\n status: 'proposed',\n labels: aiPlanLabels(reused.plan, inventory),\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n reusedFrom: reused.jobId,\n })\n const unspent = aiUnspentOutcome(resolved)\n return (\n (await refusalOnKeep(plan, unspent)) ?? {\n ...unspent,\n plan,\n review: { reason: 'plan', message: AI_JOB_PLAN_REVIEW_COPY, findings: [] },\n }\n )\n }\n\n const result = await runValidatedGeneration('plan', {\n step: 'job.plan',\n ...(model ? { model } : {}),\n instructions: AI_JOB_PLAN_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: prompt }],\n tool,\n // The routing ceiling, lowered on a tier too slow to look records up,\n // answer and ask again inside the least time the step registered.\n maxTokens: aiJobPlanMaxTokens(resolved),\n ...(route.thinking ? { thinking: route.thinking } : {}),\n ...(route.effort ? { effort: route.effort } : {}),\n ...(signal ? { signal } : {}),\n capabilities,\n extend: planViolations(scope, job.brief),\n })\n const spent: AiJobStepOutcome = {\n outputs: [],\n usage: result.usage,\n estCostUsd: result.estCostUsd,\n model: result.model,\n stopReason: result.stopReason,\n ...(result.effort ? { effort: result.effort } : {}),\n }\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n // Each draft the plan decides is named as the plan is kept, before anything is built (AGL-3079).\n const plan: AiJobPlan = aiPlanWithDraftIds(job.kind, {\n ...result.value,\n status: 'proposed',\n labels: aiPlanLabels(result.value, inventory),\n // A Date the Admin SDK stores as a timestamp, like every instant the machine writes.\n proposedAt: now as unknown as AiJobPlan['proposedAt'],\n confirmedAt: null,\n confirmedBy: null,\n key,\n })\n return (\n (await refusalOnKeep(plan, spent)) ?? {\n ...spent,\n plan,\n review: { reason: 'plan', message: AI_JOB_PLAN_REVIEW_COPY, findings: [] },\n }\n )\n }\n}\n\nexport const runAiJobPlanStep = createAiJobPlanStep()\n\n/**\n * Registers the plan step every planned kind runs first, with the least time\n * a plan needs; the plugin's console surface calls it.\n */\nexport function registerAiJobPlan(): void {\n registerAiJobPlanStep(runAiJobPlanStep, { minimumMs: AI_JOB_PLAN_STEP_MINIMUM_MS })\n}\n"],"names":["createHash","AI_BUILD_PLAN_TOOL","aiBuildPlanToolFor","isAiPlanNewRef","AI_TEMPLATE_SUBJECT_DEFINITIONS","aiTemplateShownTokens","AI_JOB_CREATE_KINDS","AI_JOB_CREATE_NOUNS","AI_PAGE_CREATE_KINDS","aiPagePlanShapeRefusal","aiPlanCapabilitiesForJob","aiPlanCapabilityLines","AI_SITE_CREATE_KINDS","aiSitePlanShapeRefusal","aiDoctrineSystemBlocks","runValidatedGeneration","AI_STEP_TIERS","aiPlanEmbedBriefViolations","AI_ROUTING_TABLE","aiModelForStep","readSiteInventory","aiJobAdmissionRefusal","readAiPlanCapabilities","AI_JOB_BRIEF_MAX_CHARS","aiJobStepBudget","aiDoctrineReview","aiUnspentOutcome","aiPlanWithDraftIds","AI_JOBS_COLLECTION","registerAiJobPlanStep","AI_JOB_PLAN_INSTRUCTIONS","text","AI_JOB_PLAN_STEP_BUDGET","tier","maxTokens","AI_JOB_PLAN_STEP_MINIMUM_MS","minimumMs","aiJobPlanMaxTokens","model","AI_JOB_PLAN_REVIEW_COPY","aiJobPlanPrompt","job","capabilities","lines","kind","brief","slice","key","value","Object","entries","inputs","push","String","aiPlanTemplateTokenLines","join","subject","definition","undefined","AI_JOB_PLAN_SCOPES","page","noun","creates","shapeRefusal","site","fromEntries","map","planViolations","scope","plan","message","rule","code","aiPlanLabels","inventory","names","Map","rows","components","layouts","templates","forms","datasets","collections","screens","row","set","id","name","refs","reuse","entry","create","duplicateOf","flatMap","screen","layout","template","sections","section","uses","labels","ref","get","AI_PLAN_REUSE_WINDOW_MS","AI_PLAN_REUSE_CANDIDATES","aiJobPlanKey","input","digest","part","hostId","prompt","system","block","JSON","stringify","tool","update","length","UNREUSABLE","planMillis","at","proposedAt","Date","getTime","toMillis","aiReusablePlan","candidates","context","floor","now","best","bestAt","candidate","jobId","includes","status","findAiJobsByPlanKey","orgId","firestore","snapshot","collection","doc","where","limit","docs","data","readAiJobPlanCapabilities","org","createAiJobPlanStep","deps","readInventory","findPlansByKey","readCapabilities","admissionRefusal","signal","modelFor","orgDocument","workspace","Promise","all","resolve","route","refusalOnKeep","outcome","refusal","uid","createdBy","error","console","$id","failure","resolved","instructions","reused","confirmedAt","confirmedBy","reusedFrom","unspent","review","reason","findings","result","step","messages","role","content","thinking","effort","extend","spent","outputs","usage","estCostUsd","stopReason","refused","runAiJobPlanStep","registerAiJobPlan"],"mappings":"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"}
@@ -150,6 +150,8 @@ export interface AiSiteBuiltRef {
150
150
  */
151
151
  export declare function aiSiteBuiltRefs(units: readonly AiSiteUnit[], outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[]): Map<string, AiSiteBuiltRef>;
152
152
  type BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>;
153
+ /** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */
154
+ export declare function aiSiteResolvedRef(ref: string | null, built: BuiltRefs): string | null;
153
155
  /**
154
156
  * The site's own words, as every unit's brief carries them.
155
157
  *
@@ -209,7 +209,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
209
209
  }
210
210
  return built;
211
211
  }
212
- /** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */ function resolved(ref, built) {
212
+ /** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */ export function aiSiteResolvedRef(ref, built) {
213
213
  var _ref;
214
214
  var _built_get;
215
215
  if (!ref) return null;
@@ -313,7 +313,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
313
313
  unitPlan.reuse = plan.reuse.filter((entry)=>!placedByScreens.has(entry.id));
314
314
  unitPlan.create = [
315
315
  _extends({}, unit.creation, {
316
- duplicateOf: resolved(unit.creation.duplicateOf, built)
316
+ duplicateOf: aiSiteResolvedRef(unit.creation.duplicateOf, built)
317
317
  })
318
318
  ];
319
319
  brief.push(`Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`);
@@ -332,11 +332,11 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
332
332
  }
333
333
  unitPlan.screens = [
334
334
  _extends({}, screen, {
335
- layout: resolved(screen.layout, built),
336
- template: resolved(screen.template, built),
337
- duplicateOf: resolved(screen.duplicateOf, built),
335
+ layout: aiSiteResolvedRef(screen.layout, built),
336
+ template: aiSiteResolvedRef(screen.template, built),
337
+ duplicateOf: aiSiteResolvedRef(screen.duplicateOf, built),
338
338
  sections: screen.sections.map((section)=>_extends({}, section, {
339
- uses: section.uses.map((ref)=>resolved(ref, built)).filter((ref)=>!!ref)
339
+ uses: section.uses.map((ref)=>aiSiteResolvedRef(ref, built)).filter((ref)=>!!ref)
340
340
  }))
341
341
  })
342
342
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n type AiBuildPlanScreen,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobKind,\n AiJobOutput,\n AiJobOutputResource,\n AiJobPlan,\n} from '../model/ai-jobs.types'\nimport {\n AI_SITE_EMAIL_TYPE,\n AI_SITE_MAX_SECTIONS,\n AI_SITE_PAGES,\n aiSitePlanRefusal,\n aiSiteSubmissions,\n aiSiteWords,\n parseAiSiteJobInputs,\n type AiSiteJobInputs,\n} from '../model/ai-site-job'\nimport {\n AI_SITE_SEO_OUTPUT_ID,\n aiSiteSeoProposalForInputs,\n} from '../model/ai-site-start-seo'\nimport { aiModelForStep } from '../providers/routing'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiRecordedJobDraftId } from './ai-job-draft-ids'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepContext,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The site scaffold (AGL-2911): the generation step of a `site` job, run once\n * a member confirmed the plan their brief produced.\n *\n * ── It builds nothing itself ─────────────────────────────────────────────\n *\n * A scaffold is a whole site: a palette suggestion, the layout its pages\n * render inside, the contact form they place, four to eight pages with their\n * search listings, and a welcome email. Every one of those is already a job\n * kind with a step of its own, so this step generates nothing and asks no\n * model anything. It works through the confirmed plan one UNIT at a time and\n * hands each unit to the runner registered for the kind that owns it — the\n * theme step for the palette, the page step for a page — under a job of that\n * kind derived from this one.\n *\n * That is why the scaffold cannot drift from the thing it scaffolds: a page\n * it builds is a page job's page, held to the same doctrine, written by the\n * same draft writer, left unpublished by the same rule. A kind whose step\n * this deployment has not loaded is simply not among the units, so nothing\n * here knows which issues have landed.\n *\n * ── One unit a pass ──────────────────────────────────────────────────────\n *\n * The step runs ONE delegated pass and asks the machine to continue, so every\n * pass is one reservation, one provider exchange and one recorded spend, and\n * the org's monthly ceiling binds a scaffold exactly as it binds a chat turn.\n * Credits running out mid-scaffold is therefore the machine's own park: the\n * job waits as `needs_input` with the meter's words, and the beat resumes it\n * where it stopped once the workspace's standing has changed. A pass needs the\n * time its unit's own step needs (`aiSiteJobRunMinimumMs`), so the beat starts\n * a palette change only with a palette change's time left.\n *\n * Where it stopped is read from the job's OUTPUTS rather than kept anywhere:\n * each unit reports exactly one output when it completes, so the units\n * already represented there are the ones already built. A pass interrupted\n * between its work and its record repeats a unit that then finds its own\n * draft and reports it, which is what every step's draft id already gives.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * Nothing here publishes, routes or sends: each delegated step writes the\n * unpublished draft it already wrote for a job of its own kind, and the\n * palette and navigation travel as proposals a member applies.\n *\n * ── The page job builds its creations the same way (AGL-3031) ────────────\n *\n * A page job whose plan creates a layout, forms or components builds them\n * before its page through this same machinery: the units a creation implies\n * (`aiCreationUnit`), where the job stands read from its outputs\n * (`aiSitePendingUnits`), one delegated pass a unit (`aiRunJobUnit`), and the\n * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,\n * `aiSiteUnitJob`). There is one loop, and it is the machine's.\n */\n\n/** What a scaffold's units are, in the order it builds them; a component is a page job's. */\nexport type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email'\n\nexport interface AiSiteUnit {\n kind: AiSiteUnitKind\n /** The job kind whose registered runner builds it. */\n jobKind: AiJobKind\n /** The resource the unit reports when it completes. */\n resource: AiJobOutputResource\n /** Stable within the job: what the unit's derived job and draft are addressed by. */\n slot: string\n /** The plan screen a page unit builds. */\n screen?: AiBuildPlanScreen\n /** The creation a theme, layout or form unit builds. */\n creation?: AiBuildPlanCreate\n /** What the unit is, for the derived job's brief. */\n label: string\n}\n\n/** The job kind and output resource each unit is built through. */\nconst UNIT_KINDS: Record<\n AiSiteUnitKind,\n { jobKind: AiJobKind; resource: AiJobOutputResource }\n> = {\n theme: { jobKind: 'theme', resource: 'theme' },\n layout: { jobKind: 'layout', resource: 'layout' },\n form: { jobKind: 'form', resource: 'form' },\n component: { jobKind: 'component', resource: 'reusableComponent' },\n page: { jobKind: 'page', resource: 'screen' },\n email: { jobKind: 'email', resource: 'emailScreen' },\n}\n\n/** The unit kind a creation is built as, where a unit builds it. */\nconst CREATION_UNIT_KINDS: Partial<Record<AiBuildPlanCreateKind, AiSiteUnitKind>> = {\n 'theme-change': 'theme',\n layout: 'layout',\n form: 'form',\n component: 'component',\n}\n\n/**\n * The unit one creation is built as, under the slot its derived job and draft\n * are addressed by; `null` for a creation no unit builds.\n */\nexport function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null {\n const kind = CREATION_UNIT_KINDS[creation.kind]\n if (!kind) return null\n return { kind, ...UNIT_KINDS[kind], slot, creation, label: creation.name }\n}\n\n/** The creation kinds a unit is built from, by unit kind. */\nconst CREATION_UNITS: Array<{\n unit: AiSiteUnitKind\n create: AiBuildPlanCreateKind\n}> = [\n { unit: 'theme', create: 'theme-change' },\n { unit: 'layout', create: 'layout' },\n { unit: 'form', create: 'form' },\n]\n\nexport const AI_SITE_NO_PLAN_COPY =\n 'This site job has no confirmed plan to build.'\n\n/** What a scaffold answers where the step that builds a page is not loaded. */\nexport const AI_SITE_NO_PAGE_STEP_COPY =\n 'Building a whole site is not available yet.'\n\n/** A unit that finished without reporting what it built; the scaffold cannot go on. */\nexport const AI_SITE_UNIT_EMPTY_COPY =\n 'Part of this site could not be built. Try the site brief again.'\n\n/**\n * The most passes a scaffold's step may take: the largest plan it admits —\n * eight pages of eight sections, its three creations and a welcome email —\n * with nothing to spare, so a runner that never finishes is still bounded\n * while a real site is not.\n */\nexport const AI_SITE_MAX_PASSES =\n AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + 1\n\n/**\n * The units a plan implies, in build order: the palette first, because a\n * member reads it while the pages are still building; then the layout every\n * page renders inside and the form they place, because a page binds both by\n * id and so needs them to exist; then the pages; then the welcome email,\n * which is about the site rather than part of it.\n */\nexport function aiSiteJobUnits(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n options: { welcomeEmail?: boolean } = {},\n): AiSiteUnit[] {\n const units: AiSiteUnit[] = []\n for (const { unit, create } of CREATION_UNITS) {\n const creation = plan.create.find((entry) => entry.kind === create)\n if (!creation) continue\n units.push({\n kind: unit,\n ...UNIT_KINDS[unit],\n slot: unit[0],\n creation,\n label: creation.name,\n })\n }\n plan.screens.forEach((screen, index) => {\n units.push({\n kind: 'page',\n ...UNIT_KINDS.page,\n slot: `p${index}`,\n screen,\n label: screen.title,\n })\n })\n if (options.welcomeEmail) {\n units.push({\n kind: 'email',\n ...UNIT_KINDS.email,\n slot: 'e',\n label: 'Welcome email',\n })\n }\n return units\n}\n\n/**\n * The units a scaffold still owes after the outputs recorded so far. Each\n * unit reports one output of its own resource when it completes, so the\n * outputs already recorded are consumed against the units in build order.\n */\nexport function aiSitePendingUnits(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource'>[],\n): AiSiteUnit[] {\n const done = new Map<AiJobOutputResource, number>()\n for (const output of outputs) {\n done.set(output.resource, (done.get(output.resource) ?? 0) + 1)\n }\n return units.filter((unit) => {\n const left = done.get(unit.resource) ?? 0\n if (left <= 0) return true\n done.set(unit.resource, left - 1)\n return false\n })\n}\n\n/**\n * The site's own search listing as the scaffold reports it (AGL-2918), or\n * nothing when the job already reported one or its inputs describe no site.\n *\n * It costs nothing and asks no model: the values are arithmetic on the\n * answers (`aiSiteSeoProposal`), so they ride out on the first pass that\n * builds anything rather than waiting for a pass of their own. Its resource\n * is `seo`, which no unit reports, so it cannot be mistaken for a unit's\n * output by `aiSitePendingUnits` and cannot move where the scaffold thinks\n * it is.\n */\nexport function aiSiteSeoOutputs(job: AiJob): AiJobOutput[] {\n const outputs = job.outputs ?? []\n if (outputs.some((output) => output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {\n return []\n }\n const proposal = aiSiteSeoProposalForInputs(job.inputs)\n if (!proposal) return []\n return [\n {\n resource: 'seo',\n id: AI_SITE_SEO_OUTPUT_ID,\n hostId: job.hostId ?? null,\n label: 'The site’s search title and description',\n proposal: proposal as unknown as Record<string, unknown>,\n },\n ]\n}\n\n/** A record an earlier unit created, as a later one references it. */\nexport interface AiSiteBuiltRef {\n id: string\n label: string\n /** Only what becomes a record a page can name; a palette change is not one. */\n kind: 'layout' | 'form' | 'component'\n}\n\n/**\n * What the scaffold has already created, as the plan's `new:<name>`\n * references resolve to: the id of the record each completed creation wrote.\n * A page unit is told about the layout and the form by id, exactly as a page\n * job is told about records the site already had.\n */\nexport function aiSiteBuiltRefs(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const byResource = new Map<\n AiJobOutputResource,\n Array<Pick<AiJobOutput, 'id' | 'label'>>\n >()\n for (const output of outputs) {\n const rows = byResource.get(output.resource) ?? []\n rows.push(output)\n byResource.set(output.resource, rows)\n }\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const rows = byResource.get(unit.resource)\n const row = rows?.shift()\n if (!row || !unit.creation) continue\n // A theme change is a proposal with no record to reference; a layout, a\n // form and a component become ids a page can name.\n if (\n unit.creation.kind !== 'layout' &&\n unit.creation.kind !== 'form' &&\n unit.creation.kind !== 'component'\n ) {\n continue\n }\n built.set(unit.creation.name.toLowerCase(), {\n id: row.id,\n label: row.label || unit.creation.name,\n kind: unit.creation.kind,\n })\n }\n return built\n}\n\ntype BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>\n\n/** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */\nfunction resolved(ref: string | null, built: BuiltRefs): string | null {\n if (!ref) return null\n if (!isAiPlanNewRef(ref)) return ref\n return (\n built.get(\n ref\n .slice(ref.indexOf(':') + 1)\n .trim()\n .toLowerCase(),\n )?.id ?? null\n )\n}\n\n/**\n * The site's own words, as every unit's brief carries them.\n *\n * The audience is here rather than left to the brief's prose (AGL-2918). A\n * guided start writes it into the brief itself, but an agency batch's brief\n * is a member's own sentence and may never mention it, and a theme, a layout\n * or a form asked to serve nobody in particular serves nobody in particular.\n */\nexport function aiSiteBriefLines(\n brief: string,\n inputs: AiSiteJobInputs,\n): string[] {\n const lines = [brief.trim()]\n const site = [\n inputs.businessName ? `name: ${inputs.businessName}` : '',\n `business: ${inputs.businessType}`,\n inputs.audience ? `for: ${inputs.audience}` : '',\n inputs.city ? `city: ${inputs.city}` : '',\n inputs.brand ? `brand: ${inputs.brand}` : '',\n ].filter(Boolean)\n lines.push(`Site — ${site.join('; ')}.`)\n return lines\n}\n\n/**\n * What the welcome email is told (AGL-2918), beyond that it is a welcome\n * email.\n *\n * The scaffold's email unit used to carry one sentence, and a yes/no toggle\n * decided whether it ran — so the draft was a welcome email for a business\n * in general, to nobody in particular, about nothing that had happened. The\n * two answers it is missing are who wrote in and what now becomes of what\n * they wrote, and the second of those is the person's own routing answer, so\n * the email and the form it acknowledges cannot say different things.\n *\n * ⛔ A DRAFT either way. Nothing here sends, schedules or enrolls anybody:\n * the email step writes an unpublished email design and stops.\n */\nexport function aiSiteEmailBriefLines(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): string[] {\n const lines = [\n 'Write the welcome email this site sends someone who gets in touch.',\n ]\n const { audience } = aiSiteWords(inputs)\n if (audience) lines.push(`Write it to ${audience}.`)\n const submissions = aiSiteSubmissions(inputs)\n if (submissions === 'lead') {\n lines.push('Their message is a sales lead, so say that somebody will be in touch about it.')\n } else if (submissions === 'inbox') {\n lines.push('Their message has been read, so say it arrived and that a reply is coming.')\n }\n return lines\n}\n\n/**\n * The id a unit's job is named by, and so the id its draft is written under\n * (AGL-3079): the id the plan entry it builds recorded, or for the welcome\n * email the id recorded on this job's step. A unit that writes no draft — a\n * palette change — and a unit whose plan recorded no id are named by this\n * job's id and the unit's slot, the name every draft of such a plan has.\n */\nexport function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit: AiSiteUnit): string {\n const recorded =\n unit.creation?.id ?? unit.screen?.id ?? (unit.kind === 'email' ? aiRecordedJobDraftId(job, 'email') : null)\n return recorded || `${job.$id}-${unit.slot}`\n}\n\n/**\n * The job a unit is built under: this job's org, site, creator and model,\n * the kind that owns the unit, and a plan narrowed to the unit alone.\n *\n * The derived job carries NO steps and NO outputs of the scaffold's: a page\n * job's step reads its own draft and its own plan, and what the other units\n * produced is not its business. Its `$id` is the id the unit's draft is\n * written under (`aiSiteUnitJobId`), which is what a step with no id of its\n * own recorded names its draft by, so a unit re-run after its write finds its\n * own draft rather than writing a second.\n *\n * A creation unit reuses what the plan reuses less what the plan's screens\n * place themselves (AGL-3031): a component a page's section places is the\n * page's to place, and a layout or a card told to place it would be refused\n * for leaving it out.\n */\nexport function aiSiteUnitJob(\n job: AiJob,\n unit: AiSiteUnit,\n built: BuiltRefs,\n): AiJob {\n const plan = job.plan as AiJobPlan\n const labels = { ...(plan.labels ?? {}) }\n for (const entry of built.values()) labels[entry.id] = entry.label\n const inputs = parseAiSiteJobInputs(job.inputs)\n const brief =\n typeof inputs === 'string'\n ? [job.brief]\n : aiSiteBriefLines(job.brief, inputs)\n const unitPlan: AiJobPlan = {\n ...plan,\n labels,\n reuse: [...plan.reuse],\n create: [],\n screens: [],\n }\n if (unit.creation) {\n const placedByScreens = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses)),\n )\n unitPlan.reuse = plan.reuse.filter((entry) => !placedByScreens.has(entry.id))\n unitPlan.create = [\n {\n ...unit.creation,\n duplicateOf: resolved(unit.creation.duplicateOf, built),\n },\n ]\n brief.push(\n `Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`,\n )\n }\n if (unit.screen) {\n const screen = unit.screen\n // Everything the scaffold has already built is a record the site has, so\n // the page is planned against it the way a page job is planned against\n // what the site always had.\n for (const entry of built.values()) {\n unitPlan.reuse.push({\n kind: entry.kind,\n id: entry.id,\n purpose: `the ${entry.kind} this site’s pages are built on`,\n })\n }\n unitPlan.screens = [\n {\n ...screen,\n layout: resolved(screen.layout, built),\n template: resolved(screen.template, built),\n duplicateOf: resolved(screen.duplicateOf, built),\n sections: screen.sections.map((section) => ({\n ...section,\n uses: section.uses\n .map((ref) => resolved(ref, built))\n .filter((ref): ref is string => !!ref),\n })),\n },\n ]\n brief.push(\n `Build the page “${screen.title}” of this site, at ${screen.slug}.`,\n )\n }\n if (unit.kind === 'email') {\n brief.push(...aiSiteEmailBriefLines(job.inputs))\n }\n return {\n ...job,\n $id: aiSiteUnitJobId(job, unit),\n kind: unit.jobKind,\n steps: [],\n outputs: [],\n plan: unitPlan,\n // A theme change on a site with no theme of its own is a new palette.\n ...(unit.kind === 'theme'\n ? { inputs: { ...job.inputs, mode: 'create' } }\n : {}),\n // The email step reads the kind off the inputs it is handed, so a job the\n // scaffold composed says which kind it is exactly as a member's own does.\n ...(unit.kind === 'email'\n ? { inputs: { ...job.inputs, emailType: AI_SITE_EMAIL_TYPE } }\n : {}),\n brief: brief.join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/**\n * A scaffold is admitted with inputs that read, for a site of the job's own\n * org, and only where this deployment has loaded the step that builds a page:\n * a scaffold whose pages nothing can build is not a scaffold.\n */\nexport const aiSiteJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiSiteJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) {\n return {\n status: 400,\n error: 'Open the site the scaffold is for before starting the job',\n }\n }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId)\n return { status: 404, error: 'Unknown site' }\n if (!aiJobStepRunnerFor('page')) {\n return { status: 400, error: AI_SITE_NO_PAGE_STEP_COPY }\n }\n return null\n}\n\n/** What one delegated pass came to: the unit's spend as the job records it, and whether the unit is built. */\nexport interface AiJobUnitPass {\n outcome: AiJobStepOutcome\n /** The unit reported what it built and asked for no pass of its own. */\n built: boolean\n}\n\n/**\n * One delegated pass (AGL-2911, AGL-3031): the unit handed to the runner of\n * the kind that owns it, under the job derived for it, and its outcome as the\n * delegating job records it. A delegate proposes a plan for its own job,\n * which does not exist, so no plan is carried back. A unit that stopped for a\n * person, refused or failed is the delegating job's stop; a unit that asked\n * for another pass keeps its turn; and a unit that finished without reporting\n * what it built fails with `emptyCopy`, since another pass would ask the same\n * thing again. The tokens it spent are on the bill either way.\n */\nexport async function aiRunJobUnit(\n context: AiJobStepContext,\n input: {\n unit: AiSiteUnit\n units: readonly AiSiteUnit[]\n runner: AiJobStepRunner\n emptyCopy: string\n },\n): Promise<AiJobUnitPass> {\n const { job } = context\n const outputs = job.outputs ?? []\n const outcome = await input.runner({\n ...context,\n job: aiSiteUnitJob(job, input.unit, aiSiteBuiltRefs(input.units, outputs)),\n })\n const spent: AiJobStepOutcome = {\n outputs: outcome.outputs,\n usage: outcome.usage,\n estCostUsd: outcome.estCostUsd,\n model: outcome.model,\n stopReason: outcome.stopReason,\n ...(outcome.effort ? { effort: outcome.effort } : {}),\n ...(outcome.refused ? { refused: true } : {}),\n ...(outcome.failure ? { failure: outcome.failure } : {}),\n ...(outcome.review ? { review: outcome.review } : {}),\n }\n if (spent.review || spent.refused || spent.failure) return { outcome: spent, built: false }\n if (outcome.continue) return { outcome: { ...spent, continue: true }, built: false }\n if (!outcome.outputs.length) return { outcome: { ...spent, failure: input.emptyCopy }, built: false }\n return { outcome: spent, built: true }\n}\n\nexport interface AiJobSiteStepDeps {\n /** The runner registry; specs hand in a fake for a kind they drive. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobSiteStep(\n deps: AiJobSiteStepDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n // The scaffold asks no model of its own. What it names where it spends\n // nothing is the model its plan ran on, which is the one exchange this\n // job has already had of its own.\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const plan = aiConfirmedPlan(job)\n if (!plan)\n return { ...aiUnspentOutcome(model), failure: AI_SITE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation once it can read\n // one; a plan confirmed before a site changed still stops here, unspent.\n const refusal = aiSitePlanRefusal(plan)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (typeof inputs === 'string')\n return { ...aiUnspentOutcome(model), failure: inputs }\n\n // A kind this deployment has not loaded is not among the units at all, so\n // a scaffold owes only what something can build.\n const units = aiSiteJobUnits(plan, {\n welcomeEmail: inputs.welcomeEmail,\n }).filter((unit) => runnerFor(unit.jobKind))\n const outputs = job.outputs ?? []\n const pending = aiSitePendingUnits(units, outputs)\n if (!pending.length) return aiUnspentOutcome(model)\n const unit = pending[0]\n const runner = runnerFor(unit.jobKind)\n if (!runner) return aiUnspentOutcome(model)\n\n const pass = await aiRunJobUnit(context, {\n unit,\n units,\n runner,\n emptyCopy: AI_SITE_UNIT_EMPTY_COPY,\n })\n // The site's own listing rides out beside the first unit's output, once\n // (AGL-2918): it is derived from the answers rather than generated, so it\n // is ready before anything is built and costs the pass nothing.\n const outcome: AiJobStepOutcome = {\n ...pass.outcome,\n outputs: [...aiSiteSeoOutputs(job), ...pass.outcome.outputs],\n }\n // A built unit continues the scaffold while units remain after it.\n return pass.built && pending.length > 1 ? { ...outcome, continue: true } : outcome\n }\n}\n\nexport const runAiJobSiteStep = createAiJobSiteStep()\n\n/**\n * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no\n * model itself: each pass hands one unit to the step registered for its kind,\n * under the job derived for it, so it needs what that step registers for that\n * job — a palette change's or a layout's time for those units, a page pass's\n * for a page. A scaffold with nothing left to build, or with no plan it can\n * build, spends nothing, and a page pass's time covers it.\n */\nexport function aiSiteJobRunMinimumMs(job: AiJob): number {\n const plan = aiConfirmedPlan(job)\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (!plan || typeof inputs === 'string') return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiSiteJobUnits(plan, { welcomeEmail: inputs.welcomeEmail }).filter((unit) =>\n aiJobStepRunnerFor(unit.jobKind),\n )\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * Registers the site scaffold with the least time a pass needs — every\n * scaffold builds pages, and a page pass needs the least of any unit — and the\n * time its next pass needs, the passes a whole site's plan may take, and the\n * check a site job passes before it is created or resumed.\n */\nexport function registerAiSiteJob(): void {\n registerAiJobStep('site', runAiJobSiteStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiSiteJobRunMinimumMs,\n })\n registerAiJobStepPasses('site', AI_SITE_MAX_PASSES)\n registerAiJobAdmission('site', aiSiteJobAdmission)\n}\n"],"names":["resolveOrgIdForHost","isAiPlanNewRef","AI_SITE_EMAIL_TYPE","AI_SITE_MAX_SECTIONS","AI_SITE_PAGES","aiSitePlanRefusal","aiSiteSubmissions","aiSiteWords","parseAiSiteJobInputs","AI_SITE_SEO_OUTPUT_ID","aiSiteSeoProposalForInputs","aiModelForStep","registerAiJobAdmission","aiRecordedJobDraftId","aiConfirmedPlan","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","UNIT_KINDS","theme","jobKind","resource","layout","form","component","page","email","CREATION_UNIT_KINDS","aiCreationUnit","creation","slot","kind","label","name","CREATION_UNITS","unit","create","AI_SITE_NO_PLAN_COPY","AI_SITE_NO_PAGE_STEP_COPY","AI_SITE_UNIT_EMPTY_COPY","AI_SITE_MAX_PASSES","max","length","aiSiteJobUnits","plan","options","units","find","entry","push","screens","forEach","screen","index","title","welcomeEmail","aiSitePendingUnits","outputs","done","Map","output","set","get","filter","left","aiSiteSeoOutputs","job","some","id","proposal","inputs","hostId","aiSiteBuiltRefs","byResource","rows","built","row","shift","toLowerCase","resolved","ref","slice","indexOf","trim","aiSiteBriefLines","brief","lines","site","businessName","businessType","audience","city","brand","Boolean","join","aiSiteEmailBriefLines","submissions","aiSiteUnitJobId","recorded","$id","aiSiteUnitJob","labels","values","unitPlan","reuse","placedByScreens","Set","flatMap","sections","section","uses","has","duplicateOf","why","purpose","template","map","slug","steps","mode","emailType","aiSiteJobAdmission","context","status","error","owner","orgId","aiRunJobUnit","input","outcome","runner","spent","usage","estCostUsd","model","stopReason","effort","refused","failure","review","continue","emptyCopy","createAiJobSiteStep","deps","runnerFor","modelFor","refusal","pending","pass","runAiJobSiteStep","aiSiteJobRunMinimumMs","registerAiSiteJob","minimumMs","minimumMsFor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n isAiPlanNewRef,\n type AiBuildPlanCreate,\n type AiBuildPlanCreateKind,\n type AiBuildPlanScreen,\n} from '../model/ai-build-plan'\nimport type {\n AiJob,\n AiJobKind,\n AiJobOutput,\n AiJobOutputResource,\n AiJobPlan,\n} from '../model/ai-jobs.types'\nimport {\n AI_SITE_EMAIL_TYPE,\n AI_SITE_MAX_SECTIONS,\n AI_SITE_PAGES,\n aiSitePlanRefusal,\n aiSiteSubmissions,\n aiSiteWords,\n parseAiSiteJobInputs,\n type AiSiteJobInputs,\n} from '../model/ai-site-job'\nimport {\n AI_SITE_SEO_OUTPUT_ID,\n aiSiteSeoProposalForInputs,\n} from '../model/ai-site-start-seo'\nimport { aiModelForStep } from '../providers/routing'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport { aiRecordedJobDraftId } from './ai-job-draft-ids'\nimport { aiConfirmedPlan, aiUnspentOutcome } from './ai-job-generation'\nimport {\n AI_JOB_BRIEF_MAX_CHARS,\n type AiJobStepContext,\n type AiJobStepOutcome,\n type AiJobStepRunner,\n} from './ai-job-text-step'\nimport { AI_JOB_PAGE_STEP_MINIMUM_MS } from './ai-job-page-budget'\nimport {\n aiJobStepRunMinimumMs,\n aiJobStepRunnerFor,\n registerAiJobStep,\n registerAiJobStepPasses,\n} from './ai-jobs'\n\n/**\n * The site scaffold (AGL-2911): the generation step of a `site` job, run once\n * a member confirmed the plan their brief produced.\n *\n * ── It builds nothing itself ─────────────────────────────────────────────\n *\n * A scaffold is a whole site: a palette suggestion, the layout its pages\n * render inside, the contact form they place, four to eight pages with their\n * search listings, and a welcome email. Every one of those is already a job\n * kind with a step of its own, so this step generates nothing and asks no\n * model anything. It works through the confirmed plan one UNIT at a time and\n * hands each unit to the runner registered for the kind that owns it — the\n * theme step for the palette, the page step for a page — under a job of that\n * kind derived from this one.\n *\n * That is why the scaffold cannot drift from the thing it scaffolds: a page\n * it builds is a page job's page, held to the same doctrine, written by the\n * same draft writer, left unpublished by the same rule. A kind whose step\n * this deployment has not loaded is simply not among the units, so nothing\n * here knows which issues have landed.\n *\n * ── One unit a pass ──────────────────────────────────────────────────────\n *\n * The step runs ONE delegated pass and asks the machine to continue, so every\n * pass is one reservation, one provider exchange and one recorded spend, and\n * the org's monthly ceiling binds a scaffold exactly as it binds a chat turn.\n * Credits running out mid-scaffold is therefore the machine's own park: the\n * job waits as `needs_input` with the meter's words, and the beat resumes it\n * where it stopped once the workspace's standing has changed. A pass needs the\n * time its unit's own step needs (`aiSiteJobRunMinimumMs`), so the beat starts\n * a palette change only with a palette change's time left.\n *\n * Where it stopped is read from the job's OUTPUTS rather than kept anywhere:\n * each unit reports exactly one output when it completes, so the units\n * already represented there are the ones already built. A pass interrupted\n * between its work and its record repeats a unit that then finds its own\n * draft and reports it, which is what every step's draft id already gives.\n *\n * ── Never published ──────────────────────────────────────────────────────\n *\n * Nothing here publishes, routes or sends: each delegated step writes the\n * unpublished draft it already wrote for a job of its own kind, and the\n * palette and navigation travel as proposals a member applies.\n *\n * ── The page job builds its creations the same way (AGL-3031) ────────────\n *\n * A page job whose plan creates a layout, forms or components builds them\n * before its page through this same machinery: the units a creation implies\n * (`aiCreationUnit`), where the job stands read from its outputs\n * (`aiSitePendingUnits`), one delegated pass a unit (`aiRunJobUnit`), and the\n * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,\n * `aiSiteUnitJob`). There is one loop, and it is the machine's.\n */\n\n/** What a scaffold's units are, in the order it builds them; a component is a page job's. */\nexport type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email'\n\nexport interface AiSiteUnit {\n kind: AiSiteUnitKind\n /** The job kind whose registered runner builds it. */\n jobKind: AiJobKind\n /** The resource the unit reports when it completes. */\n resource: AiJobOutputResource\n /** Stable within the job: what the unit's derived job and draft are addressed by. */\n slot: string\n /** The plan screen a page unit builds. */\n screen?: AiBuildPlanScreen\n /** The creation a theme, layout or form unit builds. */\n creation?: AiBuildPlanCreate\n /** What the unit is, for the derived job's brief. */\n label: string\n}\n\n/** The job kind and output resource each unit is built through. */\nconst UNIT_KINDS: Record<\n AiSiteUnitKind,\n { jobKind: AiJobKind; resource: AiJobOutputResource }\n> = {\n theme: { jobKind: 'theme', resource: 'theme' },\n layout: { jobKind: 'layout', resource: 'layout' },\n form: { jobKind: 'form', resource: 'form' },\n component: { jobKind: 'component', resource: 'reusableComponent' },\n page: { jobKind: 'page', resource: 'screen' },\n email: { jobKind: 'email', resource: 'emailScreen' },\n}\n\n/** The unit kind a creation is built as, where a unit builds it. */\nconst CREATION_UNIT_KINDS: Partial<Record<AiBuildPlanCreateKind, AiSiteUnitKind>> = {\n 'theme-change': 'theme',\n layout: 'layout',\n form: 'form',\n component: 'component',\n}\n\n/**\n * The unit one creation is built as, under the slot its derived job and draft\n * are addressed by; `null` for a creation no unit builds.\n */\nexport function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null {\n const kind = CREATION_UNIT_KINDS[creation.kind]\n if (!kind) return null\n return { kind, ...UNIT_KINDS[kind], slot, creation, label: creation.name }\n}\n\n/** The creation kinds a unit is built from, by unit kind. */\nconst CREATION_UNITS: Array<{\n unit: AiSiteUnitKind\n create: AiBuildPlanCreateKind\n}> = [\n { unit: 'theme', create: 'theme-change' },\n { unit: 'layout', create: 'layout' },\n { unit: 'form', create: 'form' },\n]\n\nexport const AI_SITE_NO_PLAN_COPY =\n 'This site job has no confirmed plan to build.'\n\n/** What a scaffold answers where the step that builds a page is not loaded. */\nexport const AI_SITE_NO_PAGE_STEP_COPY =\n 'Building a whole site is not available yet.'\n\n/** A unit that finished without reporting what it built; the scaffold cannot go on. */\nexport const AI_SITE_UNIT_EMPTY_COPY =\n 'Part of this site could not be built. Try the site brief again.'\n\n/**\n * The most passes a scaffold's step may take: the largest plan it admits —\n * eight pages of eight sections, its three creations and a welcome email —\n * with nothing to spare, so a runner that never finishes is still bounded\n * while a real site is not.\n */\nexport const AI_SITE_MAX_PASSES =\n AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + 1\n\n/**\n * The units a plan implies, in build order: the palette first, because a\n * member reads it while the pages are still building; then the layout every\n * page renders inside and the form they place, because a page binds both by\n * id and so needs them to exist; then the pages; then the welcome email,\n * which is about the site rather than part of it.\n */\nexport function aiSiteJobUnits(\n plan: Pick<AiJobPlan, 'create' | 'screens'>,\n options: { welcomeEmail?: boolean } = {},\n): AiSiteUnit[] {\n const units: AiSiteUnit[] = []\n for (const { unit, create } of CREATION_UNITS) {\n const creation = plan.create.find((entry) => entry.kind === create)\n if (!creation) continue\n units.push({\n kind: unit,\n ...UNIT_KINDS[unit],\n slot: unit[0],\n creation,\n label: creation.name,\n })\n }\n plan.screens.forEach((screen, index) => {\n units.push({\n kind: 'page',\n ...UNIT_KINDS.page,\n slot: `p${index}`,\n screen,\n label: screen.title,\n })\n })\n if (options.welcomeEmail) {\n units.push({\n kind: 'email',\n ...UNIT_KINDS.email,\n slot: 'e',\n label: 'Welcome email',\n })\n }\n return units\n}\n\n/**\n * The units a scaffold still owes after the outputs recorded so far. Each\n * unit reports one output of its own resource when it completes, so the\n * outputs already recorded are consumed against the units in build order.\n */\nexport function aiSitePendingUnits(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource'>[],\n): AiSiteUnit[] {\n const done = new Map<AiJobOutputResource, number>()\n for (const output of outputs) {\n done.set(output.resource, (done.get(output.resource) ?? 0) + 1)\n }\n return units.filter((unit) => {\n const left = done.get(unit.resource) ?? 0\n if (left <= 0) return true\n done.set(unit.resource, left - 1)\n return false\n })\n}\n\n/**\n * The site's own search listing as the scaffold reports it (AGL-2918), or\n * nothing when the job already reported one or its inputs describe no site.\n *\n * It costs nothing and asks no model: the values are arithmetic on the\n * answers (`aiSiteSeoProposal`), so they ride out on the first pass that\n * builds anything rather than waiting for a pass of their own. Its resource\n * is `seo`, which no unit reports, so it cannot be mistaken for a unit's\n * output by `aiSitePendingUnits` and cannot move where the scaffold thinks\n * it is.\n */\nexport function aiSiteSeoOutputs(job: AiJob): AiJobOutput[] {\n const outputs = job.outputs ?? []\n if (outputs.some((output) => output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {\n return []\n }\n const proposal = aiSiteSeoProposalForInputs(job.inputs)\n if (!proposal) return []\n return [\n {\n resource: 'seo',\n id: AI_SITE_SEO_OUTPUT_ID,\n hostId: job.hostId ?? null,\n label: 'The site’s search title and description',\n proposal: proposal as unknown as Record<string, unknown>,\n },\n ]\n}\n\n/** A record an earlier unit created, as a later one references it. */\nexport interface AiSiteBuiltRef {\n id: string\n label: string\n /** Only what becomes a record a page can name; a palette change is not one. */\n kind: 'layout' | 'form' | 'component'\n}\n\n/**\n * What the scaffold has already created, as the plan's `new:<name>`\n * references resolve to: the id of the record each completed creation wrote.\n * A page unit is told about the layout and the form by id, exactly as a page\n * job is told about records the site already had.\n */\nexport function aiSiteBuiltRefs(\n units: readonly AiSiteUnit[],\n outputs: readonly Pick<AiJobOutput, 'resource' | 'id' | 'label'>[],\n): Map<string, AiSiteBuiltRef> {\n const byResource = new Map<\n AiJobOutputResource,\n Array<Pick<AiJobOutput, 'id' | 'label'>>\n >()\n for (const output of outputs) {\n const rows = byResource.get(output.resource) ?? []\n rows.push(output)\n byResource.set(output.resource, rows)\n }\n const built = new Map<string, AiSiteBuiltRef>()\n for (const unit of units) {\n const rows = byResource.get(unit.resource)\n const row = rows?.shift()\n if (!row || !unit.creation) continue\n // A theme change is a proposal with no record to reference; a layout, a\n // form and a component become ids a page can name.\n if (\n unit.creation.kind !== 'layout' &&\n unit.creation.kind !== 'form' &&\n unit.creation.kind !== 'component'\n ) {\n continue\n }\n built.set(unit.creation.name.toLowerCase(), {\n id: row.id,\n label: row.label || unit.creation.name,\n kind: unit.creation.kind,\n })\n }\n return built\n}\n\ntype BuiltRefs = ReturnType<typeof aiSiteBuiltRefs>\n\n/** A plan reference as the unit's own job reads it: an id the site now has, or nothing. */\nexport function aiSiteResolvedRef(ref: string | null, built: BuiltRefs): string | null {\n if (!ref) return null\n if (!isAiPlanNewRef(ref)) return ref\n return (\n built.get(\n ref\n .slice(ref.indexOf(':') + 1)\n .trim()\n .toLowerCase(),\n )?.id ?? null\n )\n}\n\n/**\n * The site's own words, as every unit's brief carries them.\n *\n * The audience is here rather than left to the brief's prose (AGL-2918). A\n * guided start writes it into the brief itself, but an agency batch's brief\n * is a member's own sentence and may never mention it, and a theme, a layout\n * or a form asked to serve nobody in particular serves nobody in particular.\n */\nexport function aiSiteBriefLines(\n brief: string,\n inputs: AiSiteJobInputs,\n): string[] {\n const lines = [brief.trim()]\n const site = [\n inputs.businessName ? `name: ${inputs.businessName}` : '',\n `business: ${inputs.businessType}`,\n inputs.audience ? `for: ${inputs.audience}` : '',\n inputs.city ? `city: ${inputs.city}` : '',\n inputs.brand ? `brand: ${inputs.brand}` : '',\n ].filter(Boolean)\n lines.push(`Site — ${site.join('; ')}.`)\n return lines\n}\n\n/**\n * What the welcome email is told (AGL-2918), beyond that it is a welcome\n * email.\n *\n * The scaffold's email unit used to carry one sentence, and a yes/no toggle\n * decided whether it ran — so the draft was a welcome email for a business\n * in general, to nobody in particular, about nothing that had happened. The\n * two answers it is missing are who wrote in and what now becomes of what\n * they wrote, and the second of those is the person's own routing answer, so\n * the email and the form it acknowledges cannot say different things.\n *\n * ⛔ A DRAFT either way. Nothing here sends, schedules or enrolls anybody:\n * the email step writes an unpublished email design and stops.\n */\nexport function aiSiteEmailBriefLines(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): string[] {\n const lines = [\n 'Write the welcome email this site sends someone who gets in touch.',\n ]\n const { audience } = aiSiteWords(inputs)\n if (audience) lines.push(`Write it to ${audience}.`)\n const submissions = aiSiteSubmissions(inputs)\n if (submissions === 'lead') {\n lines.push('Their message is a sales lead, so say that somebody will be in touch about it.')\n } else if (submissions === 'inbox') {\n lines.push('Their message has been read, so say it arrived and that a reply is coming.')\n }\n return lines\n}\n\n/**\n * The id a unit's job is named by, and so the id its draft is written under\n * (AGL-3079): the id the plan entry it builds recorded, or for the welcome\n * email the id recorded on this job's step. A unit that writes no draft — a\n * palette change — and a unit whose plan recorded no id are named by this\n * job's id and the unit's slot, the name every draft of such a plan has.\n */\nexport function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit: AiSiteUnit): string {\n const recorded =\n unit.creation?.id ?? unit.screen?.id ?? (unit.kind === 'email' ? aiRecordedJobDraftId(job, 'email') : null)\n return recorded || `${job.$id}-${unit.slot}`\n}\n\n/**\n * The job a unit is built under: this job's org, site, creator and model,\n * the kind that owns the unit, and a plan narrowed to the unit alone.\n *\n * The derived job carries NO steps and NO outputs of the scaffold's: a page\n * job's step reads its own draft and its own plan, and what the other units\n * produced is not its business. Its `$id` is the id the unit's draft is\n * written under (`aiSiteUnitJobId`), which is what a step with no id of its\n * own recorded names its draft by, so a unit re-run after its write finds its\n * own draft rather than writing a second.\n *\n * A creation unit reuses what the plan reuses less what the plan's screens\n * place themselves (AGL-3031): a component a page's section places is the\n * page's to place, and a layout or a card told to place it would be refused\n * for leaving it out.\n */\nexport function aiSiteUnitJob(\n job: AiJob,\n unit: AiSiteUnit,\n built: BuiltRefs,\n): AiJob {\n const plan = job.plan as AiJobPlan\n const labels = { ...(plan.labels ?? {}) }\n for (const entry of built.values()) labels[entry.id] = entry.label\n const inputs = parseAiSiteJobInputs(job.inputs)\n const brief =\n typeof inputs === 'string'\n ? [job.brief]\n : aiSiteBriefLines(job.brief, inputs)\n const unitPlan: AiJobPlan = {\n ...plan,\n labels,\n reuse: [...plan.reuse],\n create: [],\n screens: [],\n }\n if (unit.creation) {\n const placedByScreens = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses)),\n )\n unitPlan.reuse = plan.reuse.filter((entry) => !placedByScreens.has(entry.id))\n unitPlan.create = [\n {\n ...unit.creation,\n duplicateOf: aiSiteResolvedRef(unit.creation.duplicateOf, built),\n },\n ]\n brief.push(\n `Build the ${unit.creation.kind} “${unit.creation.name}”: ${unit.creation.why}`,\n )\n }\n if (unit.screen) {\n const screen = unit.screen\n // Everything the scaffold has already built is a record the site has, so\n // the page is planned against it the way a page job is planned against\n // what the site always had.\n for (const entry of built.values()) {\n unitPlan.reuse.push({\n kind: entry.kind,\n id: entry.id,\n purpose: `the ${entry.kind} this site’s pages are built on`,\n })\n }\n unitPlan.screens = [\n {\n ...screen,\n layout: aiSiteResolvedRef(screen.layout, built),\n template: aiSiteResolvedRef(screen.template, built),\n duplicateOf: aiSiteResolvedRef(screen.duplicateOf, built),\n sections: screen.sections.map((section) => ({\n ...section,\n uses: section.uses\n .map((ref) => aiSiteResolvedRef(ref, built))\n .filter((ref): ref is string => !!ref),\n })),\n },\n ]\n brief.push(\n `Build the page “${screen.title}” of this site, at ${screen.slug}.`,\n )\n }\n if (unit.kind === 'email') {\n brief.push(...aiSiteEmailBriefLines(job.inputs))\n }\n return {\n ...job,\n $id: aiSiteUnitJobId(job, unit),\n kind: unit.jobKind,\n steps: [],\n outputs: [],\n plan: unitPlan,\n // A theme change on a site with no theme of its own is a new palette.\n ...(unit.kind === 'theme'\n ? { inputs: { ...job.inputs, mode: 'create' } }\n : {}),\n // The email step reads the kind off the inputs it is handed, so a job the\n // scaffold composed says which kind it is exactly as a member's own does.\n ...(unit.kind === 'email'\n ? { inputs: { ...job.inputs, emailType: AI_SITE_EMAIL_TYPE } }\n : {}),\n brief: brief.join('\\n').slice(0, AI_JOB_BRIEF_MAX_CHARS),\n }\n}\n\n/**\n * A scaffold is admitted with inputs that read, for a site of the job's own\n * org, and only where this deployment has loaded the step that builds a page:\n * a scaffold whose pages nothing can build is not a scaffold.\n */\nexport const aiSiteJobAdmission: AiJobAdmission = async (context) => {\n const inputs = parseAiSiteJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (!context.hostId) {\n return {\n status: 400,\n error: 'Open the site the scaffold is for before starting the job',\n }\n }\n const owner = await resolveOrgIdForHost(context.hostId)\n if (!owner || owner !== context.orgId)\n return { status: 404, error: 'Unknown site' }\n if (!aiJobStepRunnerFor('page')) {\n return { status: 400, error: AI_SITE_NO_PAGE_STEP_COPY }\n }\n return null\n}\n\n/** What one delegated pass came to: the unit's spend as the job records it, and whether the unit is built. */\nexport interface AiJobUnitPass {\n outcome: AiJobStepOutcome\n /** The unit reported what it built and asked for no pass of its own. */\n built: boolean\n}\n\n/**\n * One delegated pass (AGL-2911, AGL-3031): the unit handed to the runner of\n * the kind that owns it, under the job derived for it, and its outcome as the\n * delegating job records it. A delegate proposes a plan for its own job,\n * which does not exist, so no plan is carried back. A unit that stopped for a\n * person, refused or failed is the delegating job's stop; a unit that asked\n * for another pass keeps its turn; and a unit that finished without reporting\n * what it built fails with `emptyCopy`, since another pass would ask the same\n * thing again. The tokens it spent are on the bill either way.\n */\nexport async function aiRunJobUnit(\n context: AiJobStepContext,\n input: {\n unit: AiSiteUnit\n units: readonly AiSiteUnit[]\n runner: AiJobStepRunner\n emptyCopy: string\n },\n): Promise<AiJobUnitPass> {\n const { job } = context\n const outputs = job.outputs ?? []\n const outcome = await input.runner({\n ...context,\n job: aiSiteUnitJob(job, input.unit, aiSiteBuiltRefs(input.units, outputs)),\n })\n const spent: AiJobStepOutcome = {\n outputs: outcome.outputs,\n usage: outcome.usage,\n estCostUsd: outcome.estCostUsd,\n model: outcome.model,\n stopReason: outcome.stopReason,\n ...(outcome.effort ? { effort: outcome.effort } : {}),\n ...(outcome.refused ? { refused: true } : {}),\n ...(outcome.failure ? { failure: outcome.failure } : {}),\n ...(outcome.review ? { review: outcome.review } : {}),\n }\n if (spent.review || spent.refused || spent.failure) return { outcome: spent, built: false }\n if (outcome.continue) return { outcome: { ...spent, continue: true }, built: false }\n if (!outcome.outputs.length) return { outcome: { ...spent, failure: input.emptyCopy }, built: false }\n return { outcome: spent, built: true }\n}\n\nexport interface AiJobSiteStepDeps {\n /** The runner registry; specs hand in a fake for a kind they drive. */\n runnerFor?: typeof aiJobStepRunnerFor\n}\n\nexport function createAiJobSiteStep(\n deps: AiJobSiteStepDeps = {},\n): AiJobStepRunner {\n const runnerFor = deps.runnerFor ?? aiJobStepRunnerFor\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n // The scaffold asks no model of its own. What it names where it spends\n // nothing is the model its plan ran on, which is the one exchange this\n // job has already had of its own.\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const plan = aiConfirmedPlan(job)\n if (!plan)\n return { ...aiUnspentOutcome(model), failure: AI_SITE_NO_PLAN_COPY }\n // The resume door refuses such a plan at confirmation once it can read\n // one; a plan confirmed before a site changed still stops here, unspent.\n const refusal = aiSitePlanRefusal(plan)\n if (refusal) return { ...aiUnspentOutcome(model), failure: refusal }\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (typeof inputs === 'string')\n return { ...aiUnspentOutcome(model), failure: inputs }\n\n // A kind this deployment has not loaded is not among the units at all, so\n // a scaffold owes only what something can build.\n const units = aiSiteJobUnits(plan, {\n welcomeEmail: inputs.welcomeEmail,\n }).filter((unit) => runnerFor(unit.jobKind))\n const outputs = job.outputs ?? []\n const pending = aiSitePendingUnits(units, outputs)\n if (!pending.length) return aiUnspentOutcome(model)\n const unit = pending[0]\n const runner = runnerFor(unit.jobKind)\n if (!runner) return aiUnspentOutcome(model)\n\n const pass = await aiRunJobUnit(context, {\n unit,\n units,\n runner,\n emptyCopy: AI_SITE_UNIT_EMPTY_COPY,\n })\n // The site's own listing rides out beside the first unit's output, once\n // (AGL-2918): it is derived from the answers rather than generated, so it\n // is ready before anything is built and costs the pass nothing.\n const outcome: AiJobStepOutcome = {\n ...pass.outcome,\n outputs: [...aiSiteSeoOutputs(job), ...pass.outcome.outputs],\n }\n // A built unit continues the scaffold while units remain after it.\n return pass.built && pending.length > 1 ? { ...outcome, continue: true } : outcome\n }\n}\n\nexport const runAiJobSiteStep = createAiJobSiteStep()\n\n/**\n * The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no\n * model itself: each pass hands one unit to the step registered for its kind,\n * under the job derived for it, so it needs what that step registers for that\n * job — a palette change's or a layout's time for those units, a page pass's\n * for a page. A scaffold with nothing left to build, or with no plan it can\n * build, spends nothing, and a page pass's time covers it.\n */\nexport function aiSiteJobRunMinimumMs(job: AiJob): number {\n const plan = aiConfirmedPlan(job)\n const inputs = parseAiSiteJobInputs(job.inputs)\n if (!plan || typeof inputs === 'string') return AI_JOB_PAGE_STEP_MINIMUM_MS\n const units = aiSiteJobUnits(plan, { welcomeEmail: inputs.welcomeEmail }).filter((unit) =>\n aiJobStepRunnerFor(unit.jobKind),\n )\n const outputs = job.outputs ?? []\n const [unit] = aiSitePendingUnits(units, outputs)\n if (!unit) return AI_JOB_PAGE_STEP_MINIMUM_MS\n return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiSiteBuiltRefs(units, outputs)))\n}\n\n/**\n * Registers the site scaffold with the least time a pass needs — every\n * scaffold builds pages, and a page pass needs the least of any unit — and the\n * time its next pass needs, the passes a whole site's plan may take, and the\n * check a site job passes before it is created or resumed.\n */\nexport function registerAiSiteJob(): void {\n registerAiJobStep('site', runAiJobSiteStep, {\n minimumMs: AI_JOB_PAGE_STEP_MINIMUM_MS,\n minimumMsFor: aiSiteJobRunMinimumMs,\n })\n registerAiJobStepPasses('site', AI_SITE_MAX_PASSES)\n registerAiJobAdmission('site', aiSiteJobAdmission)\n}\n"],"names":["resolveOrgIdForHost","isAiPlanNewRef","AI_SITE_EMAIL_TYPE","AI_SITE_MAX_SECTIONS","AI_SITE_PAGES","aiSitePlanRefusal","aiSiteSubmissions","aiSiteWords","parseAiSiteJobInputs","AI_SITE_SEO_OUTPUT_ID","aiSiteSeoProposalForInputs","aiModelForStep","registerAiJobAdmission","aiRecordedJobDraftId","aiConfirmedPlan","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_JOB_PAGE_STEP_MINIMUM_MS","aiJobStepRunMinimumMs","aiJobStepRunnerFor","registerAiJobStep","registerAiJobStepPasses","UNIT_KINDS","theme","jobKind","resource","layout","form","component","page","email","CREATION_UNIT_KINDS","aiCreationUnit","creation","slot","kind","label","name","CREATION_UNITS","unit","create","AI_SITE_NO_PLAN_COPY","AI_SITE_NO_PAGE_STEP_COPY","AI_SITE_UNIT_EMPTY_COPY","AI_SITE_MAX_PASSES","max","length","aiSiteJobUnits","plan","options","units","find","entry","push","screens","forEach","screen","index","title","welcomeEmail","aiSitePendingUnits","outputs","done","Map","output","set","get","filter","left","aiSiteSeoOutputs","job","some","id","proposal","inputs","hostId","aiSiteBuiltRefs","byResource","rows","built","row","shift","toLowerCase","aiSiteResolvedRef","ref","slice","indexOf","trim","aiSiteBriefLines","brief","lines","site","businessName","businessType","audience","city","brand","Boolean","join","aiSiteEmailBriefLines","submissions","aiSiteUnitJobId","recorded","$id","aiSiteUnitJob","labels","values","unitPlan","reuse","placedByScreens","Set","flatMap","sections","section","uses","has","duplicateOf","why","purpose","template","map","slug","steps","mode","emailType","aiSiteJobAdmission","context","status","error","owner","orgId","aiRunJobUnit","input","outcome","runner","spent","usage","estCostUsd","model","stopReason","effort","refused","failure","review","continue","emptyCopy","createAiJobSiteStep","deps","runnerFor","modelFor","refusal","pending","pass","runAiJobSiteStep","aiSiteJobRunMinimumMs","registerAiSiteJob","minimumMs","minimumMsFor"],"mappings":"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"}
@@ -35,8 +35,9 @@ import type { AiJobStepRunner } from './ai-job-text-step';
35
35
  * picker offers for that page; a link or media prop may hold one of those
36
36
  * tokens whole, which the palette validator admits only because this door
37
37
  * names them. The door adds one check of its own through `extend` (rule 8):
38
- * no token the page does not fill, an h1 bound to the subject's title, and no
39
- * block that fills itself only on another subject's page.
38
+ * no token the page does not fill, an h1 bound to the subject's title, no
39
+ * block that fills itself only on another subject's page, and no address,
40
+ * slug or timestamp printed in its copy.
40
41
  *
41
42
  * The model is shown the platform's starter pages as examples, in a cached
42
43
  * block (`runtime/ai-template-examples.ts`), and writes against them.
@@ -72,13 +73,14 @@ export declare function readAiTemplateCollection(firestore: FirebaseFirestore.Fi
72
73
  export declare function aiTemplateDraftName(definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null, planned: string | null | undefined): string;
73
74
  /** The address Use template offers for the page it creates. */
74
75
  export declare function aiTemplateDraftSlug(definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null): string | null;
75
- /** The template job as the generation's user turn. */
76
- export declare function aiJobTemplatePrompt(job: Pick<AiJob, 'brief'>, definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null, plan: AiJobPlan | null, name: string): string;
76
+ /** The template job as the generation's user turn, with the template a short copy would have started from (AGL-3143 §14). */
77
+ export declare function aiJobTemplatePrompt(job: Pick<AiJob, 'brief'>, definition: AiTemplateSubjectDefinition, collection: AiTemplateCollection | null, plan: AiJobPlan | null, name: string, source?: readonly string[]): string;
77
78
  /**
78
79
  * The template door's check on a tree the doctrine admitted (rule 8): every
79
- * token is one the page fills, the h1 binds the subject's title, and no block
80
+ * token is one the page fills, the h1 binds the subject's title, no block
80
81
  * that fills itself on another subject's page is placed — except inside a
81
- * Collection Entries card, where each fills from the card's own entry.
82
+ * Collection Entries card, where each fills from the card's own entry — and
83
+ * no copy prints an address, a slug or a timestamp.
82
84
  */
83
85
  export declare function aiTemplateBindingCheck(definition: AiTemplateSubjectDefinition): (tree: AiValidatedTree) => AiDoctrineViolation[];
84
86
  export interface AiTemplateJobAdmissionDeps {
@@ -121,6 +123,7 @@ export declare function aiCopiedTemplateReview(firestore: FirebaseFirestore.Fire
121
123
  definition: AiTemplateSubjectDefinition;
122
124
  }): Promise<AiJobReview | null>;
123
125
  export declare function createAiJobTemplateStep(deps?: AiJobTemplateStepDeps): AiJobStepRunner;
126
+ /** The template step, building the components and forms its plan creates before the template (AGL-3143 §15). */
124
127
  export declare const runAiJobTemplateStep: AiJobStepRunner;
125
128
  /** Registers the template step and the check a template job passes before it is created or resumed. */
126
129
  export declare function registerAiTemplateJob(): void;