@aglyn/plugins-ai 1.0.0-beta.221 → 1.0.0-beta.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-job-plan.component.js +1 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/jobs/ai-job-crm-step.d.ts +44 -3
- package/src/lib/jobs/ai-job-crm-step.js +298 -8
- package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +33 -1
- package/src/lib/jobs/ai-job-page-sections.js +55 -1
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +20 -11
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +6 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +52 -15
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +6 -0
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +8 -7
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +2 -2
- package/src/lib/jobs/ai-workflow-records.js +2 -2
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.js +1 -0
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-free-page-recording.js +2 -0
- package/src/lib/jobs/fixtures/ai-free-page-recording.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-page-briefs.js +2 -0
- package/src/lib/jobs/fixtures/ai-page-briefs.js.map +1 -1
- package/src/lib/model/ai-automation-draft.d.ts +4 -4
- package/src/lib/model/ai-automation-draft.js +15 -10
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-automation-format.d.ts +139 -0
- package/src/lib/model/ai-automation-format.js +42 -0
- package/src/lib/model/ai-automation-format.js.map +1 -0
- package/src/lib/model/ai-automation-outline.d.ts +4 -4
- package/src/lib/model/ai-automation-outline.js +10 -8
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +39 -2
- package/src/lib/model/ai-build-plan.js +80 -3
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-workflow-job.js +2 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +23 -0
- package/src/lib/providers/catalog.js +29 -1
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +8 -0
- package/src/lib/runtime/ai-doctrine-validators.js +34 -3
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +1 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +2 -2
- package/src/lib/runtime/ai-eval.js +3 -3
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +8 -1
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +5 -2
- package/src/lib/runtime/seo-audit.js +16 -7
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +10 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +31 -5
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +5 -1
- package/src/lib/tools/ai-seo-tool.js +6 -2
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +15 -4
- package/src/lib/tools/ai-workflow-tool.js +70 -12
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage-axes.js +14 -1
- package/src/lib/usage-axes.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-workflow-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ACTION_MAX_CONDITIONS,\n ACTION_MAX_STEPS,\n CONTACT_TAG_MAX_LENGTH,\n FLOW_TIMED_OUT_FIELD,\n FLOW_WAIT_MAX_MINUTES,\n HOST_ACTION_STEP_LABELS,\n} from '@aglyn/aglyn/app-utils/actions'\nimport { CONTACT_LIFECYCLE_STAGES, CRM_TASK_MAX_DUE_DAYS } from '@aglyn/aglyn/app-utils/crm'\nimport { HOST_EVENT_PAYLOAD_KEYS, type HostEventType } from '@aglyn/aglyn/app-utils/host-events'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiAutomationDraft,\n aiAutomationDraftNote,\n type AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport {\n aiActionOutline,\n aiActionRecordKinds,\n aiRunOutline,\n aiWorkflowExplanationText,\n aiWorkflowOutline,\n type AiRunRecord,\n} from '../model/ai-automation-outline'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport {\n AI_AUTOMATION_NEED_LABELS,\n AI_AUTOMATION_RESOURCE,\n AI_AUTOMATION_STEP_NEEDS,\n AI_AUTOMATION_STEP_TYPES,\n AI_AUTOMATION_TRIGGER_NEEDS,\n AI_AUTOMATION_TRIGGERS,\n AI_AUTOMATION_UNSUPPORTED_COPY,\n AI_WORKFLOW_GONE_COPY,\n AI_WORKFLOW_NO_DRAFT_COPY,\n AI_WORKFLOW_NO_EXPLANATION_COPY,\n AI_WORKFLOW_NO_SITE_COPY,\n AI_WORKFLOW_RUN_GONE_COPY,\n AI_WORKFLOW_RUN_NOT_FAILED_COPY,\n AI_WORKFLOW_SAVE_FAILURE_COPY,\n AI_WORKFLOW_UNAVAILABLE_COPY,\n aiAutomationCapabilities,\n aiAutomationTriggerLabel,\n parseAiWorkflowJobInputs,\n type AiAutomationCapabilities,\n type AiAutomationStepType,\n} from '../model/ai-workflow-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n runValidatedGeneration,\n type AiCustomGenerationInput,\n type AiGenerationCheck,\n} from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport {\n AI_AUTOMATION_TOOL_NAME,\n AI_WORKFLOW_EXPLANATION_TOOL_NAME,\n aiAutomationTool,\n aiWorkflowExplanationTool,\n readAiAutomationAnswer,\n readAiWorkflowExplanation,\n type AiAutomationAnswer,\n type AiWorkflowExplanation,\n} from '../tools/ai-workflow-tool'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport { aiDoctrineReview, aiGenerationSpent, aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport {\n aiPluginDraftAdmissionRefusal,\n aiPluginDraftOwner,\n aiPluginDraftUnavailable,\n aiPluginDraftWriter,\n type AiPluginDraftWriterLookup,\n} from './ai-job-plugin-drafts'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\nimport {\n readAiAutomationRecords,\n readAiWorkflowFunctions,\n readAiWorkflowRun,\n readAiWorkflowTarget,\n} from './ai-workflow-records'\n\n/**\n * The `workflow` step (AGL-2919): an automation drafted from a description,\n * or a saved automation explained.\n *\n * ## Drafting\n *\n * The model is shown the platform's automation vocabulary — every trigger with\n * the fields it carries, every step with the fields it takes — in a cached\n * block, and in the user turn what this workspace can run, the site's forms\n * and datasets by name, and the description. It answers through\n * `submit_automation`, and the answer is held to the vocabulary and to what\n * the workspace can run before anything is written.\n *\n * A record the description names — a list, a campaign, a workflow, a webhook,\n * a dataset, a form, a deal stage — is answered in the description's WORDS,\n * and looked up among the site's records in code, after the answer\n * (`model/ai-automation-draft.ts`). The model is never shown the site's lists,\n * campaigns, workflows, webhooks or pipelines. Words that name no record, or\n * more than one, are kept as a placeholder the person fills in.\n *\n * The automation is written through the workflows plugin's draft writer —\n * that plugin's stored shape, validator, role, plan and cap — keyed by the\n * job's id, and it is written OFF. A run cut off after the write finds its\n * draft and spends nothing.\n *\n * ## Explaining\n *\n * The model is shown an outline of the saved automation, and for a failed run\n * what the run history recorded about it (`model/ai-automation-outline.ts`):\n * never the stored documents, never an email address, never a run's event\n * payload. It answers through `submit_explanation`, which becomes a `text`\n * output. Nothing is changed.\n */\n\n/** The longest an explanation or a draft's generation may take reading the site first. */\nexport const AI_WORKFLOW_RECORDS_READ_MS = 4_000\n\n/**\n * The step's time (AGL-3035): its answer and its re-ask at the routing\n * table's `job.workflow` ceiling on the tier the step is served from. It sends\n * no site inventory block, so it makes no lookup; its own reads — the records\n * a draft is matched against, or the automation and run an explanation\n * reads — are the declared `AI_WORKFLOW_RECORDS_READ_MS`.\n */\nexport const AI_JOB_WORKFLOW_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.workflow'],\n maxTokens: AI_ROUTING_TABLE['job.workflow'].maxTokens,\n lookups: 0,\n ownReadsMs: AI_WORKFLOW_RECORDS_READ_MS,\n})\n\n/** The least time one workflow step needs before it starts. */\nexport const AI_JOB_WORKFLOW_STEP_MINIMUM_MS = AI_JOB_WORKFLOW_STEP_BUDGET.minimumMs\n\n/** The routing table's answer for `job.workflow`. */\nexport function aiJobWorkflowModel(): string {\n return aiModelForStep('job.workflow')\n}\n\n/** How many forms and datasets the user turn lists, and how many fields a form. */\nexport const AI_WORKFLOW_FORMS_LISTED = 40\nexport const AI_WORKFLOW_FORM_FIELDS_LISTED = 20\nexport const AI_WORKFLOW_DATASETS_LISTED = 40\n\n// ── The vocabulary, for the model ─────────────────────────────────────────\n\nconst STEP_USES: Readonly<Record<AiAutomationStepType, string>> = {\n sendEmail: 'emails the person the event is about. Fields: subject, body (plain text), toField',\n notifyAdmins: 'notifies the site’s admins in the console. Fields: text (what the admins read)',\n enrollList: 'adds the person to an email list. Fields: reference (the list)',\n assignCampaign: 'files the person under a campaign. Fields: reference (the campaign)',\n runWorkflow: 'runs one of the site’s workflows. Fields: reference (the workflow)',\n datasetAppend: 'saves the event as a new record in a dataset. Fields: reference (the dataset)',\n updateDataset: 'updates the event’s record in a dataset. Fields: reference (the dataset)',\n webhookPost: 'sends the event to one of the site’s outbound webhooks. Fields: reference (the webhook)',\n siteAlert: 'shows the visitor who caused the event a short message. Fields: text (the message), severity',\n wait: 'waits before the next step. Fields: minutes',\n waitForEvent: 'waits until something else happens to the same person, or gives up. Fields: event, minutes',\n exitFlow: 'ends the automation here; with a when it is a branch. Fields: none',\n setContactStage: 'sets the person’s lifecycle stage. Fields: stage',\n addContactTag: 'tags the person. Fields: text (the tag)',\n assignContactOwner: 'gives the person an owner. Fields: text (the owner)',\n createCrmTask: 'creates a follow-up task. Fields: text (the task), taskKind, dueInDays',\n logCrmActivity: 'logs what happened on the person’s record. Fields: activityKind, text (what happened)',\n}\n\nfunction triggerLine(event: HostEventType): string {\n const fields =\n event === 'formSubmission'\n ? 'formName (the form’s name, as the request lists it), path, and every field the form collects, by its field name'\n : (HOST_EVENT_PAYLOAD_KEYS[event] ?? []).join(', ') || 'none'\n const need = AI_AUTOMATION_TRIGGER_NEEDS[event]\n return `- ${event} (${aiAutomationTriggerLabel(event)}). Fields: ${fields}.${\n need ? ` Needs ${AI_AUTOMATION_NEED_LABELS[need]}.` : ''\n }`\n}\n\nfunction stepLine(type: AiAutomationStepType): string {\n const need = AI_AUTOMATION_STEP_NEEDS[type]\n return `- ${type} (${HOST_ACTION_STEP_LABELS[type]}) ${STEP_USES[type]}.${\n need ? ` Needs ${AI_AUTOMATION_NEED_LABELS[need]}.` : ''\n }`\n}\n\n/**\n * The drafting instructions, byte-identical on every request so they cache.\n * Built from the platform's own trigger and step catalogs, so the vocabulary\n * the model is shown is the vocabulary the answer is held to.\n */\nexport const AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n `You turn a description into one automation for the Actions page of a website builder: something that happens on the site (the trigger), the conditions it must meet, and the steps that run, in order. Answer by calling ${AI_AUTOMATION_TOOL_NAME} exactly once. A reply in prose cannot be used.`,\n '',\n 'A step is written as when it runs and what it does: a when beside an action that carries its type and only that type’s fields. Use only these triggers and steps; each step lists the fields its action takes, and it carries only those.',\n '',\n 'Triggers, with the fields their conditions can read:',\n ...AI_AUTOMATION_TRIGGERS.map(triggerLine),\n '',\n 'Steps:',\n ...AI_AUTOMATION_STEP_TYPES.map(stepLine),\n '',\n 'Rules:',\n '- A step that names a list, campaign, workflow, webhook or dataset writes it as reference, in the description’s own words, such as \"newsletter\". The platform finds the site’s record by those words. Never write an id for one.',\n '- A form is named by the id or the name the request lists: formId on contactCreated, formName on formSubmission. A form the description names that the request does not list is written in the description’s words.',\n '- A deal stage in a condition on stageId or previousStageId is written as the stage’s name.',\n `- A lifecycle stage is one of: ${CONTACT_LIFECYCLE_STAGES.join(', ')}. A new lead is the stage lead.`,\n `- The trigger holds at most ${ACTION_MAX_CONDITIONS} conditions; combinator is and unless the description means any of them. A step's when holds one condition of its own at most, over the same fields. After waitForEvent, when on the field \"${FLOW_TIMED_OUT_FIELD}\" with notEmpty means the wait ran out of time.`,\n `- At most ${ACTION_MAX_STEPS} steps. minutes are whole minutes, at most ${FLOW_WAIT_MAX_MINUTES}; dueInDays is from 0 to ${CRM_TASK_MAX_DUE_DAYS}; addContactTag’s text is at most ${CONTACT_TAG_MAX_LENGTH} characters.`,\n '- assignContactOwner’s text is \"round robin\", or the teammate’s email address when the description gives one. A teammate named without an address goes in notes.',\n '- Write an email’s subject and body, and any text a person reads, in the site’s voice and the language of the description. Where the description lacks a fact such as a phone number, an address or a price, write a short description of it in square brackets instead of inventing it.',\n '- A trigger or a step the request says this workspace cannot use is never used. When the description needs one, or needs something no trigger or step here does, answer with unsupported set to the reason, no steps, and trigger event formSubmission with no conditions. unsupported is empty on every automation that is built.',\n '- notes holds what the person must decide that the automation cannot hold, in at most three short sentences; empty when there is nothing.',\n '- The automation is drafted switched off, and a person reviews it and switches it on. Never ask to turn it on or to run it.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The explaining instructions, byte-identical on every request so they cache. */\nexport const AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n `You explain an automation on a website builder to the person who owns the site, in plain words for someone who does not write code. You are shown the automation as it is set up — what starts it, its conditions and its steps in order — and, when you are asked why a run failed, what the run history recorded about that run. Answer by calling ${AI_WORKFLOW_EXPLANATION_TOOL_NAME} exactly once.`,\n '',\n '- summary: in one or two sentences, what the automation does; or, for a failed run, the most likely reason it failed.',\n '- points: what happens, step by step, in the order it happens; or, for a failed run, what the run did and where it went wrong. One sentence each.',\n '- suggestions: what the person can check or change — a list, campaign or workflow the site no longer has, a condition that can never match, a placeholder nobody has filled in, a step the plan does not include. For a failed run, how to fix it. Empty when there is nothing to suggest.',\n '',\n 'Rules:',\n '- Explain only what you are shown. Never guess at settings, records or history you were not given, and never say you changed anything: you change nothing.',\n '- Name triggers and steps the way you are shown them, not by internal names.',\n '- A value in square brackets is a placeholder nobody has filled in yet.',\n '- Personal details were removed from what you are shown, and are written as [email address] or a named teammate. Never ask for them.',\n '- Write plain text, with no markdown and no HTML.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n// ── The user turns ────────────────────────────────────────────────────────\n\nfunction yesNo(value: boolean): string {\n return value ? 'yes' : 'no'\n}\n\n/** What a draft is asked from: the workspace's reach, the site's forms and datasets, and the brief. */\nexport function aiJobWorkflowDraftPrompt(input: {\n brief: string\n capabilities: AiAutomationCapabilities\n records: Pick<AiAutomationRecords, 'forms' | 'datasets'>\n}): string {\n const { crm, webhooks, bookings } = input.capabilities\n const forms = input.records.forms.slice(0, AI_WORKFLOW_FORMS_LISTED)\n const datasets = input.records.datasets.slice(0, AI_WORKFLOW_DATASETS_LISTED)\n return [\n `This workspace can use: the CRM — ${yesNo(crm)}; webhooks — ${yesNo(webhooks)}; bookings — ${yesNo(bookings)}.`,\n 'Forms on this site (id · name · fields):',\n ...(forms.length\n ? forms.map(\n (form) =>\n `- ${form.id} · ${form.name} · ${form.fields.slice(0, AI_WORKFLOW_FORM_FIELDS_LISTED).join(', ') || 'no fields'}`,\n )\n : ['- none']),\n 'Datasets on this site (name):',\n ...(datasets.length ? datasets.map((dataset) => `- ${dataset.name}`) : ['- none']),\n `Brief: ${input.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n ].join('\\n')\n}\n\n/** What an explanation is asked from: the mode, the outlines, and the member's question. */\nexport function aiJobWorkflowExplainPrompt(input: {\n brief: string\n mode: 'explain' | 'diagnose'\n outline: string\n run: string | null\n}): string {\n return [\n input.mode === 'diagnose'\n ? 'Asked: why a run of this automation failed.'\n : 'Asked: what this automation does.',\n input.outline,\n ...(input.run ? [input.run] : []),\n `Question: ${input.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n ].join('\\n\\n')\n}\n\n/** The routing row's thinking and effort, as a generation input spells them. */\nfunction routedEffort(): Pick<AiCustomGenerationInput<unknown>, 'thinking' | 'effort'> {\n const row = AI_ROUTING_TABLE['job.workflow']\n return {\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n }\n}\n\n/** The draft generation as the doctrine's loop runs it; the eval harness records one through this same call. */\nexport function aiJobWorkflowDraftGeneration(request: {\n brief: string\n capabilities: AiAutomationCapabilities\n records: Pick<AiAutomationRecords, 'forms' | 'datasets'>\n model: string\n signal?: AbortSignal\n}): AiCustomGenerationInput<AiAutomationAnswer> {\n const check: AiGenerationCheck<AiAutomationAnswer> = (answer) =>\n readAiAutomationAnswer(answer, request.capabilities)\n return {\n step: 'job.workflow',\n model: request.model,\n instructions: AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiJobWorkflowDraftPrompt(request) }],\n tool: aiAutomationTool(),\n maxTokens: AI_JOB_WORKFLOW_STEP_BUDGET.maxTokens(request.model),\n ...routedEffort(),\n ...(request.signal ? { signal: request.signal } : {}),\n check,\n }\n}\n\n/** The explanation generation as the doctrine's loop runs it. */\nexport function aiJobWorkflowExplainGeneration(request: {\n brief: string\n mode: 'explain' | 'diagnose'\n outline: string\n run: string | null\n model: string\n signal?: AbortSignal\n}): AiCustomGenerationInput<AiWorkflowExplanation> {\n return {\n step: 'job.workflow',\n model: request.model,\n instructions: AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiJobWorkflowExplainPrompt(request) }],\n tool: aiWorkflowExplanationTool(),\n maxTokens: AI_JOB_WORKFLOW_STEP_BUDGET.maxTokens(request.model),\n ...routedEffort(),\n ...(request.signal ? { signal: request.signal } : {}),\n check: readAiWorkflowExplanation,\n }\n}\n\n// ── The runner ────────────────────────────────────────────────────────────\n\nexport interface AiJobWorkflowStepDeps {\n /** How the automation draft writer is found; the core's registry otherwise. */\n writerFor?: AiPluginDraftWriterLookup\n readRecords?: typeof readAiAutomationRecords\n readTarget?: typeof readAiWorkflowTarget\n readRun?: typeof readAiWorkflowRun\n readFunctions?: typeof readAiWorkflowFunctions\n}\n\n/** A job label: the automation's name in quotes, cut to a line. */\nfunction quotedName(name: string): string {\n const clean = name.replace(/\\s+/g, ' ').trim()\n return `“${clean.length > 60 ? `${clean.slice(0, 60)}…` : clean}”`\n}\n\nexport function createAiJobWorkflowStep(deps: AiJobWorkflowStepDeps = {}): AiJobStepRunner {\n const writerFor = deps.writerFor ?? aiPluginDraftWriter\n const readRecords = deps.readRecords ?? readAiAutomationRecords\n const readTarget = deps.readTarget ?? readAiWorkflowTarget\n const readRun = deps.readRun ?? readAiWorkflowRun\n const readFunctions = deps.readFunctions ?? readAiWorkflowFunctions\n return async ({ job, now, signal, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.workflow') ?? aiJobWorkflowModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const inputs = parseAiWorkflowJobInputs(job.inputs)\n if (typeof inputs === 'string') return unspent(inputs)\n const hostId = job.hostId\n if (!hostId) return unspent(AI_WORKFLOW_NO_SITE_COPY)\n // The door checks the caller's membership of the org the job is metered\n // against; the site is checked against that org here, before anything\n // about it is read into a prompt.\n const host = await firestore.collection('hosts').doc(hostId).get()\n if (!host.exists || host.get('orgId') !== job.orgId) return unspent(AI_WORKFLOW_NO_SITE_COPY)\n const subdomain = host.get('subdomain')\n const hostSubdomain = typeof subdomain === 'string' && subdomain ? subdomain : null\n const orgData = (org ?? (await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null) as Record<\n string,\n unknown\n > | null\n const capabilities = aiAutomationCapabilities(orgData)\n\n if (inputs.mode === 'draft') {\n const writer = writerFor(AI_AUTOMATION_RESOURCE)\n if (!writer) return unspent(AI_WORKFLOW_UNAVAILABLE_COPY)\n const place = { hostId, hostSubdomain }\n // What an earlier run of this same job already wrote, under the id the job recorded.\n const draftId = aiJobDraftId(job, 'workflow')\n const written = await writer.read({ hostId, id: draftId })\n if (written) {\n return aiUnspentOutcome(model, {\n outputs: [draftOutput(written.id, written.name, place, 'It is off until you switch it on.')],\n })\n }\n const context = { orgId: job.orgId, hostId, uid: job.createdBy, org: orgData, now }\n const refusal = await writer.refusal(context)\n if (refusal) {\n return refusal.status === 403\n ? aiUnspentOutcome(model, { review: aiLimitReview(refusal.error) })\n : unspent(refusal.error)\n }\n const records = await readRecords(firestore, { orgId: job.orgId, hostId, crm: capabilities.crm })\n const generation = await runValidatedGeneration(\n 'workflow',\n aiJobWorkflowDraftGeneration({\n brief: job.brief,\n capabilities,\n records,\n model,\n ...(signal ? { signal } : {}),\n }),\n )\n const spent = { ...aiGenerationSpent(generation), ...(generation.effort ? { effort: generation.effort } : {}) }\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, review: aiDoctrineReview(generation) }\n const answer = generation.value\n if (answer.unsupported) return { ...spent, failure: AI_AUTOMATION_UNSUPPORTED_COPY[answer.unsupported] }\n if (!answer.steps.length) return { ...spent, failure: AI_WORKFLOW_NO_DRAFT_COPY }\n const draft = aiAutomationDraft(answer, records)\n const write = await writer.write({\n ...context,\n id: draftId,\n name: draft.action.name,\n content: { action: draft.action },\n })\n if (write.ok === false) {\n return write.status === 403\n ? { ...spent, review: aiLimitReview(write.error) }\n : { ...spent, failure: AI_WORKFLOW_SAVE_FAILURE_COPY }\n }\n return {\n ...spent,\n outputs: [draftOutput(write.id, write.name, place, aiAutomationDraftNote(draft, answer.notes))],\n }\n }\n\n const target = await readTarget({ hostId, type: inputs.targetType, id: inputs.targetId })\n if (!target) return unspent(AI_WORKFLOW_GONE_COPY)\n let run: AiRunRecord | null = null\n if (inputs.mode === 'diagnose') {\n const read = await readRun(firestore, { hostId, targetId: inputs.targetId, runId: inputs.runId })\n if (read.ok === false) {\n return unspent(read.reason === 'gone' ? AI_WORKFLOW_RUN_GONE_COPY : AI_WORKFLOW_RUN_NOT_FAILED_COPY)\n }\n run = read.run\n }\n // Only the kinds of record the automation's steps name are read, to say\n // whether each still exists.\n const outline =\n target.type === 'action'\n ? aiActionOutline(\n target.action,\n await readRecords(firestore, {\n orgId: job.orgId,\n hostId,\n crm: false,\n only: aiActionRecordKinds(target.action),\n }),\n )\n : aiWorkflowOutline(target.workflow, await readFunctions(firestore, hostId))\n const generation = await runValidatedGeneration(\n 'workflow',\n aiJobWorkflowExplainGeneration({\n brief: job.brief,\n mode: inputs.mode,\n outline,\n run: run ? aiRunOutline(run) : null,\n model,\n ...(signal ? { signal } : {}),\n }),\n )\n const spent = { ...aiGenerationSpent(generation), ...(generation.effort ? { effort: generation.effort } : {}) }\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: AI_WORKFLOW_NO_EXPLANATION_COPY }\n const output: AiJobOutput = {\n resource: 'text',\n // An explanation has no document of its own; the id names what it is\n // within the job, as a text output's `draft` does.\n id: inputs.mode === 'diagnose' ? 'diagnosis' : 'explanation',\n hostId,\n hostSubdomain,\n label:\n inputs.mode === 'diagnose'\n ? `Why a run of ${quotedName(target.name)} failed`\n : `How ${quotedName(target.name)} works`,\n text: aiWorkflowExplanationText(generation.value, inputs.mode),\n }\n return { ...spent, outputs: [output] }\n }\n}\n\nfunction draftOutput(\n id: string,\n name: string,\n place: { hostId: string; hostSubdomain: string | null },\n note: string,\n): AiJobOutput {\n return {\n resource: 'workflow',\n id,\n versionId: null,\n hostId: place.hostId,\n hostSubdomain: place.hostSubdomain,\n label: name,\n note,\n }\n}\n\nexport const runAiJobWorkflowStep = createAiJobWorkflowStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\n/**\n * Whether the plugin that writes automations runs on this site: switched on,\n * and past its release flag. The owner is whichever plugin registered the\n * automation writer (AGL-3080); none registered is the same refusal.\n */\nasync function workflowsPluginRefusal(\n context: Parameters<AiJobAdmission>[0],\n hostId: string,\n): Promise<AiJobAdmissionRefusal | null> {\n const owner = aiPluginDraftOwner(AI_AUTOMATION_RESOURCE)\n if (!owner) return { status: 403, error: aiPluginDraftUnavailable('Automation') }\n const host = (await context.firestore.collection('hosts').doc(hostId).get()).data() ?? null\n const org = context.org as { enabledPlugins?: string[] } | null\n const released = await filterEnabledPluginsByReleaseFlags([owner], {\n orgId: context.orgId,\n authorization: null,\n })\n return released.includes(owner) && isHostPluginEnabled(org, host, owner)\n ? null\n : { status: 403, error: aiPluginDraftUnavailable('Automation') }\n}\n\nexport interface AiWorkflowJobAdmissionDeps {\n readTarget?: typeof readAiWorkflowTarget\n readRun?: typeof readAiWorkflowRun\n writerFor?: AiPluginDraftWriterLookup\n}\n\n/**\n * A workflow job is admitted for a site of its org with the Automation plugin\n * on. A draft also needs the draft writer and the member's room and role in\n * its own words; an explanation needs the automation it names to exist, and\n * a run's explanation a failed run of it.\n */\nexport function createAiWorkflowJobAdmission(deps: AiWorkflowJobAdmissionDeps = {}): AiJobAdmission {\n const readTarget = deps.readTarget ?? readAiWorkflowTarget\n const readRun = deps.readRun ?? readAiWorkflowRun\n return async (context) => {\n const inputs = parseAiWorkflowJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (inputs.mode === 'draft') {\n return aiPluginDraftAdmissionRefusal(context, {\n kind: 'workflow',\n drafts: [{ resource: AI_AUTOMATION_RESOURCE, label: 'Automation' }],\n ...(deps.writerFor ? { writerFor: deps.writerFor } : {}),\n })\n }\n const hostId = context.hostId\n if (!hostId) return { status: 400, error: 'Open the site the automation is on before asking about it' }\n const owner = await resolveOrgIdForHost(hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const plugin = await workflowsPluginRefusal(context, hostId)\n if (plugin) return plugin\n const target = await readTarget({ hostId, type: inputs.targetType, id: inputs.targetId })\n if (!target) return { status: 404, error: AI_WORKFLOW_GONE_COPY }\n if (inputs.mode === 'diagnose') {\n const run = await readRun(context.firestore, { hostId, targetId: inputs.targetId, runId: inputs.runId })\n if (run.ok === false) {\n return run.reason === 'gone'\n ? { status: 404, error: AI_WORKFLOW_RUN_GONE_COPY }\n : { status: 400, error: AI_WORKFLOW_RUN_NOT_FAILED_COPY }\n }\n }\n return null\n }\n}\n\nexport const aiWorkflowJobAdmission = createAiWorkflowJobAdmission()\n\n/** Registers the workflow step and the check a workflow job passes before it is created or resumed. */\nexport function registerAiWorkflowJob(): void {\n registerAiJobStep('workflow', runAiJobWorkflowStep, { minimumMs: AI_JOB_WORKFLOW_STEP_MINIMUM_MS })\n registerAiJobAdmission('workflow', aiWorkflowJobAdmission)\n}\n"],"names":["ACTION_MAX_CONDITIONS","ACTION_MAX_STEPS","CONTACT_TAG_MAX_LENGTH","FLOW_TIMED_OUT_FIELD","FLOW_WAIT_MAX_MINUTES","HOST_ACTION_STEP_LABELS","CONTACT_LIFECYCLE_STAGES","CRM_TASK_MAX_DUE_DAYS","HOST_EVENT_PAYLOAD_KEYS","isHostPluginEnabled","filterEnabledPluginsByReleaseFlags","resolveOrgIdForHost","aiAutomationDraft","aiAutomationDraftNote","aiActionOutline","aiActionRecordKinds","aiRunOutline","aiWorkflowExplanationText","aiWorkflowOutline","AI_AUTOMATION_NEED_LABELS","AI_AUTOMATION_RESOURCE","AI_AUTOMATION_STEP_NEEDS","AI_AUTOMATION_STEP_TYPES","AI_AUTOMATION_TRIGGER_NEEDS","AI_AUTOMATION_TRIGGERS","AI_AUTOMATION_UNSUPPORTED_COPY","AI_WORKFLOW_GONE_COPY","AI_WORKFLOW_NO_DRAFT_COPY","AI_WORKFLOW_NO_EXPLANATION_COPY","AI_WORKFLOW_NO_SITE_COPY","AI_WORKFLOW_RUN_GONE_COPY","AI_WORKFLOW_RUN_NOT_FAILED_COPY","AI_WORKFLOW_SAVE_FAILURE_COPY","AI_WORKFLOW_UNAVAILABLE_COPY","aiAutomationCapabilities","aiAutomationTriggerLabel","parseAiWorkflowJobInputs","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","runValidatedGeneration","AI_AUTOMATION_TOOL_NAME","AI_WORKFLOW_EXPLANATION_TOOL_NAME","aiAutomationTool","aiWorkflowExplanationTool","readAiAutomationAnswer","readAiWorkflowExplanation","registerAiJobAdmission","aiJobStepBudget","aiJobDraftId","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","aiPluginDraftOwner","aiPluginDraftUnavailable","aiPluginDraftWriter","AI_JOB_BRIEF_MAX_CHARS","registerAiJobStep","readAiAutomationRecords","readAiWorkflowFunctions","readAiWorkflowRun","readAiWorkflowTarget","AI_WORKFLOW_RECORDS_READ_MS","AI_JOB_WORKFLOW_STEP_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_WORKFLOW_STEP_MINIMUM_MS","minimumMs","aiJobWorkflowModel","AI_WORKFLOW_FORMS_LISTED","AI_WORKFLOW_FORM_FIELDS_LISTED","AI_WORKFLOW_DATASETS_LISTED","STEP_USES","sendEmail","notifyAdmins","enrollList","assignCampaign","runWorkflow","datasetAppend","updateDataset","webhookPost","siteAlert","wait","waitForEvent","exitFlow","setContactStage","addContactTag","assignContactOwner","createCrmTask","logCrmActivity","triggerLine","event","fields","join","need","stepLine","type","AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS","text","map","cacheBreakpoint","AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS","yesNo","value","aiJobWorkflowDraftPrompt","input","crm","webhooks","bookings","capabilities","forms","records","slice","datasets","length","form","id","name","dataset","brief","aiJobWorkflowExplainPrompt","mode","outline","run","routedEffort","row","thinking","effort","aiJobWorkflowDraftGeneration","request","check","answer","step","model","instructions","messages","role","content","tool","signal","aiJobWorkflowExplainGeneration","quotedName","clean","replace","trim","createAiJobWorkflowStep","deps","writerFor","readRecords","readTarget","readRun","readFunctions","job","now","firestore","org","modelFor","unspent","failure","inputs","hostId","host","collection","doc","get","exists","orgId","subdomain","hostSubdomain","orgData","data","writer","place","draftId","written","read","outputs","draftOutput","context","uid","createdBy","refusal","status","review","error","generation","spent","refused","unsupported","steps","draft","write","action","ok","notes","target","targetType","targetId","runId","reason","only","workflow","output","resource","label","note","versionId","runAiJobWorkflowStep","workflowsPluginRefusal","owner","released","authorization","includes","createAiWorkflowJobAdmission","kind","drafts","plugin","aiWorkflowJobAdmission","registerAiWorkflowJob"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-workflow-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 { ACTION_MAX_CONDITIONS, ACTION_MAX_STEPS } from '@aglyn/aglyn/app-utils/site-interactions'\nimport {\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_TAG_MAX_LENGTH,\n CRM_TASK_MAX_DUE_DAYS,\n} from '@aglyn/aglyn/app-utils/crm'\nimport { HOST_EVENT_PAYLOAD_KEYS, type HostEventType } from '@aglyn/aglyn/app-utils/host-events'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiAutomationDraft,\n aiAutomationDraftNote,\n type AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport {\n AI_AUTOMATION_TIMED_OUT_FIELD,\n AI_AUTOMATION_WAIT_MINUTES,\n aiStepLabel,\n} from '../model/ai-automation-format'\nimport {\n aiActionOutline,\n aiActionRecordKinds,\n aiRunOutline,\n aiWorkflowExplanationText,\n aiWorkflowOutline,\n type AiRunRecord,\n} from '../model/ai-automation-outline'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport {\n AI_AUTOMATION_NEED_LABELS,\n AI_AUTOMATION_RESOURCE,\n AI_AUTOMATION_STEP_NEEDS,\n AI_AUTOMATION_STEP_TYPES,\n AI_AUTOMATION_TRIGGER_NEEDS,\n AI_AUTOMATION_TRIGGERS,\n AI_AUTOMATION_UNSUPPORTED_COPY,\n AI_WORKFLOW_GONE_COPY,\n AI_WORKFLOW_NO_DRAFT_COPY,\n AI_WORKFLOW_NO_EXPLANATION_COPY,\n AI_WORKFLOW_NO_SITE_COPY,\n AI_WORKFLOW_RUN_GONE_COPY,\n AI_WORKFLOW_RUN_NOT_FAILED_COPY,\n AI_WORKFLOW_SAVE_FAILURE_COPY,\n AI_WORKFLOW_UNAVAILABLE_COPY,\n aiAutomationCapabilities,\n aiAutomationTriggerLabel,\n parseAiWorkflowJobInputs,\n type AiAutomationCapabilities,\n type AiAutomationStepType,\n} from '../model/ai-workflow-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n runValidatedGeneration,\n type AiCustomGenerationInput,\n type AiGenerationCheck,\n} from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport {\n AI_AUTOMATION_TOOL_NAME,\n AI_WORKFLOW_EXPLANATION_TOOL_NAME,\n aiAutomationTool,\n aiWorkflowExplanationTool,\n readAiAutomationAnswer,\n readAiWorkflowExplanation,\n type AiAutomationAnswer,\n type AiWorkflowExplanation,\n} from '../tools/ai-workflow-tool'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiJobDraftId } from './ai-job-draft-ids'\nimport { aiDoctrineReview, aiGenerationSpent, aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport {\n aiPluginDraftAdmissionRefusal,\n aiPluginDraftOwner,\n aiPluginDraftUnavailable,\n aiPluginDraftWriter,\n type AiPluginDraftWriterLookup,\n} from './ai-job-plugin-drafts'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\nimport {\n readAiAutomationRecords,\n readAiWorkflowFunctions,\n readAiWorkflowRun,\n readAiWorkflowTarget,\n} from './ai-workflow-records'\n\n/**\n * The `workflow` step (AGL-2919): an automation drafted from a description,\n * or a saved automation explained.\n *\n * ## Drafting\n *\n * The model is shown the platform's automation vocabulary — every trigger with\n * the fields it carries, every step with the fields it takes — in a cached\n * block, and in the user turn what this workspace can run, the site's forms\n * and datasets by name, and the description. It answers through\n * `submit_automation`, and the answer is held to the vocabulary and to what\n * the workspace can run before anything is written.\n *\n * A record the description names — a list, a campaign, a workflow, a webhook,\n * a dataset, a form, a deal stage — is answered in the description's WORDS,\n * and looked up among the site's records in code, after the answer\n * (`model/ai-automation-draft.ts`). The model is never shown the site's lists,\n * campaigns, workflows, webhooks or pipelines. Words that name no record, or\n * more than one, are kept as a placeholder the person fills in.\n *\n * The automation is written through the workflows plugin's draft writer —\n * that plugin's stored shape, validator, role, plan and cap — keyed by the\n * job's id, and it is written OFF. A run cut off after the write finds its\n * draft and spends nothing.\n *\n * ## Explaining\n *\n * The model is shown an outline of the saved automation, and for a failed run\n * what the run history recorded about it (`model/ai-automation-outline.ts`):\n * never the stored documents, never an email address, never a run's event\n * payload. It answers through `submit_explanation`, which becomes a `text`\n * output. Nothing is changed.\n */\n\n/** The longest an explanation or a draft's generation may take reading the site first. */\nexport const AI_WORKFLOW_RECORDS_READ_MS = 4_000\n\n/**\n * The step's time (AGL-3035): its answer and its re-ask at the routing\n * table's `job.workflow` ceiling on the tier the step is served from. It sends\n * no site inventory block, so it makes no lookup; its own reads — the records\n * a draft is matched against, or the automation and run an explanation\n * reads — are the declared `AI_WORKFLOW_RECORDS_READ_MS`.\n */\nexport const AI_JOB_WORKFLOW_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.workflow'],\n maxTokens: AI_ROUTING_TABLE['job.workflow'].maxTokens,\n lookups: 0,\n ownReadsMs: AI_WORKFLOW_RECORDS_READ_MS,\n})\n\n/** The least time one workflow step needs before it starts. */\nexport const AI_JOB_WORKFLOW_STEP_MINIMUM_MS = AI_JOB_WORKFLOW_STEP_BUDGET.minimumMs\n\n/** The routing table's answer for `job.workflow`. */\nexport function aiJobWorkflowModel(): string {\n return aiModelForStep('job.workflow')\n}\n\n/** How many forms and datasets the user turn lists, and how many fields a form. */\nexport const AI_WORKFLOW_FORMS_LISTED = 40\nexport const AI_WORKFLOW_FORM_FIELDS_LISTED = 20\nexport const AI_WORKFLOW_DATASETS_LISTED = 40\n\n// ── The vocabulary, for the model ─────────────────────────────────────────\n\nconst STEP_USES: Readonly<Record<AiAutomationStepType, string>> = {\n sendEmail: 'emails the person the event is about. Fields: subject, body (plain text), toField',\n notifyAdmins: 'notifies the site’s admins in the console. Fields: text (what the admins read)',\n enrollList: 'adds the person to an email list. Fields: reference (the list)',\n assignCampaign: 'files the person under a campaign. Fields: reference (the campaign)',\n runWorkflow: 'runs one of the site’s workflows. Fields: reference (the workflow)',\n datasetAppend: 'saves the event as a new record in a dataset. Fields: reference (the dataset)',\n updateDataset: 'updates the event’s record in a dataset. Fields: reference (the dataset)',\n webhookPost: 'sends the event to one of the site’s outbound webhooks. Fields: reference (the webhook)',\n siteAlert: 'shows the visitor who caused the event a short message. Fields: text (the message), severity',\n wait: 'waits before the next step. Fields: minutes',\n waitForEvent: 'waits until something else happens to the same person, or gives up. Fields: event, minutes',\n exitFlow: 'ends the automation here; with a when it is a branch. Fields: none',\n setContactStage: 'sets the person’s lifecycle stage. Fields: stage',\n addContactTag: 'tags the person. Fields: text (the tag)',\n assignContactOwner: 'gives the person an owner. Fields: text (the owner)',\n createCrmTask: 'creates a follow-up task. Fields: text (the task), taskKind, priority, dueInDays',\n logCrmActivity: 'logs what happened on the person’s record. Fields: activityKind, direction (a call’s or an email’s), text (what happened)',\n}\n\nfunction triggerLine(event: HostEventType): string {\n const fields =\n event === 'formSubmission'\n ? 'formName (the form’s name, as the request lists it), path, and every field the form collects, by its field name'\n : (HOST_EVENT_PAYLOAD_KEYS[event] ?? []).join(', ') || 'none'\n const need = AI_AUTOMATION_TRIGGER_NEEDS[event]\n return `- ${event} (${aiAutomationTriggerLabel(event)}). Fields: ${fields}.${\n need ? ` Needs ${AI_AUTOMATION_NEED_LABELS[need]}.` : ''\n }`\n}\n\nfunction stepLine(type: AiAutomationStepType): string {\n const need = AI_AUTOMATION_STEP_NEEDS[type]\n return `- ${type} (${aiStepLabel(type)}) ${STEP_USES[type]}.${\n need ? ` Needs ${AI_AUTOMATION_NEED_LABELS[need]}.` : ''\n }`\n}\n\n/**\n * The drafting instructions, byte-identical on every request so they cache.\n * Built from the platform's own trigger and step catalogs, so the vocabulary\n * the model is shown is the vocabulary the answer is held to.\n */\nexport const AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n `You turn a description into one automation for the Actions page of a website builder: something that happens on the site (the trigger), the conditions it must meet, and the steps that run, in order. Answer by calling ${AI_AUTOMATION_TOOL_NAME} exactly once. A reply in prose cannot be used.`,\n '',\n 'A step is written as when it runs and what it does: a when beside an action that carries its type and only that type’s fields. Use only these triggers and steps; each step lists the fields its action takes, and it carries only those.',\n '',\n 'Triggers, with the fields their conditions can read:',\n ...AI_AUTOMATION_TRIGGERS.map(triggerLine),\n '',\n 'Steps:',\n ...AI_AUTOMATION_STEP_TYPES.map(stepLine),\n '',\n 'Rules:',\n '- A step that names a list, campaign, workflow, webhook or dataset writes it as reference, in the description’s own words, such as \"newsletter\". The platform finds the site’s record by those words. Never write an id for one.',\n '- A form is named by the id or the name the request lists: formId on contactCreated, formName on formSubmission. A form the description names that the request does not list is written in the description’s words.',\n '- A deal stage in a condition on stageId or previousStageId is written as the stage’s name.',\n `- A lifecycle stage is one of: ${CONTACT_LIFECYCLE_STAGES.join(', ')}. A new lead is the stage lead.`,\n `- The trigger holds at most ${ACTION_MAX_CONDITIONS} conditions; combinator is and unless the description means any of them. A step's when holds one condition of its own at most, over the same fields. After waitForEvent, when on the field \"${AI_AUTOMATION_TIMED_OUT_FIELD}\" with notEmpty means the wait ran out of time.`,\n `- At most ${ACTION_MAX_STEPS} steps. minutes are whole minutes, at most ${AI_AUTOMATION_WAIT_MINUTES.max}; dueInDays is from 0 to ${CRM_TASK_MAX_DUE_DAYS}; addContactTag’s text is at most ${CONTACT_TAG_MAX_LENGTH} characters.`,\n '- assignContactOwner’s text is \"round robin\", or the teammate’s email address when the description gives one. A teammate named without an address goes in notes.',\n '- Write an email’s subject and body, and any text a person reads, in the site’s voice and the language of the description. Where the description lacks a fact such as a phone number, an address or a price, write a short description of it in square brackets instead of inventing it.',\n '- A trigger or a step the request says this workspace cannot use is never used. When the description needs one, or needs something no trigger or step here does, answer with unsupported set to the reason, no steps, and trigger event formSubmission with no conditions. unsupported is empty on every automation that is built.',\n '- notes holds what the person must decide that the automation cannot hold, in at most three short sentences; empty when there is nothing.',\n '- The automation is drafted switched off, and a person reviews it and switches it on. Never ask to turn it on or to run it.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** The explaining instructions, byte-identical on every request so they cache. */\nexport const AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n `You explain an automation on a website builder to the person who owns the site, in plain words for someone who does not write code. You are shown the automation as it is set up — what starts it, its conditions and its steps in order — and, when you are asked why a run failed, what the run history recorded about that run. Answer by calling ${AI_WORKFLOW_EXPLANATION_TOOL_NAME} exactly once.`,\n '',\n '- summary: in one or two sentences, what the automation does; or, for a failed run, the most likely reason it failed.',\n '- points: what happens, step by step, in the order it happens; or, for a failed run, what the run did and where it went wrong. One sentence each.',\n '- suggestions: what the person can check or change — a list, campaign or workflow the site no longer has, a condition that can never match, a placeholder nobody has filled in, a step the plan does not include. For a failed run, how to fix it. Empty when there is nothing to suggest.',\n '',\n 'Rules:',\n '- Explain only what you are shown. Never guess at settings, records or history you were not given, and never say you changed anything: you change nothing.',\n '- Name triggers and steps the way you are shown them, not by internal names.',\n '- A value in square brackets is a placeholder nobody has filled in yet.',\n '- Personal details were removed from what you are shown, and are written as [email address] or a named teammate. Never ask for them.',\n '- Write plain text, with no markdown and no HTML.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n// ── The user turns ────────────────────────────────────────────────────────\n\nfunction yesNo(value: boolean): string {\n return value ? 'yes' : 'no'\n}\n\n/** What a draft is asked from: the workspace's reach, the site's forms and datasets, and the brief. */\nexport function aiJobWorkflowDraftPrompt(input: {\n brief: string\n capabilities: AiAutomationCapabilities\n records: Pick<AiAutomationRecords, 'forms' | 'datasets'>\n}): string {\n const { crm, webhooks, bookings } = input.capabilities\n const forms = input.records.forms.slice(0, AI_WORKFLOW_FORMS_LISTED)\n const datasets = input.records.datasets.slice(0, AI_WORKFLOW_DATASETS_LISTED)\n return [\n `This workspace can use: the CRM — ${yesNo(crm)}; webhooks — ${yesNo(webhooks)}; bookings — ${yesNo(bookings)}.`,\n 'Forms on this site (id · name · fields):',\n ...(forms.length\n ? forms.map(\n (form) =>\n `- ${form.id} · ${form.name} · ${form.fields.slice(0, AI_WORKFLOW_FORM_FIELDS_LISTED).join(', ') || 'no fields'}`,\n )\n : ['- none']),\n 'Datasets on this site (name):',\n ...(datasets.length ? datasets.map((dataset) => `- ${dataset.name}`) : ['- none']),\n `Brief: ${input.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n ].join('\\n')\n}\n\n/** What an explanation is asked from: the mode, the outlines, and the member's question. */\nexport function aiJobWorkflowExplainPrompt(input: {\n brief: string\n mode: 'explain' | 'diagnose'\n outline: string\n run: string | null\n}): string {\n return [\n input.mode === 'diagnose'\n ? 'Asked: why a run of this automation failed.'\n : 'Asked: what this automation does.',\n input.outline,\n ...(input.run ? [input.run] : []),\n `Question: ${input.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n ].join('\\n\\n')\n}\n\n/** The routing row's thinking and effort, as a generation input spells them. */\nfunction routedEffort(): Pick<AiCustomGenerationInput<unknown>, 'thinking' | 'effort'> {\n const row = AI_ROUTING_TABLE['job.workflow']\n return {\n ...(row.thinking ? { thinking: row.thinking } : {}),\n ...(row.effort ? { effort: row.effort } : {}),\n }\n}\n\n/** The draft generation as the doctrine's loop runs it; the eval harness records one through this same call. */\nexport function aiJobWorkflowDraftGeneration(request: {\n brief: string\n capabilities: AiAutomationCapabilities\n records: Pick<AiAutomationRecords, 'forms' | 'datasets'>\n model: string\n signal?: AbortSignal\n}): AiCustomGenerationInput<AiAutomationAnswer> {\n const check: AiGenerationCheck<AiAutomationAnswer> = (answer) =>\n readAiAutomationAnswer(answer, request.capabilities)\n return {\n step: 'job.workflow',\n model: request.model,\n instructions: AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiJobWorkflowDraftPrompt(request) }],\n tool: aiAutomationTool(),\n maxTokens: AI_JOB_WORKFLOW_STEP_BUDGET.maxTokens(request.model),\n ...routedEffort(),\n ...(request.signal ? { signal: request.signal } : {}),\n check,\n }\n}\n\n/** The explanation generation as the doctrine's loop runs it. */\nexport function aiJobWorkflowExplainGeneration(request: {\n brief: string\n mode: 'explain' | 'diagnose'\n outline: string\n run: string | null\n model: string\n signal?: AbortSignal\n}): AiCustomGenerationInput<AiWorkflowExplanation> {\n return {\n step: 'job.workflow',\n model: request.model,\n instructions: AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS,\n messages: [{ role: 'user', content: aiJobWorkflowExplainPrompt(request) }],\n tool: aiWorkflowExplanationTool(),\n maxTokens: AI_JOB_WORKFLOW_STEP_BUDGET.maxTokens(request.model),\n ...routedEffort(),\n ...(request.signal ? { signal: request.signal } : {}),\n check: readAiWorkflowExplanation,\n }\n}\n\n// ── The runner ────────────────────────────────────────────────────────────\n\nexport interface AiJobWorkflowStepDeps {\n /** How the automation draft writer is found; the core's registry otherwise. */\n writerFor?: AiPluginDraftWriterLookup\n readRecords?: typeof readAiAutomationRecords\n readTarget?: typeof readAiWorkflowTarget\n readRun?: typeof readAiWorkflowRun\n readFunctions?: typeof readAiWorkflowFunctions\n}\n\n/** A job label: the automation's name in quotes, cut to a line. */\nfunction quotedName(name: string): string {\n const clean = name.replace(/\\s+/g, ' ').trim()\n return `“${clean.length > 60 ? `${clean.slice(0, 60)}…` : clean}”`\n}\n\nexport function createAiJobWorkflowStep(deps: AiJobWorkflowStepDeps = {}): AiJobStepRunner {\n const writerFor = deps.writerFor ?? aiPluginDraftWriter\n const readRecords = deps.readRecords ?? readAiAutomationRecords\n const readTarget = deps.readTarget ?? readAiWorkflowTarget\n const readRun = deps.readRun ?? readAiWorkflowRun\n const readFunctions = deps.readFunctions ?? readAiWorkflowFunctions\n return async ({ job, now, signal, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.workflow') ?? aiJobWorkflowModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const inputs = parseAiWorkflowJobInputs(job.inputs)\n if (typeof inputs === 'string') return unspent(inputs)\n const hostId = job.hostId\n if (!hostId) return unspent(AI_WORKFLOW_NO_SITE_COPY)\n // The door checks the caller's membership of the org the job is metered\n // against; the site is checked against that org here, before anything\n // about it is read into a prompt.\n const host = await firestore.collection('hosts').doc(hostId).get()\n if (!host.exists || host.get('orgId') !== job.orgId) return unspent(AI_WORKFLOW_NO_SITE_COPY)\n const subdomain = host.get('subdomain')\n const hostSubdomain = typeof subdomain === 'string' && subdomain ? subdomain : null\n const orgData = (org ?? (await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null) as Record<\n string,\n unknown\n > | null\n const capabilities = aiAutomationCapabilities(orgData)\n\n if (inputs.mode === 'draft') {\n const writer = writerFor(AI_AUTOMATION_RESOURCE)\n if (!writer) return unspent(AI_WORKFLOW_UNAVAILABLE_COPY)\n const place = { hostId, hostSubdomain }\n // What an earlier run of this same job already wrote, under the id the job recorded.\n const draftId = aiJobDraftId(job, 'workflow')\n const written = await writer.read({ hostId, id: draftId })\n if (written) {\n return aiUnspentOutcome(model, {\n outputs: [draftOutput(written.id, written.name, place, 'It is off until you switch it on.')],\n })\n }\n const context = { orgId: job.orgId, hostId, uid: job.createdBy, org: orgData, now }\n const refusal = await writer.refusal(context)\n if (refusal) {\n return refusal.status === 403\n ? aiUnspentOutcome(model, { review: aiLimitReview(refusal.error) })\n : unspent(refusal.error)\n }\n const records = await readRecords(firestore, { orgId: job.orgId, hostId, crm: capabilities.crm })\n const generation = await runValidatedGeneration(\n 'workflow',\n aiJobWorkflowDraftGeneration({\n brief: job.brief,\n capabilities,\n records,\n model,\n ...(signal ? { signal } : {}),\n }),\n )\n const spent = { ...aiGenerationSpent(generation), ...(generation.effort ? { effort: generation.effort } : {}) }\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, review: aiDoctrineReview(generation) }\n const answer = generation.value\n if (answer.unsupported) return { ...spent, failure: AI_AUTOMATION_UNSUPPORTED_COPY[answer.unsupported] }\n if (!answer.steps.length) return { ...spent, failure: AI_WORKFLOW_NO_DRAFT_COPY }\n const draft = aiAutomationDraft(answer, records)\n const write = await writer.write({\n ...context,\n id: draftId,\n name: draft.action.name,\n content: { action: draft.action },\n })\n if (write.ok === false) {\n return write.status === 403\n ? { ...spent, review: aiLimitReview(write.error) }\n : { ...spent, failure: AI_WORKFLOW_SAVE_FAILURE_COPY }\n }\n return {\n ...spent,\n outputs: [draftOutput(write.id, write.name, place, aiAutomationDraftNote(draft, answer.notes))],\n }\n }\n\n const target = await readTarget({ hostId, type: inputs.targetType, id: inputs.targetId })\n if (!target) return unspent(AI_WORKFLOW_GONE_COPY)\n let run: AiRunRecord | null = null\n if (inputs.mode === 'diagnose') {\n const read = await readRun(firestore, { hostId, targetId: inputs.targetId, runId: inputs.runId })\n if (read.ok === false) {\n return unspent(read.reason === 'gone' ? AI_WORKFLOW_RUN_GONE_COPY : AI_WORKFLOW_RUN_NOT_FAILED_COPY)\n }\n run = read.run\n }\n // Only the kinds of record the automation's steps name are read, to say\n // whether each still exists.\n const outline =\n target.type === 'action'\n ? aiActionOutline(\n target.action,\n await readRecords(firestore, {\n orgId: job.orgId,\n hostId,\n crm: false,\n only: aiActionRecordKinds(target.action),\n }),\n )\n : aiWorkflowOutline(target.workflow, await readFunctions(firestore, hostId))\n const generation = await runValidatedGeneration(\n 'workflow',\n aiJobWorkflowExplainGeneration({\n brief: job.brief,\n mode: inputs.mode,\n outline,\n run: run ? aiRunOutline(run) : null,\n model,\n ...(signal ? { signal } : {}),\n }),\n )\n const spent = { ...aiGenerationSpent(generation), ...(generation.effort ? { effort: generation.effort } : {}) }\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: AI_WORKFLOW_NO_EXPLANATION_COPY }\n const output: AiJobOutput = {\n resource: 'text',\n // An explanation has no document of its own; the id names what it is\n // within the job, as a text output's `draft` does.\n id: inputs.mode === 'diagnose' ? 'diagnosis' : 'explanation',\n hostId,\n hostSubdomain,\n label:\n inputs.mode === 'diagnose'\n ? `Why a run of ${quotedName(target.name)} failed`\n : `How ${quotedName(target.name)} works`,\n text: aiWorkflowExplanationText(generation.value, inputs.mode),\n }\n return { ...spent, outputs: [output] }\n }\n}\n\nfunction draftOutput(\n id: string,\n name: string,\n place: { hostId: string; hostSubdomain: string | null },\n note: string,\n): AiJobOutput {\n return {\n resource: 'workflow',\n id,\n versionId: null,\n hostId: place.hostId,\n hostSubdomain: place.hostSubdomain,\n label: name,\n note,\n }\n}\n\nexport const runAiJobWorkflowStep = createAiJobWorkflowStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\n/**\n * Whether the plugin that writes automations runs on this site: switched on,\n * and past its release flag. The owner is whichever plugin registered the\n * automation writer (AGL-3080); none registered is the same refusal.\n */\nasync function workflowsPluginRefusal(\n context: Parameters<AiJobAdmission>[0],\n hostId: string,\n): Promise<AiJobAdmissionRefusal | null> {\n const owner = aiPluginDraftOwner(AI_AUTOMATION_RESOURCE)\n if (!owner) return { status: 403, error: aiPluginDraftUnavailable('Automation') }\n const host = (await context.firestore.collection('hosts').doc(hostId).get()).data() ?? null\n const org = context.org as { enabledPlugins?: string[] } | null\n const released = await filterEnabledPluginsByReleaseFlags([owner], {\n orgId: context.orgId,\n authorization: null,\n })\n return released.includes(owner) && isHostPluginEnabled(org, host, owner)\n ? null\n : { status: 403, error: aiPluginDraftUnavailable('Automation') }\n}\n\nexport interface AiWorkflowJobAdmissionDeps {\n readTarget?: typeof readAiWorkflowTarget\n readRun?: typeof readAiWorkflowRun\n writerFor?: AiPluginDraftWriterLookup\n}\n\n/**\n * A workflow job is admitted for a site of its org with the Automation plugin\n * on. A draft also needs the draft writer and the member's room and role in\n * its own words; an explanation needs the automation it names to exist, and\n * a run's explanation a failed run of it.\n */\nexport function createAiWorkflowJobAdmission(deps: AiWorkflowJobAdmissionDeps = {}): AiJobAdmission {\n const readTarget = deps.readTarget ?? readAiWorkflowTarget\n const readRun = deps.readRun ?? readAiWorkflowRun\n return async (context) => {\n const inputs = parseAiWorkflowJobInputs(context.inputs)\n if (typeof inputs === 'string') return { status: 400, error: inputs }\n if (inputs.mode === 'draft') {\n return aiPluginDraftAdmissionRefusal(context, {\n kind: 'workflow',\n drafts: [{ resource: AI_AUTOMATION_RESOURCE, label: 'Automation' }],\n ...(deps.writerFor ? { writerFor: deps.writerFor } : {}),\n })\n }\n const hostId = context.hostId\n if (!hostId) return { status: 400, error: 'Open the site the automation is on before asking about it' }\n const owner = await resolveOrgIdForHost(hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const plugin = await workflowsPluginRefusal(context, hostId)\n if (plugin) return plugin\n const target = await readTarget({ hostId, type: inputs.targetType, id: inputs.targetId })\n if (!target) return { status: 404, error: AI_WORKFLOW_GONE_COPY }\n if (inputs.mode === 'diagnose') {\n const run = await readRun(context.firestore, { hostId, targetId: inputs.targetId, runId: inputs.runId })\n if (run.ok === false) {\n return run.reason === 'gone'\n ? { status: 404, error: AI_WORKFLOW_RUN_GONE_COPY }\n : { status: 400, error: AI_WORKFLOW_RUN_NOT_FAILED_COPY }\n }\n }\n return null\n }\n}\n\nexport const aiWorkflowJobAdmission = createAiWorkflowJobAdmission()\n\n/** Registers the workflow step and the check a workflow job passes before it is created or resumed. */\nexport function registerAiWorkflowJob(): void {\n registerAiJobStep('workflow', runAiJobWorkflowStep, { minimumMs: AI_JOB_WORKFLOW_STEP_MINIMUM_MS })\n registerAiJobAdmission('workflow', aiWorkflowJobAdmission)\n}\n"],"names":["ACTION_MAX_CONDITIONS","ACTION_MAX_STEPS","CONTACT_LIFECYCLE_STAGES","CONTACT_TAG_MAX_LENGTH","CRM_TASK_MAX_DUE_DAYS","HOST_EVENT_PAYLOAD_KEYS","isHostPluginEnabled","filterEnabledPluginsByReleaseFlags","resolveOrgIdForHost","aiAutomationDraft","aiAutomationDraftNote","AI_AUTOMATION_TIMED_OUT_FIELD","AI_AUTOMATION_WAIT_MINUTES","aiStepLabel","aiActionOutline","aiActionRecordKinds","aiRunOutline","aiWorkflowExplanationText","aiWorkflowOutline","AI_AUTOMATION_NEED_LABELS","AI_AUTOMATION_RESOURCE","AI_AUTOMATION_STEP_NEEDS","AI_AUTOMATION_STEP_TYPES","AI_AUTOMATION_TRIGGER_NEEDS","AI_AUTOMATION_TRIGGERS","AI_AUTOMATION_UNSUPPORTED_COPY","AI_WORKFLOW_GONE_COPY","AI_WORKFLOW_NO_DRAFT_COPY","AI_WORKFLOW_NO_EXPLANATION_COPY","AI_WORKFLOW_NO_SITE_COPY","AI_WORKFLOW_RUN_GONE_COPY","AI_WORKFLOW_RUN_NOT_FAILED_COPY","AI_WORKFLOW_SAVE_FAILURE_COPY","AI_WORKFLOW_UNAVAILABLE_COPY","aiAutomationCapabilities","aiAutomationTriggerLabel","parseAiWorkflowJobInputs","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","runValidatedGeneration","AI_AUTOMATION_TOOL_NAME","AI_WORKFLOW_EXPLANATION_TOOL_NAME","aiAutomationTool","aiWorkflowExplanationTool","readAiAutomationAnswer","readAiWorkflowExplanation","registerAiJobAdmission","aiJobStepBudget","aiJobDraftId","aiDoctrineReview","aiGenerationSpent","aiLimitReview","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","aiPluginDraftOwner","aiPluginDraftUnavailable","aiPluginDraftWriter","AI_JOB_BRIEF_MAX_CHARS","registerAiJobStep","readAiAutomationRecords","readAiWorkflowFunctions","readAiWorkflowRun","readAiWorkflowTarget","AI_WORKFLOW_RECORDS_READ_MS","AI_JOB_WORKFLOW_STEP_BUDGET","tier","maxTokens","lookups","ownReadsMs","AI_JOB_WORKFLOW_STEP_MINIMUM_MS","minimumMs","aiJobWorkflowModel","AI_WORKFLOW_FORMS_LISTED","AI_WORKFLOW_FORM_FIELDS_LISTED","AI_WORKFLOW_DATASETS_LISTED","STEP_USES","sendEmail","notifyAdmins","enrollList","assignCampaign","runWorkflow","datasetAppend","updateDataset","webhookPost","siteAlert","wait","waitForEvent","exitFlow","setContactStage","addContactTag","assignContactOwner","createCrmTask","logCrmActivity","triggerLine","event","fields","join","need","stepLine","type","AI_JOB_WORKFLOW_DRAFT_INSTRUCTIONS","text","map","max","cacheBreakpoint","AI_JOB_WORKFLOW_EXPLAIN_INSTRUCTIONS","yesNo","value","aiJobWorkflowDraftPrompt","input","crm","webhooks","bookings","capabilities","forms","records","slice","datasets","length","form","id","name","dataset","brief","aiJobWorkflowExplainPrompt","mode","outline","run","routedEffort","row","thinking","effort","aiJobWorkflowDraftGeneration","request","check","answer","step","model","instructions","messages","role","content","tool","signal","aiJobWorkflowExplainGeneration","quotedName","clean","replace","trim","createAiJobWorkflowStep","deps","writerFor","readRecords","readTarget","readRun","readFunctions","job","now","firestore","org","modelFor","unspent","failure","inputs","hostId","host","collection","doc","get","exists","orgId","subdomain","hostSubdomain","orgData","data","writer","place","draftId","written","read","outputs","draftOutput","context","uid","createdBy","refusal","status","review","error","generation","spent","refused","unsupported","steps","draft","write","action","ok","notes","target","targetType","targetId","runId","reason","only","workflow","output","resource","label","note","versionId","runAiJobWorkflowStep","workflowsPluginRefusal","owner","released","authorization","includes","createAiWorkflowJobAdmission","kind","drafts","plugin","aiWorkflowJobAdmission","registerAiWorkflowJob"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,qBAAqB,EAAEC,gBAAgB,QAAQ,2CAA0C;AAClG,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,qBAAqB,QAChB,6BAA4B;AACnC,SAASC,uBAAuB,QAA4B,qCAAoC;AAChG,SAASC,mBAAmB,QAAQ,8CAA6C;AACjF,SAASC,kCAAkC,QAAQ,gDAA+C;AAClG,SAASC,mBAAmB,QAAQ,gDAA+C;AACnF,SACEC,iBAAiB,EACjBC,qBAAqB,QAEhB,kCAA8B;AACrC,SACEC,6BAA6B,EAC7BC,0BAA0B,EAC1BC,WAAW,QACN,mCAA+B;AACtC,SACEC,eAAe,EACfC,mBAAmB,EACnBC,YAAY,EACZC,yBAAyB,EACzBC,iBAAiB,QAEZ,oCAAgC;AAEvC,SACEC,yBAAyB,EACzBC,sBAAsB,EACtBC,wBAAwB,EACxBC,wBAAwB,EACxBC,2BAA2B,EAC3BC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,yBAAyB,EACzBC,+BAA+B,EAC/BC,wBAAwB,EACxBC,yBAAyB,EACzBC,+BAA+B,EAC/BC,6BAA6B,EAC7BC,4BAA4B,EAC5BC,wBAAwB,EACxBC,wBAAwB,EACxBC,wBAAwB,QAGnB,8BAA0B;AACjC,SAASC,aAAa,QAAQ,0BAAsB;AACpD,SAASC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAsB;AACvE,SACEC,sBAAsB,QAGjB,4BAAwB;AAE/B,SACEC,uBAAuB,EACvBC,iCAAiC,EACjCC,gBAAgB,EAChBC,yBAAyB,EACzBC,sBAAsB,EACtBC,yBAAyB,QAGpB,+BAA2B;AAClC,SAASC,sBAAsB,QAAyD,wBAAoB;AAC5G,SAASC,eAAe,QAAQ,qBAAiB;AACjD,SAASC,YAAY,QAAQ,wBAAoB;AACjD,SAASC,gBAAgB,EAAEC,iBAAiB,EAAEC,aAAa,EAAEC,gBAAgB,QAAQ,yBAAqB;AAC1G,SACEC,6BAA6B,EAC7BC,kBAAkB,EAClBC,wBAAwB,EACxBC,mBAAmB,QAEd,4BAAwB;AAC/B,SAASC,sBAAsB,QAAqD,wBAAoB;AACxG,SAASC,iBAAiB,QAAQ,eAAW;AAC7C,SACEC,uBAAuB,EACvBC,uBAAuB,EACvBC,iBAAiB,EACjBC,oBAAoB,QACf,2BAAuB;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCC,GAED,wFAAwF,GACxF,OAAO,MAAMC,8BAA8B,KAAK;AAEhD;;;;;;CAMC,GACD,OAAO,MAAMC,8BAA8BjB,gBAAgB;IACzDkB,MAAM7B,aAAa,CAAC,eAAe;IACnC8B,WAAW7B,gBAAgB,CAAC,eAAe,CAAC6B,SAAS;IACrDC,SAAS;IACTC,YAAYL;AACd,GAAE;AAEF,6DAA6D,GAC7D,OAAO,MAAMM,kCAAkCL,4BAA4BM,SAAS,CAAA;AAEpF,mDAAmD,GACnD,OAAO,SAASC;IACd,OAAOjC,eAAe;AACxB;AAEA,iFAAiF,GACjF,OAAO,MAAMkC,2BAA2B,GAAE;AAC1C,OAAO,MAAMC,iCAAiC,GAAE;AAChD,OAAO,MAAMC,8BAA8B,GAAE;AAE7C,6EAA6E;AAE7E,MAAMC,YAA4D;IAChEC,WAAW;IACXC,cAAc;IACdC,YAAY;IACZC,gBAAgB;IAChBC,aAAa;IACbC,eAAe;IACfC,eAAe;IACfC,aAAa;IACbC,WAAW;IACXC,MAAM;IACNC,cAAc;IACdC,UAAU;IACVC,iBAAiB;IACjBC,eAAe;IACfC,oBAAoB;IACpBC,eAAe;IACfC,gBAAgB;AAClB;AAEA,SAASC,YAAYC,KAAoB;QAIhC1F;IAHP,MAAM2F,SACJD,UAAU,mBACN,oHACA,EAAC1F,iCAAAA,uBAAuB,CAAC0F,MAAM,YAA9B1F,iCAAkC,EAAE,EAAE4F,IAAI,CAAC,SAAS;IAC3D,MAAMC,OAAO3E,2BAA2B,CAACwE,MAAM;IAC/C,OAAO,CAAC,EAAE,EAAEA,MAAM,EAAE,EAAE5D,yBAAyB4D,OAAO,WAAW,EAAEC,OAAO,CAAC,EACzEE,OAAO,CAAC,OAAO,EAAE/E,yBAAyB,CAAC+E,KAAK,CAAC,CAAC,CAAC,GAAG,IACtD;AACJ;AAEA,SAASC,SAASC,IAA0B;IAC1C,MAAMF,OAAO7E,wBAAwB,CAAC+E,KAAK;IAC3C,OAAO,CAAC,EAAE,EAAEA,KAAK,EAAE,EAAEvF,YAAYuF,MAAM,EAAE,EAAExB,SAAS,CAACwB,KAAK,CAAC,CAAC,EAC1DF,OAAO,CAAC,OAAO,EAAE/E,yBAAyB,CAAC+E,KAAK,CAAC,CAAC,CAAC,GAAG,IACtD;AACJ;AAEA;;;;CAIC,GACD,OAAO,MAAMG,qCAA+D;IAC1E;QACEC,MAAM;YACJ,CAAC,yNAAyN,EAAE7D,wBAAwB,+CAA+C,CAAC;YACpS;YACA;YACA;YACA;eACGjB,uBAAuB+E,GAAG,CAACT;YAC9B;YACA;eACGxE,yBAAyBiF,GAAG,CAACJ;YAChC;YACA;YACA;YACA;YACA;YACA,CAAC,+BAA+B,EAAEjG,yBAAyB+F,IAAI,CAAC,MAAM,+BAA+B,CAAC;YACtG,CAAC,4BAA4B,EAAEjG,sBAAsB,4LAA4L,EAAEW,8BAA8B,+CAA+C,CAAC;YACjU,CAAC,UAAU,EAAEV,iBAAiB,2CAA2C,EAAEW,2BAA2B4F,GAAG,CAAC,yBAAyB,EAAEpG,sBAAsB,kCAAkC,EAAED,uBAAuB,YAAY,CAAC;YACnO;YACA;YACA;YACA;YACA;SACD,CAAC8F,IAAI,CAAC;QACPQ,iBAAiB;IACnB;CACD,CAAA;AAED,gFAAgF,GAChF,OAAO,MAAMC,uCAAiE;IAC5E;QACEJ,MAAM;YACJ,CAAC,qVAAqV,EAAE5D,kCAAkC,cAAc,CAAC;YACzY;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD,CAACuD,IAAI,CAAC;QACPQ,iBAAiB;IACnB;CACD,CAAA;AAED,6EAA6E;AAE7E,SAASE,MAAMC,KAAc;IAC3B,OAAOA,QAAQ,QAAQ;AACzB;AAEA,qGAAqG,GACrG,OAAO,SAASC,yBAAyBC,KAIxC;IACC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAGH,MAAMI,YAAY;IACtD,MAAMC,QAAQL,MAAMM,OAAO,CAACD,KAAK,CAACE,KAAK,CAAC,GAAG5C;IAC3C,MAAM6C,WAAWR,MAAMM,OAAO,CAACE,QAAQ,CAACD,KAAK,CAAC,GAAG1C;IACjD,OAAO;QACL,CAAC,kCAAkC,EAAEgC,MAAMI,KAAK,aAAa,EAAEJ,MAAMK,UAAU,aAAa,EAAEL,MAAMM,UAAU,CAAC,CAAC;QAChH;WACIE,MAAMI,MAAM,GACZJ,MAAMZ,GAAG,CACP,CAACiB,OACC,CAAC,EAAE,EAAEA,KAAKC,EAAE,CAAC,GAAG,EAAED,KAAKE,IAAI,CAAC,GAAG,EAAEF,KAAKxB,MAAM,CAACqB,KAAK,CAAC,GAAG3C,gCAAgCuB,IAAI,CAAC,SAAS,aAAa,IAErH;YAAC;SAAS;QACd;WACIqB,SAASC,MAAM,GAAGD,SAASf,GAAG,CAAC,CAACoB,UAAY,CAAC,EAAE,EAAEA,QAAQD,IAAI,EAAE,IAAI;YAAC;SAAS;QACjF,CAAC,OAAO,EAAEZ,MAAMc,KAAK,CAACP,KAAK,CAAC,GAAG3D,yBAAyB;KACzD,CAACuC,IAAI,CAAC;AACT;AAEA,0FAA0F,GAC1F,OAAO,SAAS4B,2BAA2Bf,KAK1C;IACC,OAAO;QACLA,MAAMgB,IAAI,KAAK,aACX,gDACA;QACJhB,MAAMiB,OAAO;WACTjB,MAAMkB,GAAG,GAAG;YAAClB,MAAMkB,GAAG;SAAC,GAAG,EAAE;QAChC,CAAC,UAAU,EAAElB,MAAMc,KAAK,CAACP,KAAK,CAAC,GAAG3D,yBAAyB;KAC5D,CAACuC,IAAI,CAAC;AACT;AAEA,8EAA8E,GAC9E,SAASgC;IACP,MAAMC,MAAM5F,gBAAgB,CAAC,eAAe;IAC5C,OAAO,aACD4F,IAAIC,QAAQ,GAAG;QAAEA,UAAUD,IAAIC,QAAQ;IAAC,IAAI,CAAC,GAC7CD,IAAIE,MAAM,GAAG;QAAEA,QAAQF,IAAIE,MAAM;IAAC,IAAI,CAAC;AAE/C;AAEA,8GAA8G,GAC9G,OAAO,SAASC,6BAA6BC,OAM5C;IACC,MAAMC,QAA+C,CAACC,SACpD3F,uBAAuB2F,QAAQF,QAAQpB,YAAY;IACrD,OAAO;QACLuB,MAAM;QACNC,OAAOJ,QAAQI,KAAK;QACpBC,cAActC;QACduC,UAAU;YAAC;gBAAEC,MAAM;gBAAQC,SAASjC,yBAAyByB;YAAS;SAAE;QACxES,MAAMpG;QACNwB,WAAWF,4BAA4BE,SAAS,CAACmE,QAAQI,KAAK;OAC3DT,gBACCK,QAAQU,MAAM,GAAG;QAAEA,QAAQV,QAAQU,MAAM;IAAC,IAAI,CAAC;QACnDT;;AAEJ;AAEA,+DAA+D,GAC/D,OAAO,SAASU,+BAA+BX,OAO9C;IACC,OAAO;QACLG,MAAM;QACNC,OAAOJ,QAAQI,KAAK;QACpBC,cAAcjC;QACdkC,UAAU;YAAC;gBAAEC,MAAM;gBAAQC,SAASjB,2BAA2BS;YAAS;SAAE;QAC1ES,MAAMnG;QACNuB,WAAWF,4BAA4BE,SAAS,CAACmE,QAAQI,KAAK;OAC3DT,gBACCK,QAAQU,MAAM,GAAG;QAAEA,QAAQV,QAAQU,MAAM;IAAC,IAAI,CAAC;QACnDT,OAAOzF;;AAEX;AAaA,iEAAiE,GACjE,SAASoG,WAAWxB,IAAY;IAC9B,MAAMyB,QAAQzB,KAAK0B,OAAO,CAAC,QAAQ,KAAKC,IAAI;IAC5C,OAAO,CAAC,CAAC,EAAEF,MAAM5B,MAAM,GAAG,KAAK,GAAG4B,MAAM9B,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG8B,MAAM,CAAC,CAAC;AACpE;AAEA,OAAO,SAASG,wBAAwBC,OAA8B,CAAC,CAAC;QACpDA,iBACEA,mBACDA,kBACHA,eACMA;IAJtB,MAAMC,aAAYD,kBAAAA,KAAKC,SAAS,YAAdD,kBAAkB9F;IACpC,MAAMgG,eAAcF,oBAAAA,KAAKE,WAAW,YAAhBF,oBAAoB3F;IACxC,MAAM8F,cAAaH,mBAAAA,KAAKG,UAAU,YAAfH,mBAAmBxF;IACtC,MAAM4F,WAAUJ,gBAAAA,KAAKI,OAAO,YAAZJ,gBAAgBzF;IAChC,MAAM8F,iBAAgBL,sBAAAA,KAAKK,aAAa,YAAlBL,sBAAsB1F;IAC5C,OAAO,OAAO,EAAEgG,GAAG,EAAEC,GAAG,EAAEd,MAAM,EAAEe,SAAS,EAAEC,GAAG,EAAEC,QAAQ,EAAE;kBAezCD;QAdjB,yEAAyE;QACzE,MAAMtB,gBAAQuB,4BAAAA,SAAW,kCAAmBzF;QAC5C,MAAM0F,UAAU,CAACC,UAAuC,aAAK9G,iBAAiBqF;gBAAQyB;;QACtF,MAAMC,SAAShI,yBAAyByH,IAAIO,MAAM;QAClD,IAAI,OAAOA,WAAW,UAAU,OAAOF,QAAQE;QAC/C,MAAMC,SAASR,IAAIQ,MAAM;QACzB,IAAI,CAACA,QAAQ,OAAOH,QAAQrI;QAC5B,wEAAwE;QACxE,sEAAsE;QACtE,kCAAkC;QAClC,MAAMyI,OAAO,MAAMP,UAAUQ,UAAU,CAAC,SAASC,GAAG,CAACH,QAAQI,GAAG;QAChE,IAAI,CAACH,KAAKI,MAAM,IAAIJ,KAAKG,GAAG,CAAC,aAAaZ,IAAIc,KAAK,EAAE,OAAOT,QAAQrI;QACpE,MAAM+I,YAAYN,KAAKG,GAAG,CAAC;QAC3B,MAAMI,gBAAgB,OAAOD,cAAc,YAAYA,YAAYA,YAAY;QAC/E,MAAME,WAAWd,QAAAA,cAAAA,MAAO,AAAC,CAAA,MAAMD,UAAUQ,UAAU,CAAC,QAAQC,GAAG,CAACX,IAAIc,KAAK,EAAEF,GAAG,EAAC,EAAGM,IAAI,cAArEf,QAA2E;QAI5F,MAAM9C,eAAehF,yBAAyB4I;QAE9C,IAAIV,OAAOtC,IAAI,KAAK,SAAS;YAC3B,MAAMkD,SAASxB,UAAUpI;YACzB,IAAI,CAAC4J,QAAQ,OAAOd,QAAQjI;YAC5B,MAAMgJ,QAAQ;gBAAEZ;gBAAQQ;YAAc;YACtC,qFAAqF;YACrF,MAAMK,UAAUjI,aAAa4G,KAAK;YAClC,MAAMsB,UAAU,MAAMH,OAAOI,IAAI,CAAC;gBAAEf;gBAAQ5C,IAAIyD;YAAQ;YACxD,IAAIC,SAAS;gBACX,OAAO9H,iBAAiBqF,OAAO;oBAC7B2C,SAAS;wBAACC,YAAYH,QAAQ1D,EAAE,EAAE0D,QAAQzD,IAAI,EAAEuD,OAAO;qBAAqC;gBAC9F;YACF;YACA,MAAMM,UAAU;gBAAEZ,OAAOd,IAAIc,KAAK;gBAAEN;gBAAQmB,KAAK3B,IAAI4B,SAAS;gBAAEzB,KAAKc;gBAAShB;YAAI;YAClF,MAAM4B,UAAU,MAAMV,OAAOU,OAAO,CAACH;YACrC,IAAIG,SAAS;gBACX,OAAOA,QAAQC,MAAM,KAAK,MACtBtI,iBAAiBqF,OAAO;oBAAEkD,QAAQxI,cAAcsI,QAAQG,KAAK;gBAAE,KAC/D3B,QAAQwB,QAAQG,KAAK;YAC3B;YACA,MAAMzE,UAAU,MAAMqC,YAAYM,WAAW;gBAAEY,OAAOd,IAAIc,KAAK;gBAAEN;gBAAQtD,KAAKG,aAAaH,GAAG;YAAC;YAC/F,MAAM+E,aAAa,MAAMtJ,uBACvB,YACA6F,6BAA6B;gBAC3BT,OAAOiC,IAAIjC,KAAK;gBAChBV;gBACAE;gBACAsB;eACIM,SAAS;gBAAEA;YAAO,IAAI,CAAC;YAG/B,MAAM+C,QAAQ,aAAK5I,kBAAkB2I,aAAiBA,WAAW1D,MAAM,GAAG;gBAAEA,QAAQ0D,WAAW1D,MAAM;YAAC,IAAI,CAAC;YAC3G,IAAI0D,WAAWH,MAAM,KAAK,WAAW,OAAO,aAAKI;gBAAOC,SAAS;;YACjE,IAAIF,WAAWH,MAAM,KAAK,eAAe,OAAO,aAAKI;gBAAOH,QAAQ1I,iBAAiB4I;;YACrF,MAAMtD,SAASsD,WAAWlF,KAAK;YAC/B,IAAI4B,OAAOyD,WAAW,EAAE,OAAO,aAAKF;gBAAO5B,SAAS1I,8BAA8B,CAAC+G,OAAOyD,WAAW,CAAC;;YACtG,IAAI,CAACzD,OAAO0D,KAAK,CAAC3E,MAAM,EAAE,OAAO,aAAKwE;gBAAO5B,SAASxI;;YACtD,MAAMwK,QAAQ1L,kBAAkB+H,QAAQpB;YACxC,MAAMgF,QAAQ,MAAMpB,OAAOoB,KAAK,CAAC,aAC5Bb;gBACH9D,IAAIyD;gBACJxD,MAAMyE,MAAME,MAAM,CAAC3E,IAAI;gBACvBoB,SAAS;oBAAEuD,QAAQF,MAAME,MAAM;gBAAC;;YAElC,IAAID,MAAME,EAAE,KAAK,OAAO;gBACtB,OAAOF,MAAMT,MAAM,KAAK,MACpB,aAAKI;oBAAOH,QAAQxI,cAAcgJ,MAAMP,KAAK;qBAC7C,aAAKE;oBAAO5B,SAASnI;;YAC3B;YACA,OAAO,aACF+J;gBACHV,SAAS;oBAACC,YAAYc,MAAM3E,EAAE,EAAE2E,MAAM1E,IAAI,EAAEuD,OAAOvK,sBAAsByL,OAAO3D,OAAO+D,KAAK;iBAAG;;QAEnG;QAEA,MAAMC,SAAS,MAAM9C,WAAW;YAAEW;YAAQjE,MAAMgE,OAAOqC,UAAU;YAAEhF,IAAI2C,OAAOsC,QAAQ;QAAC;QACvF,IAAI,CAACF,QAAQ,OAAOtC,QAAQxI;QAC5B,IAAIsG,MAA0B;QAC9B,IAAIoC,OAAOtC,IAAI,KAAK,YAAY;YAC9B,MAAMsD,OAAO,MAAMzB,QAAQI,WAAW;gBAAEM;gBAAQqC,UAAUtC,OAAOsC,QAAQ;gBAAEC,OAAOvC,OAAOuC,KAAK;YAAC;YAC/F,IAAIvB,KAAKkB,EAAE,KAAK,OAAO;gBACrB,OAAOpC,QAAQkB,KAAKwB,MAAM,KAAK,SAAS9K,4BAA4BC;YACtE;YACAiG,MAAMoD,KAAKpD,GAAG;QAChB;QACA,wEAAwE;QACxE,6BAA6B;QAC7B,MAAMD,UACJyE,OAAOpG,IAAI,KAAK,WACZtF,gBACE0L,OAAOH,MAAM,EACb,MAAM5C,YAAYM,WAAW;YAC3BY,OAAOd,IAAIc,KAAK;YAChBN;YACAtD,KAAK;YACL8F,MAAM9L,oBAAoByL,OAAOH,MAAM;QACzC,MAEFnL,kBAAkBsL,OAAOM,QAAQ,EAAE,MAAMlD,cAAcG,WAAWM;QACxE,MAAMyB,aAAa,MAAMtJ,uBACvB,YACAyG,+BAA+B;YAC7BrB,OAAOiC,IAAIjC,KAAK;YAChBE,MAAMsC,OAAOtC,IAAI;YACjBC;YACAC,KAAKA,MAAMhH,aAAagH,OAAO;YAC/BU;WACIM,SAAS;YAAEA;QAAO,IAAI,CAAC;QAG/B,MAAM+C,QAAQ,aAAK5I,kBAAkB2I,aAAiBA,WAAW1D,MAAM,GAAG;YAAEA,QAAQ0D,WAAW1D,MAAM;QAAC,IAAI,CAAC;QAC3G,IAAI0D,WAAWH,MAAM,KAAK,WAAW,OAAO,aAAKI;YAAOC,SAAS;;QACjE,IAAIF,WAAWH,MAAM,KAAK,eAAe,OAAO,aAAKI;YAAO5B,SAASvI;;QACrE,MAAMmL,SAAsB;YAC1BC,UAAU;YACV,qEAAqE;YACrE,mDAAmD;YACnDvF,IAAI2C,OAAOtC,IAAI,KAAK,aAAa,cAAc;YAC/CuC;YACAQ;YACAoC,OACE7C,OAAOtC,IAAI,KAAK,aACZ,CAAC,aAAa,EAAEoB,WAAWsD,OAAO9E,IAAI,EAAE,OAAO,CAAC,GAChD,CAAC,IAAI,EAAEwB,WAAWsD,OAAO9E,IAAI,EAAE,MAAM,CAAC;YAC5CpB,MAAMrF,0BAA0B6K,WAAWlF,KAAK,EAAEwD,OAAOtC,IAAI;QAC/D;QACA,OAAO,aAAKiE;YAAOV,SAAS;gBAAC0B;aAAO;;IACtC;AACF;AAEA,SAASzB,YACP7D,EAAU,EACVC,IAAY,EACZuD,KAAuD,EACvDiC,IAAY;IAEZ,OAAO;QACLF,UAAU;QACVvF;QACA0F,WAAW;QACX9C,QAAQY,MAAMZ,MAAM;QACpBQ,eAAeI,MAAMJ,aAAa;QAClCoC,OAAOvF;QACPwF;IACF;AACF;AAEA,OAAO,MAAME,uBAAuB9D,0BAAyB;AAE7D,6EAA6E;AAE7E;;;;CAIC,GACD,eAAe+D,uBACb9B,OAAsC,EACtClB,MAAc;QAID;IAFb,MAAMiD,QAAQ/J,mBAAmBnC;IACjC,IAAI,CAACkM,OAAO,OAAO;QAAE3B,QAAQ;QAAKE,OAAOrI,yBAAyB;IAAc;IAChF,MAAM8G,QAAO,QAAA,AAAC,CAAA,MAAMiB,QAAQxB,SAAS,CAACQ,UAAU,CAAC,SAASC,GAAG,CAACH,QAAQI,GAAG,EAAC,EAAGM,IAAI,cAApE,QAA0E;IACvF,MAAMf,MAAMuB,QAAQvB,GAAG;IACvB,MAAMuD,WAAW,MAAMhN,mCAAmC;QAAC+M;KAAM,EAAE;QACjE3C,OAAOY,QAAQZ,KAAK;QACpB6C,eAAe;IACjB;IACA,OAAOD,SAASE,QAAQ,CAACH,UAAUhN,oBAAoB0J,KAAKM,MAAMgD,SAC9D,OACA;QAAE3B,QAAQ;QAAKE,OAAOrI,yBAAyB;IAAc;AACnE;AAQA;;;;;CAKC,GACD,OAAO,SAASkK,6BAA6BnE,OAAmC,CAAC,CAAC;QAC7DA,kBACHA;IADhB,MAAMG,cAAaH,mBAAAA,KAAKG,UAAU,YAAfH,mBAAmBxF;IACtC,MAAM4F,WAAUJ,gBAAAA,KAAKI,OAAO,YAAZJ,gBAAgBzF;IAChC,OAAO,OAAOyH;QACZ,MAAMnB,SAAShI,yBAAyBmJ,QAAQnB,MAAM;QACtD,IAAI,OAAOA,WAAW,UAAU,OAAO;YAAEuB,QAAQ;YAAKE,OAAOzB;QAAO;QACpE,IAAIA,OAAOtC,IAAI,KAAK,SAAS;YAC3B,OAAOxE,8BAA8BiI,SAAS;gBAC5CoC,MAAM;gBACNC,QAAQ;oBAAC;wBAAEZ,UAAU5L;wBAAwB6L,OAAO;oBAAa;iBAAE;eAC/D1D,KAAKC,SAAS,GAAG;gBAAEA,WAAWD,KAAKC,SAAS;YAAC,IAAI,CAAC;QAE1D;QACA,MAAMa,SAASkB,QAAQlB,MAAM;QAC7B,IAAI,CAACA,QAAQ,OAAO;YAAEsB,QAAQ;YAAKE,OAAO;QAA4D;QACtG,MAAMyB,QAAQ,MAAM9M,oBAAoB6J;QACxC,IAAI,CAACiD,SAASA,UAAU/B,QAAQZ,KAAK,EAAE,OAAO;YAAEgB,QAAQ;YAAKE,OAAO;QAAe;QACnF,MAAMgC,SAAS,MAAMR,uBAAuB9B,SAASlB;QACrD,IAAIwD,QAAQ,OAAOA;QACnB,MAAMrB,SAAS,MAAM9C,WAAW;YAAEW;YAAQjE,MAAMgE,OAAOqC,UAAU;YAAEhF,IAAI2C,OAAOsC,QAAQ;QAAC;QACvF,IAAI,CAACF,QAAQ,OAAO;YAAEb,QAAQ;YAAKE,OAAOnK;QAAsB;QAChE,IAAI0I,OAAOtC,IAAI,KAAK,YAAY;YAC9B,MAAME,MAAM,MAAM2B,QAAQ4B,QAAQxB,SAAS,EAAE;gBAAEM;gBAAQqC,UAAUtC,OAAOsC,QAAQ;gBAAEC,OAAOvC,OAAOuC,KAAK;YAAC;YACtG,IAAI3E,IAAIsE,EAAE,KAAK,OAAO;gBACpB,OAAOtE,IAAI4E,MAAM,KAAK,SAClB;oBAAEjB,QAAQ;oBAAKE,OAAO/J;gBAA0B,IAChD;oBAAE6J,QAAQ;oBAAKE,OAAO9J;gBAAgC;YAC5D;QACF;QACA,OAAO;IACT;AACF;AAEA,OAAO,MAAM+L,yBAAyBJ,+BAA8B;AAEpE,qGAAqG,GACrG,OAAO,SAASK;IACdpK,kBAAkB,YAAYyJ,sBAAsB;QAAE7I,WAAWD;IAAgC;IACjGvB,uBAAuB,YAAY+K;AACrC"}
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import type { HostAction } from '@aglyn/aglyn/app-utils/actions';
|
|
18
17
|
import type { AiAutomationNamedRecord, AiAutomationRecords } from '../model/ai-automation-draft';
|
|
19
18
|
import type { AiIndexedWorkflow, AiRunRecord } from '../model/ai-automation-outline';
|
|
20
19
|
import type { AiWorkflowTargetType } from '../model/ai-workflow-job';
|
|
20
|
+
import type { AiAutomation } from '../model/ai-automation-format';
|
|
21
21
|
/**
|
|
22
22
|
* What a `workflow` job reads (AGL-2919), scoped to the job's own site and
|
|
23
23
|
* org: the records a drafted automation's words are looked up among, the
|
|
@@ -55,7 +55,7 @@ export type AiWorkflowTarget = {
|
|
|
55
55
|
type: 'action';
|
|
56
56
|
id: string;
|
|
57
57
|
name: string;
|
|
58
|
-
action:
|
|
58
|
+
action: AiAutomation;
|
|
59
59
|
} | {
|
|
60
60
|
type: 'workflow';
|
|
61
61
|
id: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
1
2
|
/**
|
|
2
3
|
* @license
|
|
3
4
|
* Copyright 2026 Aglyn LLC
|
|
@@ -13,8 +14,7 @@
|
|
|
13
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
15
|
* See the License for the specific language governing permissions and
|
|
15
16
|
* limitations under the License.
|
|
16
|
-
*/ import {
|
|
17
|
-
import { actionRunResult } from "@aglyn/aglyn/app-utils/activity-presenter";
|
|
17
|
+
*/ import { actionRunResult } from "@aglyn/aglyn/app-utils/activity-presenter";
|
|
18
18
|
import { isFormArchived } from "@aglyn/aglyn/app-utils/forms";
|
|
19
19
|
import { pluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
|
|
20
20
|
import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-workflow-records.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostAction } from '@aglyn/aglyn/app-utils/actions'\nimport { actionRunResult } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport {\n pluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { listOrgContainers } from '@aglyn/tenant-data-admin/server/org-containers'\nimport type {\n AiAutomationForm,\n AiAutomationNamedRecord,\n AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport type { AiIndexedWorkflow, AiRunRecord } from '../model/ai-automation-outline'\nimport type { AiWorkflowTargetType } from '../model/ai-workflow-job'\n\n/**\n * What a `workflow` job reads (AGL-2919), scoped to the job's own site and\n * org: the records a drafted automation's words are looked up among, the\n * automation an explanation reads, and the run it explains. Every read is\n * windowed the way the Actions editor's pickers are, so a lookup never reads\n * more than the editor would offer.\n *\n * The site's workflows, webhooks and saved actions are the workflows plugin's\n * records, and the org's datasets the data plugin's, so they are read through\n * the indexes those plugins publish (`workflow`, `webhook`, `action`,\n * `dataset` — AGL-3080), never from their collections: where no plugin keeps a\n * kind, there are none to name. The rest are read through the Admin SDK as\n * projections of the fields named here.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\ntype Data = Record<string, unknown>\n\n/** Records of one kind a lookup reads: the Actions editor's picker window. */\nexport const AI_WORKFLOW_RECORDS_WINDOW = 100\n\n/** Pipelines a stage lookup reads. */\nexport const AI_WORKFLOW_PIPELINES_WINDOW = 20\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nasync function named(\n query: FirebaseFirestore.Query,\n fields: string[],\n keep: (data: Data) => boolean,\n nameOf: (data: Data, id: string) => string,\n): Promise<AiAutomationNamedRecord[]> {\n const snapshot = await query\n .select(...fields)\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .map((doc) => ({ data: (doc.data() ?? {}) as Data, id: doc.id }))\n .filter(({ data }) => keep(data))\n .map(({ data, id }) => ({ id, name: nameOf(data, id) }))\n .filter((record) => record.name)\n}\n\nconst live = (data: Data) => data['deletedAt'] == null\n\n/** The org's live campaigns placed on the site, named, in the editor's window. */\nasync function placedCampaigns(\n firestore: Firestore,\n orgId: string,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n const containers = await listOrgContainers(firestore, 'campaign', orgId, AI_WORKFLOW_RECORDS_WINDOW)\n return containers\n .filter((container) => container.live && container.name && visibleToHost(container.visibleTo, hostId))\n .map((container) => ({ id: container.id, name: container.name }))\n}\n\n/**\n * The live records of a kind another plugin keeps, through its index, in the\n * editor's window; none where no plugin keeps the kind here.\n */\nasync function indexed(\n kind: string,\n scope: { orgId?: string; hostId: string },\n keep: (record: PluginIndexedRecord) => boolean = () => true,\n): Promise<AiAutomationNamedRecord[]> {\n const owner = pluginRecordIndex(kind)\n if (!owner) return []\n const { records } = await owner.index.list({ ...scope, limit: AI_WORKFLOW_RECORDS_WINDOW })\n return records.filter(keep).map(({ id, name }) => ({ id, name }))\n}\n\n/** The site's forms that are not archived, with the field names their submissions carry. */\nasync function readForms(host: FirebaseFirestore.DocumentReference): Promise<AiAutomationForm[]> {\n const snapshot = await host\n .collection('forms')\n .select('displayName', 'slug', 'fields', 'archivedAt')\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .filter((doc) => !isFormArchived((doc.data() ?? {}) as { archivedAt?: unknown }))\n .map((doc) => {\n const data = (doc.data() ?? {}) as Data\n return {\n id: doc.id,\n name: text(data['displayName']) || text(data['slug']) || doc.id,\n fields: (Array.isArray(data['fields']) ? (data['fields'] as Data[]) : [])\n .map((field) => text(field?.['fieldName']))\n .filter(Boolean),\n }\n })\n}\n\n/**\n * The site's records a drafted automation may name. Stages are read only for\n * a workspace with the CRM, whose pipelines they are; a site without it has\n * no stage a condition could name. `only`, when given, names the kinds to\n * read, and every other kind is answered empty without a read.\n */\nexport async function readAiAutomationRecords(\n firestore: Firestore,\n input: { orgId: string; hostId: string; crm: boolean; only?: ReadonlyArray<keyof AiAutomationRecords> },\n): Promise<AiAutomationRecords> {\n const host = firestore.collection('hosts').doc(input.hostId)\n const org = firestore.collection('orgs').doc(input.orgId)\n const wanted = (kind: keyof AiAutomationRecords) => !input.only || input.only.includes(kind)\n const none = Promise.resolve([] as AiAutomationNamedRecord[])\n const [forms, datasets, lists, campaigns, workflows, webhooks, stages] = await Promise.all([\n !wanted('forms') ? Promise.resolve([] as AiAutomationForm[]) : readForms(host),\n // The org's datasets shared with this site, as the data plugin indexes them.\n !wanted('datasets') ? none : indexed('dataset', { orgId: input.orgId, hostId: input.hostId }),\n !wanted('lists') ? none : named(org.collection('lists'), ['name', 'deletedAt'], live, (data) => text(data['name'])),\n // The org's campaigns placed on this site: the set the automation's\n // `assignCampaign` step accepts when it runs here. Read as the `campaign`\n // container kind, where its declaration says the containers are stored.\n !wanted('campaigns') ? none : placedCampaigns(firestore, input.orgId, input.hostId),\n !wanted('workflows') ? none : indexed('workflow', { hostId: input.hostId }),\n // Only a webhook that posts OUT is a step's target; an inbound one is an\n // endpoint that runs a workflow.\n !wanted('webhooks')\n ? none\n : indexed('webhook', { hostId: input.hostId }, (record) => record.facts['direction'] === 'outbound'),\n input.crm && wanted('stages') ? readStages(input.orgId, input.hostId) : none,\n ])\n return { forms, datasets, lists, campaigns, workflows, webhooks, stages }\n}\n\n/**\n * The stages of the pipelines the site can see, asked of the plugin that keeps\n * pipelines (its `pipeline` record index), never read from its collection. No\n * index means no plugin keeps pipelines in this process: no stage to name.\n */\nasync function readStages(orgId: string, hostId: string): Promise<AiAutomationNamedRecord[]> {\n const pipelines = pluginRecordIndex('pipeline')\n if (!pipelines) return []\n const { records } = await pipelines.index.list({ orgId, hostId, limit: AI_WORKFLOW_PIPELINES_WINDOW })\n return records\n .flatMap((record) => (Array.isArray(record.facts['stages']) ? (record.facts['stages'] as Data[]) : []))\n .map((stage) => ({ id: text(stage?.['id']), name: text(stage?.['name']) }))\n .filter((stage) => stage.id && stage.name)\n}\n\n/** The site's functions, for a workflow outline that says which of its calls still resolve. */\nexport async function readAiWorkflowFunctions(\n firestore: Firestore,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n return named(\n firestore.collection('hosts').doc(hostId).collection('functions'),\n ['name', 'deletedAt'],\n live,\n (data) => text(data['name']),\n )\n}\n\nexport type AiWorkflowTarget =\n | { type: 'action'; id: string; name: string; action: HostAction }\n | { type: 'workflow'; id: string; name: string; workflow: AiIndexedWorkflow }\n\n/**\n * A saved automation of the site, read through the index of the plugin that\n * keeps it, or `null` for one that does not exist, was deleted, or that no\n * plugin here keeps.\n */\nexport async function readAiWorkflowTarget(\n input: { hostId: string; type: AiWorkflowTargetType; id: string },\n): Promise<AiWorkflowTarget | null> {\n const owner = pluginRecordIndex(input.type)\n const record = owner ? await owner.index.get({ hostId: input.hostId, id: input.id }) : null\n if (!record) return null\n const stored = { name: record.name, ...record.facts }\n return input.type === 'action'\n ? { type: 'action', id: record.id, name: record.name, action: stored as unknown as HostAction }\n : { type: 'workflow', id: record.id, name: record.name, workflow: stored as unknown as AiIndexedWorkflow }\n}\n\nexport type AiWorkflowRunRead =\n | { ok: true; run: AiRunRecord }\n | { ok: false; reason: 'gone' | 'not-failed' }\n\n/**\n * One run of an automation, as the site's activity log recorded it: gone when\n * there is no such entry or it belongs to another automation, and refused\n * when it did not fail. Only what the run history shows is read — never the\n * event's payload.\n */\nexport async function readAiWorkflowRun(\n firestore: Firestore,\n input: { hostId: string; targetId: string; runId: string },\n): Promise<AiWorkflowRunRead> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(input.hostId)\n .collection('activity')\n .doc(input.runId)\n .get()\n const data = snapshot.exists ? ((snapshot.data() ?? {}) as Data) : null\n const target = data?.['target'] as { id?: unknown } | undefined\n if (!data || target?.id !== input.targetId) return { ok: false, reason: 'gone' }\n const run: AiRunRecord = {\n result: data['result'],\n trigger: data['trigger'],\n summary: data['summary'],\n action: data['action'],\n createdAt: data['createdAt'],\n }\n return actionRunResult(run as never) === 'failed' ? { ok: true, run } : { ok: false, reason: 'not-failed' }\n}\n"],"names":["actionRunResult","isFormArchived","pluginRecordIndex","visibleToHost","listOrgContainers","AI_WORKFLOW_RECORDS_WINDOW","AI_WORKFLOW_PIPELINES_WINDOW","text","value","trim","named","query","fields","keep","nameOf","snapshot","select","limit","get","docs","map","doc","data","id","filter","name","record","live","placedCampaigns","firestore","orgId","hostId","containers","container","visibleTo","indexed","kind","scope","owner","records","index","list","readForms","host","collection","Array","isArray","field","Boolean","readAiAutomationRecords","input","org","wanted","only","includes","none","Promise","resolve","forms","datasets","lists","campaigns","workflows","webhooks","stages","all","facts","crm","readStages","pipelines","flatMap","stage","readAiWorkflowFunctions","readAiWorkflowTarget","type","stored","action","workflow","readAiWorkflowRun","runId","exists","target","targetId","ok","reason","run","result","trigger","summary","createdAt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-workflow-records.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 { actionRunResult } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport {\n pluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { listOrgContainers } from '@aglyn/tenant-data-admin/server/org-containers'\nimport type {\n AiAutomationForm,\n AiAutomationNamedRecord,\n AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport type { AiIndexedWorkflow, AiRunRecord } from '../model/ai-automation-outline'\nimport type { AiWorkflowTargetType } from '../model/ai-workflow-job'\nimport type { AiAutomation } from '../model/ai-automation-format'\n\n/**\n * What a `workflow` job reads (AGL-2919), scoped to the job's own site and\n * org: the records a drafted automation's words are looked up among, the\n * automation an explanation reads, and the run it explains. Every read is\n * windowed the way the Actions editor's pickers are, so a lookup never reads\n * more than the editor would offer.\n *\n * The site's workflows, webhooks and saved actions are the workflows plugin's\n * records, and the org's datasets the data plugin's, so they are read through\n * the indexes those plugins publish (`workflow`, `webhook`, `action`,\n * `dataset` — AGL-3080), never from their collections: where no plugin keeps a\n * kind, there are none to name. The rest are read through the Admin SDK as\n * projections of the fields named here.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\ntype Data = Record<string, unknown>\n\n/** Records of one kind a lookup reads: the Actions editor's picker window. */\nexport const AI_WORKFLOW_RECORDS_WINDOW = 100\n\n/** Pipelines a stage lookup reads. */\nexport const AI_WORKFLOW_PIPELINES_WINDOW = 20\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nasync function named(\n query: FirebaseFirestore.Query,\n fields: string[],\n keep: (data: Data) => boolean,\n nameOf: (data: Data, id: string) => string,\n): Promise<AiAutomationNamedRecord[]> {\n const snapshot = await query\n .select(...fields)\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .map((doc) => ({ data: (doc.data() ?? {}) as Data, id: doc.id }))\n .filter(({ data }) => keep(data))\n .map(({ data, id }) => ({ id, name: nameOf(data, id) }))\n .filter((record) => record.name)\n}\n\nconst live = (data: Data) => data['deletedAt'] == null\n\n/** The org's live campaigns placed on the site, named, in the editor's window. */\nasync function placedCampaigns(\n firestore: Firestore,\n orgId: string,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n const containers = await listOrgContainers(firestore, 'campaign', orgId, AI_WORKFLOW_RECORDS_WINDOW)\n return containers\n .filter((container) => container.live && container.name && visibleToHost(container.visibleTo, hostId))\n .map((container) => ({ id: container.id, name: container.name }))\n}\n\n/**\n * The live records of a kind another plugin keeps, through its index, in the\n * editor's window; none where no plugin keeps the kind here.\n */\nasync function indexed(\n kind: string,\n scope: { orgId?: string; hostId: string },\n keep: (record: PluginIndexedRecord) => boolean = () => true,\n): Promise<AiAutomationNamedRecord[]> {\n const owner = pluginRecordIndex(kind)\n if (!owner) return []\n const { records } = await owner.index.list({ ...scope, limit: AI_WORKFLOW_RECORDS_WINDOW })\n return records.filter(keep).map(({ id, name }) => ({ id, name }))\n}\n\n/** The site's forms that are not archived, with the field names their submissions carry. */\nasync function readForms(host: FirebaseFirestore.DocumentReference): Promise<AiAutomationForm[]> {\n const snapshot = await host\n .collection('forms')\n .select('displayName', 'slug', 'fields', 'archivedAt')\n .limit(AI_WORKFLOW_RECORDS_WINDOW)\n .get()\n return snapshot.docs\n .filter((doc) => !isFormArchived((doc.data() ?? {}) as { archivedAt?: unknown }))\n .map((doc) => {\n const data = (doc.data() ?? {}) as Data\n return {\n id: doc.id,\n name: text(data['displayName']) || text(data['slug']) || doc.id,\n fields: (Array.isArray(data['fields']) ? (data['fields'] as Data[]) : [])\n .map((field) => text(field?.['fieldName']))\n .filter(Boolean),\n }\n })\n}\n\n/**\n * The site's records a drafted automation may name. Stages are read only for\n * a workspace with the CRM, whose pipelines they are; a site without it has\n * no stage a condition could name. `only`, when given, names the kinds to\n * read, and every other kind is answered empty without a read.\n */\nexport async function readAiAutomationRecords(\n firestore: Firestore,\n input: { orgId: string; hostId: string; crm: boolean; only?: ReadonlyArray<keyof AiAutomationRecords> },\n): Promise<AiAutomationRecords> {\n const host = firestore.collection('hosts').doc(input.hostId)\n const org = firestore.collection('orgs').doc(input.orgId)\n const wanted = (kind: keyof AiAutomationRecords) => !input.only || input.only.includes(kind)\n const none = Promise.resolve([] as AiAutomationNamedRecord[])\n const [forms, datasets, lists, campaigns, workflows, webhooks, stages] = await Promise.all([\n !wanted('forms') ? Promise.resolve([] as AiAutomationForm[]) : readForms(host),\n // The org's datasets shared with this site, as the data plugin indexes them.\n !wanted('datasets') ? none : indexed('dataset', { orgId: input.orgId, hostId: input.hostId }),\n !wanted('lists') ? none : named(org.collection('lists'), ['name', 'deletedAt'], live, (data) => text(data['name'])),\n // The org's campaigns placed on this site: the set the automation's\n // `assignCampaign` step accepts when it runs here. Read as the `campaign`\n // container kind, where its declaration says the containers are stored.\n !wanted('campaigns') ? none : placedCampaigns(firestore, input.orgId, input.hostId),\n !wanted('workflows') ? none : indexed('workflow', { hostId: input.hostId }),\n // Only a webhook that posts OUT is a step's target; an inbound one is an\n // endpoint that runs a workflow.\n !wanted('webhooks')\n ? none\n : indexed('webhook', { hostId: input.hostId }, (record) => record.facts['direction'] === 'outbound'),\n input.crm && wanted('stages') ? readStages(input.orgId, input.hostId) : none,\n ])\n return { forms, datasets, lists, campaigns, workflows, webhooks, stages }\n}\n\n/**\n * The stages of the pipelines the site can see, asked of the plugin that keeps\n * pipelines (its `pipeline` record index), never read from its collection. No\n * index means no plugin keeps pipelines in this process: no stage to name.\n */\nasync function readStages(orgId: string, hostId: string): Promise<AiAutomationNamedRecord[]> {\n const pipelines = pluginRecordIndex('pipeline')\n if (!pipelines) return []\n const { records } = await pipelines.index.list({ orgId, hostId, limit: AI_WORKFLOW_PIPELINES_WINDOW })\n return records\n .flatMap((record) => (Array.isArray(record.facts['stages']) ? (record.facts['stages'] as Data[]) : []))\n .map((stage) => ({ id: text(stage?.['id']), name: text(stage?.['name']) }))\n .filter((stage) => stage.id && stage.name)\n}\n\n/** The site's functions, for a workflow outline that says which of its calls still resolve. */\nexport async function readAiWorkflowFunctions(\n firestore: Firestore,\n hostId: string,\n): Promise<AiAutomationNamedRecord[]> {\n return named(\n firestore.collection('hosts').doc(hostId).collection('functions'),\n ['name', 'deletedAt'],\n live,\n (data) => text(data['name']),\n )\n}\n\nexport type AiWorkflowTarget =\n | { type: 'action'; id: string; name: string; action: AiAutomation }\n | { type: 'workflow'; id: string; name: string; workflow: AiIndexedWorkflow }\n\n/**\n * A saved automation of the site, read through the index of the plugin that\n * keeps it, or `null` for one that does not exist, was deleted, or that no\n * plugin here keeps.\n */\nexport async function readAiWorkflowTarget(\n input: { hostId: string; type: AiWorkflowTargetType; id: string },\n): Promise<AiWorkflowTarget | null> {\n const owner = pluginRecordIndex(input.type)\n const record = owner ? await owner.index.get({ hostId: input.hostId, id: input.id }) : null\n if (!record) return null\n const stored = { name: record.name, ...record.facts }\n return input.type === 'action'\n ? { type: 'action', id: record.id, name: record.name, action: stored as unknown as AiAutomation }\n : { type: 'workflow', id: record.id, name: record.name, workflow: stored as unknown as AiIndexedWorkflow }\n}\n\nexport type AiWorkflowRunRead =\n | { ok: true; run: AiRunRecord }\n | { ok: false; reason: 'gone' | 'not-failed' }\n\n/**\n * One run of an automation, as the site's activity log recorded it: gone when\n * there is no such entry or it belongs to another automation, and refused\n * when it did not fail. Only what the run history shows is read — never the\n * event's payload.\n */\nexport async function readAiWorkflowRun(\n firestore: Firestore,\n input: { hostId: string; targetId: string; runId: string },\n): Promise<AiWorkflowRunRead> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(input.hostId)\n .collection('activity')\n .doc(input.runId)\n .get()\n const data = snapshot.exists ? ((snapshot.data() ?? {}) as Data) : null\n const target = data?.['target'] as { id?: unknown } | undefined\n if (!data || target?.id !== input.targetId) return { ok: false, reason: 'gone' }\n const run: AiRunRecord = {\n result: data['result'],\n trigger: data['trigger'],\n summary: data['summary'],\n action: data['action'],\n createdAt: data['createdAt'],\n }\n return actionRunResult(run as never) === 'failed' ? { ok: true, run } : { ok: false, reason: 'not-failed' }\n}\n"],"names":["actionRunResult","isFormArchived","pluginRecordIndex","visibleToHost","listOrgContainers","AI_WORKFLOW_RECORDS_WINDOW","AI_WORKFLOW_PIPELINES_WINDOW","text","value","trim","named","query","fields","keep","nameOf","snapshot","select","limit","get","docs","map","doc","data","id","filter","name","record","live","placedCampaigns","firestore","orgId","hostId","containers","container","visibleTo","indexed","kind","scope","owner","records","index","list","readForms","host","collection","Array","isArray","field","Boolean","readAiAutomationRecords","input","org","wanted","only","includes","none","Promise","resolve","forms","datasets","lists","campaigns","workflows","webhooks","stages","all","facts","crm","readStages","pipelines","flatMap","stage","readAiWorkflowFunctions","readAiWorkflowTarget","type","stored","action","workflow","readAiWorkflowRun","runId","exists","target","targetId","ok","reason","run","result","trigger","summary","createdAt"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/jobs/fixtures/ai-free-page-built-recording.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiEvalRecording } from '../../runtime/ai-eval'\n\n/**\n * RECORDED, NOT GOLDEN: the live recording of the Free page brief\n * (`page-free-law-firm-about`, 2026-09-17) that built the page end to end, on\n * `claude-sonnet-5` at 254 metered credits. A recording file is never\n * committed; this is the part a spec needs, copied from it.\n *\n * Trimmed to the page's first section: the root lists only that section, and\n * every node kept is as the draft stored it. The plan, the metered steps and\n * the grade are whole. The grader was shown the tree alone, and its notes mark\n * the page down for a layout, a main landmark and a listing the page had: the\n * plan created `main-layout`, a layout pass built it, and the screen carried\n * `/about` with a search title and description.\n */\nexport const AI_FREE_PAGE_BUILT_RECORDING: AiEvalRecording = {\n caseId: 'page-free-law-firm-about',\n candidate: {\n source: 'recorded',\n scope: 'full',\n step: 'job.page',\n model: 'claude-sonnet-5',\n effort: null,\n plan: {\n reuse: [],\n create: [\n {\n kind: 'layout',\n name: 'main-layout',\n why: 'Site has no layout yet; header, navigation and footer must live in a layout rather than on the page.',\n duplicateOf: null,\n fields: [\n 'header: logo + nav links (Home, About, Practice Areas, Contact)',\n 'footer: firm name, address, phone, copyright',\n ],\n },\n ],\n screens: [\n {\n title: 'About Brightwater Law',\n slug: '/about',\n layout: 'new:main-layout',\n template: null,\n duplicateOf: null,\n nav: true,\n seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',\n seoDescription: 'Learn who Brightwater Law is, the four areas we practice, how we work with clients, and request a consultation.',\n sections: [\n { name: 'hero', uses: [], items: 0 },\n { name: 'who we are', uses: [], items: 0 },\n { name: 'practice area: business & corporate law', uses: [], items: 1 },\n { name: 'practice area: real estate law', uses: [], items: 1 },\n { name: 'practice area: family law', uses: [], items: 1 },\n { name: 'practice area: estate planning', uses: [], items: 1 },\n { name: 'how we work with clients', uses: [], items: 3 },\n { name: 'request a consultation form', uses: [], items: 0 },\n ],\n },\n ],\n },\n answer: {\n tree: {\n rootId: '_@_',\n nodes: {\n '_@_': { $id: '_@_', componentId: 'div', parentId: null, nodes: ['ai-eval-page-fr-section-1'] },\n 'ai-eval-page-fr-section-1': {\n $id: 'ai-eval-page-fr-section-1',\n type: 'node',\n componentId: 'section',\n pluginId: 'mui',\n parentId: '_@_',\n nodes: ['kUk5o1Ekv0'],\n props: { element: 'section', ariaLabel: 'Hero' },\n },\n kUk5o1Ekv0: {\n $id: 'kUk5o1Ekv0',\n type: 'node',\n componentId: 'muiStack',\n pluginId: 'mui',\n parentId: 'ai-eval-page-fr-section-1',\n nodes: ['ggok0RX2Xx', 'Nh44I1DEPm', 'IxdLHxS6ds', 'Jgquy_nSMC'],\n props: { direction: 'column', alignItems: 'flex-start', justifyContent: 'center' },\n sx: { py: 8, gap: 2 },\n },\n ggok0RX2Xx: {\n $id: 'ggok0RX2Xx',\n type: 'node',\n componentId: 'muiTypography',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: { children: 'Brightwater Law', variant: 'subtitle1', component: 'p' },\n sx: { color: 'secondary.main' },\n },\n Nh44I1DEPm: {\n $id: 'Nh44I1DEPm',\n type: 'node',\n componentId: 'muiTypography',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: { children: 'About Brightwater Law', variant: 'h1', component: 'h1' },\n },\n IxdLHxS6ds: {\n $id: 'IxdLHxS6ds',\n type: 'node',\n componentId: 'muiTypography',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: {\n children: 'We are a client-focused law firm guiding individuals, families and businesses through the moments that matter most, with',\n variant: 'h5',\n component: 'p',\n },\n sx: { color: 'text.secondary', maxWidth: 'sm' },\n },\n Jgquy_nSMC: {\n $id: 'Jgquy_nSMC',\n type: 'node',\n componentId: 'muiButton',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: {\n children: 'Request a Consultation',\n color: 'primary',\n variant: 'contained',\n size: 'large',\n target: '_self',\n },\n },\n },\n },\n },\n usage: { inputTokens: 11_889, outputTokens: 8_973, cacheReadTokens: 67_194, cacheWriteTokens: 18_103 },\n steps: [\n {\n step: 'job.plan',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 1_064, outputTokens: 1_320, cacheReadTokens: 4_293, cacheWriteTokens: 4_293 },\n estCostUsd: 0.040379,\n credits: 41,\n },\n {\n step: 'job.layout',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 987, outputTokens: 1_113, cacheReadTokens: 6_797, cacheWriteTokens: 6_797 },\n estCostUsd: 0.047184,\n credits: 48,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 590, outputTokens: 525, cacheReadTokens: 0, cacheWriteTokens: 7_013 },\n estCostUsd: 0.035944,\n credits: 36,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 597, outputTokens: 520, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.011695,\n credits: 12,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 703, outputTokens: 935, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.018238,\n credits: 19,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 715, outputTokens: 924, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.018109,\n credits: 19,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 725, outputTokens: 511, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.011944,\n credits: 12,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 737, outputTokens: 1_014, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.019525,\n credits: 20,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 747, outputTokens: 931, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.01831,\n credits: 19,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 1_363, outputTokens: 968, cacheReadTokens: 14_026, cacheWriteTokens: 0 },\n estCostUsd: 0.022817,\n credits: 23,\n },\n {\n step: 'job.seo',\n model: 'claude-haiku-4-5',\n usage: { inputTokens: 3_661, outputTokens: 212, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0.004721,\n credits: 5,\n },\n ],\n rubric: {\n structure: 2,\n copy: 3,\n reuse: 4,\n grader: 'claude-opus-5',\n notes: 'Structure is the weakest: no layout is declared or created (the job allows one, and the site has none, so the page ships with no header/nav/footer), there is no `main` landmark, and no slug, search title/description or nav entry travels with the screen. Inside the tree the four practice sections are built three different ways (plain list, primary/secondary list, bare paragraph), and one ListItemText is left completely empty. Copy is otherwise in the right professional register, but the hero subhead is cut off mid-sentence (\"...the moments that matter most, with\"), and the hero button has no destination.',\n },\n },\n}\n"],"names":["AI_FREE_PAGE_BUILT_RECORDING","caseId","candidate","source","scope","step","model","effort","plan","reuse","create","kind","name","why","duplicateOf","fields","screens","title","slug","layout","template","nav","seoTitle","seoDescription","sections","uses","items","answer","tree","rootId","nodes","$id","componentId","parentId","type","pluginId","props","element","ariaLabel","kUk5o1Ekv0","direction","alignItems","justifyContent","sx","py","gap","ggok0RX2Xx","children","variant","component","color","Nh44I1DEPm","IxdLHxS6ds","maxWidth","Jgquy_nSMC","size","target","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","steps","estCostUsd","credits","rubric","structure","copy","grader","notes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/jobs/fixtures/ai-free-page-built-recording.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiEvalRecording } from '../../runtime/ai-eval'\n\n/**\n * RECORDED, NOT GOLDEN: the live recording of the Free page brief\n * (`page-free-law-firm-about`, 2026-09-17) that built the page end to end, on\n * `claude-sonnet-5` at 254 metered credits. A recording file is never\n * committed; this is the part a spec needs, copied from it.\n *\n * Trimmed to the page's first section: the root lists only that section, and\n * every node kept is as the draft stored it. The plan, the metered steps and\n * the grade are whole. The grader was shown the tree alone, and its notes mark\n * the page down for a layout, a main landmark and a listing the page had: the\n * plan created `main-layout`, a layout pass built it, and the screen carried\n * `/about` with a search title and description.\n */\nexport const AI_FREE_PAGE_BUILT_RECORDING: AiEvalRecording = {\n caseId: 'page-free-law-firm-about',\n candidate: {\n source: 'recorded',\n scope: 'full',\n step: 'job.page',\n model: 'claude-sonnet-5',\n effort: null,\n plan: {\n reuse: [],\n create: [\n {\n kind: 'layout',\n name: 'main-layout',\n why: 'Site has no layout yet; header, navigation and footer must live in a layout rather than on the page.',\n duplicateOf: null,\n fields: [\n 'header: logo + nav links (Home, About, Practice Areas, Contact)',\n 'footer: firm name, address, phone, copyright',\n ],\n },\n ],\n screens: [\n {\n title: 'About Brightwater Law',\n slug: '/about',\n layout: 'new:main-layout',\n template: null,\n record: null,\n duplicateOf: null,\n nav: true,\n seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',\n seoDescription: 'Learn who Brightwater Law is, the four areas we practice, how we work with clients, and request a consultation.',\n sections: [\n { name: 'hero', uses: [], items: 0 },\n { name: 'who we are', uses: [], items: 0 },\n { name: 'practice area: business & corporate law', uses: [], items: 1 },\n { name: 'practice area: real estate law', uses: [], items: 1 },\n { name: 'practice area: family law', uses: [], items: 1 },\n { name: 'practice area: estate planning', uses: [], items: 1 },\n { name: 'how we work with clients', uses: [], items: 3 },\n { name: 'request a consultation form', uses: [], items: 0 },\n ],\n },\n ],\n },\n answer: {\n tree: {\n rootId: '_@_',\n nodes: {\n '_@_': { $id: '_@_', componentId: 'div', parentId: null, nodes: ['ai-eval-page-fr-section-1'] },\n 'ai-eval-page-fr-section-1': {\n $id: 'ai-eval-page-fr-section-1',\n type: 'node',\n componentId: 'section',\n pluginId: 'mui',\n parentId: '_@_',\n nodes: ['kUk5o1Ekv0'],\n props: { element: 'section', ariaLabel: 'Hero' },\n },\n kUk5o1Ekv0: {\n $id: 'kUk5o1Ekv0',\n type: 'node',\n componentId: 'muiStack',\n pluginId: 'mui',\n parentId: 'ai-eval-page-fr-section-1',\n nodes: ['ggok0RX2Xx', 'Nh44I1DEPm', 'IxdLHxS6ds', 'Jgquy_nSMC'],\n props: { direction: 'column', alignItems: 'flex-start', justifyContent: 'center' },\n sx: { py: 8, gap: 2 },\n },\n ggok0RX2Xx: {\n $id: 'ggok0RX2Xx',\n type: 'node',\n componentId: 'muiTypography',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: { children: 'Brightwater Law', variant: 'subtitle1', component: 'p' },\n sx: { color: 'secondary.main' },\n },\n Nh44I1DEPm: {\n $id: 'Nh44I1DEPm',\n type: 'node',\n componentId: 'muiTypography',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: { children: 'About Brightwater Law', variant: 'h1', component: 'h1' },\n },\n IxdLHxS6ds: {\n $id: 'IxdLHxS6ds',\n type: 'node',\n componentId: 'muiTypography',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: {\n children: 'We are a client-focused law firm guiding individuals, families and businesses through the moments that matter most, with',\n variant: 'h5',\n component: 'p',\n },\n sx: { color: 'text.secondary', maxWidth: 'sm' },\n },\n Jgquy_nSMC: {\n $id: 'Jgquy_nSMC',\n type: 'node',\n componentId: 'muiButton',\n pluginId: 'mui',\n parentId: 'kUk5o1Ekv0',\n nodes: [],\n props: {\n children: 'Request a Consultation',\n color: 'primary',\n variant: 'contained',\n size: 'large',\n target: '_self',\n },\n },\n },\n },\n },\n usage: { inputTokens: 11_889, outputTokens: 8_973, cacheReadTokens: 67_194, cacheWriteTokens: 18_103 },\n steps: [\n {\n step: 'job.plan',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 1_064, outputTokens: 1_320, cacheReadTokens: 4_293, cacheWriteTokens: 4_293 },\n estCostUsd: 0.040379,\n credits: 41,\n },\n {\n step: 'job.layout',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 987, outputTokens: 1_113, cacheReadTokens: 6_797, cacheWriteTokens: 6_797 },\n estCostUsd: 0.047184,\n credits: 48,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 590, outputTokens: 525, cacheReadTokens: 0, cacheWriteTokens: 7_013 },\n estCostUsd: 0.035944,\n credits: 36,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 597, outputTokens: 520, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.011695,\n credits: 12,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 703, outputTokens: 935, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.018238,\n credits: 19,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 715, outputTokens: 924, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.018109,\n credits: 19,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 725, outputTokens: 511, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.011944,\n credits: 12,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 737, outputTokens: 1_014, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.019525,\n credits: 20,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 747, outputTokens: 931, cacheReadTokens: 7_013, cacheWriteTokens: 0 },\n estCostUsd: 0.01831,\n credits: 19,\n },\n {\n step: 'job.page',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 1_363, outputTokens: 968, cacheReadTokens: 14_026, cacheWriteTokens: 0 },\n estCostUsd: 0.022817,\n credits: 23,\n },\n {\n step: 'job.seo',\n model: 'claude-haiku-4-5',\n usage: { inputTokens: 3_661, outputTokens: 212, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0.004721,\n credits: 5,\n },\n ],\n rubric: {\n structure: 2,\n copy: 3,\n reuse: 4,\n grader: 'claude-opus-5',\n notes: 'Structure is the weakest: no layout is declared or created (the job allows one, and the site has none, so the page ships with no header/nav/footer), there is no `main` landmark, and no slug, search title/description or nav entry travels with the screen. Inside the tree the four practice sections are built three different ways (plain list, primary/secondary list, bare paragraph), and one ListItemText is left completely empty. Copy is otherwise in the right professional register, but the hero subhead is cut off mid-sentence (\"...the moments that matter most, with\"), and the hero button has no destination.',\n },\n },\n}\n"],"names":["AI_FREE_PAGE_BUILT_RECORDING","caseId","candidate","source","scope","step","model","effort","plan","reuse","create","kind","name","why","duplicateOf","fields","screens","title","slug","layout","template","record","nav","seoTitle","seoDescription","sections","uses","items","answer","tree","rootId","nodes","$id","componentId","parentId","type","pluginId","props","element","ariaLabel","kUk5o1Ekv0","direction","alignItems","justifyContent","sx","py","gap","ggok0RX2Xx","children","variant","component","color","Nh44I1DEPm","IxdLHxS6ds","maxWidth","Jgquy_nSMC","size","target","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","steps","estCostUsd","credits","rubric","structure","copy","grader","notes"],"mappings":"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"}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
slug: '/about',
|
|
47
47
|
layout: 'new:main-layout',
|
|
48
48
|
template: null,
|
|
49
|
+
record: null,
|
|
49
50
|
duplicateOf: null,
|
|
50
51
|
nav: true,
|
|
51
52
|
seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',
|
|
@@ -162,6 +163,7 @@
|
|
|
162
163
|
slug: '/about',
|
|
163
164
|
layout: 'new:main-layout',
|
|
164
165
|
template: null,
|
|
166
|
+
record: null,
|
|
165
167
|
duplicateOf: null,
|
|
166
168
|
nav: true,
|
|
167
169
|
seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/jobs/fixtures/ai-free-page-recording.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AiBuildPlan } from '../../model/ai-build-plan'\nimport type { AiDoctrineTree } from '../../runtime/ai-doctrine-validators'\nimport type { AiEvalRecording } from '../../runtime/ai-eval'\n\n/**\n * RECORDED, NOT GOLDEN: the first live recording of the Free page brief\n * (`page-free-law-firm-about`, 2026-09-16), kept as the shape it was written\n * in so the specs can hold the rules to it (AGL-3040). A recording file is\n * never committed; this is the one a spec needs, copied here by hand.\n *\n * Its plan created only the layout, then placed a consultation form it never\n * declared and listed the layout among the hero section's placements. No\n * plan rule refused either, so the plan was kept, and the page step's first\n * pass stopped before building anything: the recording metered the plan\n * alone, and still fit the 300-credit wall it could prove nothing about.\n */\n\n/** The plan the plan step kept, exactly as recorded. */\nexport const AI_FREE_PAGE_RECORDED_PLAN: AiBuildPlan = {\n reuse: [],\n create: [\n {\n kind: 'layout',\n name: 'main-layout',\n why: 'Site has no layouts; header, nav and footer must live in a layout rather than on the page.',\n duplicateOf: null,\n fields: [\n 'layoutSlot:slot',\n 'header:region (logo, nav links)',\n 'footer:region (firm name, address, phone, copyright)',\n ],\n },\n ],\n screens: [\n {\n title: 'About Us',\n slug: '/about',\n layout: 'new:main-layout',\n template: null,\n duplicateOf: null,\n nav: true,\n seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',\n seoDescription:\n 'Meet Brightwater Law: our approach, the four areas we practice, how we work with clients, and how to request a consultation.',\n sections: [\n { name: 'hero', uses: ['new:main-layout'], items: 0 },\n { name: 'who we are', uses: [], items: 0 },\n { name: 'practice areas', uses: [], items: 4 },\n { name: 'how we work with clients', uses: [], items: 3 },\n { name: 'consultation request form', uses: ['new:consultation-form'], items: 0 },\n ],\n },\n ],\n}\n\n/** The whole recording, as the live run wrote it. */\nexport const AI_FREE_PAGE_STOPPED_RECORDING: AiEvalRecording = {\n caseId: 'page-free-law-firm-about',\n candidate: {\n source: 'recorded',\n scope: 'plan',\n step: 'job.page',\n model: 'claude-sonnet-5',\n effort: null,\n plan: AI_FREE_PAGE_RECORDED_PLAN,\n answer: null,\n usage: { inputTokens: 1_064, outputTokens: 1_388, cacheReadTokens: 4_264, cacheWriteTokens: 4_264 },\n steps: [\n {\n step: 'job.plan',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 1_064, outputTokens: 1_388, cacheReadTokens: 4_264, cacheWriteTokens: 4_264 },\n estCostUsd: 0.041281,\n credits: 42,\n },\n {\n step: 'job.layout',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0,\n credits: 0,\n },\n ],\n note: 'stopped: This page needs what the site does not have yet. Create the component “consultation-form” on the Components page, then describe the page again.',\n rubric: {\n structure: 2,\n copy: 4,\n reuse: 2,\n grader: 'claude-opus-5',\n notes:\n 'Structure: the consultation form is placed as \"new:consultation-form\" but never declared in the create list with fields and a rationale, so the brief\\'s form is proposed without being created, and the hero section wrongly lists the layout among what it places (the layout already belongs at screen level). Reuse: the layout creation is justified, but the four repeating practice-area blocks and three how-we-work blocks are left as loose items instead of one reusable component each with typed props filled by instances.',\n },\n },\n}\n\n/**\n * RECORDED, NOT GOLDEN: the plan the third live recording of the Free page\n * brief kept (`page-free-law-firm-about`, 2026-09-17), exactly as recorded. It\n * asked for eight sections beside the layout it creates, where the Free\n * wall's worst case fits six (AGL-3070), and it split \"the four areas we\n * practice\" into four sections of one item each (AGL-3071).\n */\nexport const AI_FREE_PAGE_BUILT_PLAN: AiBuildPlan = {\n reuse: [],\n create: [\n {\n kind: 'layout',\n name: 'main-layout',\n why: 'Site has no layout yet; header, navigation and footer must live in a layout rather than on the page.',\n duplicateOf: null,\n fields: [\n 'header: logo + nav links (Home, About, Practice Areas, Contact)',\n 'footer: firm name, address, phone, copyright',\n ],\n },\n ],\n screens: [\n {\n title: 'About Brightwater Law',\n slug: '/about',\n layout: 'new:main-layout',\n template: null,\n duplicateOf: null,\n nav: true,\n seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',\n seoDescription:\n 'Learn who Brightwater Law is, the four areas we practice, how we work with clients, and request a consultation.',\n sections: [\n { name: 'hero', uses: [], items: 0 },\n { name: 'who we are', uses: [], items: 0 },\n { name: 'practice area: business & corporate law', uses: [], items: 1 },\n { name: 'practice area: real estate law', uses: [], items: 1 },\n { name: 'practice area: family law', uses: [], items: 1 },\n { name: 'practice area: estate planning', uses: [], items: 1 },\n { name: 'how we work with clients', uses: [], items: 3 },\n { name: 'request a consultation form', uses: [], items: 0 },\n ],\n },\n ],\n}\n\n/**\n * RECORDED, NOT GOLDEN: two sections of the page the third live recording of\n * the Free page brief built (`page-free-law-firm-about`, 2026-09-17), copied\n * by hand exactly as the page stored them, under the page's own root. The\n * hero's subhead ends \"…that matter most, with\", its \"Request a Consultation\"\n * button names no destination, and the estate planning list ends on a List\n * Item whose text is empty. Every validator passed the page; the rules of\n * AGL-3072 refuse each of the three, and nothing else on it.\n */\nexport const AI_FREE_PAGE_BUILT_SECTIONS: AiDoctrineTree = {\n rootId: CANVAS_ROOT_ELEMENT_ID,\n nodes: {\n [CANVAS_ROOT_ELEMENT_ID]: { componentId: 'div', nodes: ['ai-eval-page-fr-section-1', 'ai-eval-page-fr-section-6'] },\n 'ai-eval-page-fr-section-1': { componentId: 'section', props: { element: 'section', ariaLabel: 'Hero' }, nodes: ['kUk5o1Ekv0'] },\n kUk5o1Ekv0: { componentId: 'muiStack', props: { direction: 'column', alignItems: 'flex-start', justifyContent: 'center' }, sx: { py: 8, gap: 2 }, nodes: ['ggok0RX2Xx', 'Nh44I1DEPm', 'IxdLHxS6ds', 'Jgquy_nSMC'] },\n ggok0RX2Xx: { componentId: 'muiTypography', props: { children: 'Brightwater Law', variant: 'subtitle1', component: 'p' }, sx: { color: 'secondary.main' } },\n Nh44I1DEPm: { componentId: 'muiTypography', props: { children: 'About Brightwater Law', variant: 'h1', component: 'h1' } },\n IxdLHxS6ds: { componentId: 'muiTypography', props: { children: 'We are a client-focused law firm guiding individuals, families and businesses through the moments that matter most, with', variant: 'h5', component: 'p' }, sx: { color: 'text.secondary', maxWidth: 'sm' } },\n Jgquy_nSMC: { componentId: 'muiButton', props: { children: 'Request a Consultation', color: 'primary', variant: 'contained', size: 'large', target: '_self' } },\n 'ai-eval-page-fr-section-6': { componentId: 'section', props: { element: 'section', ariaLabel: 'Practice Area: Estate Planning' }, nodes: ['2De3Q98WRY'] },\n '2De3Q98WRY': { componentId: 'muiContainer', props: { maxWidth: 'lg' }, nodes: ['Aa5nKvtAXS'] },\n Aa5nKvtAXS: { componentId: 'muiStack', props: { direction: 'column', alignItems: 'stretch' }, sx: { gap: 2, py: 6 }, nodes: ['ERmFwLkC0-', 'IdsVKwRA9T', 'ZgUV9bogTg'] },\n 'ERmFwLkC0-': { componentId: 'muiTypography', props: { variant: 'h2', component: 'h2', children: 'Estate Planning' } },\n IdsVKwRA9T: { componentId: 'muiTypography', props: { variant: 'body1', children: 'We help you plan for the future with clear, practical documents that protect what matters most to you. From wills and trusts to powers of attorney, we make sure your wishes are honored and your loved ones are spared unnecessary stress.' } },\n ZgUV9bogTg: { componentId: 'muiList', nodes: ['6m3W_Nwuxt', 'bCzxZHTbtH', '3mnFquSlTC', 'Z-lpdjMIZb'] },\n '6m3W_Nwuxt': { componentId: 'muiListItem', props: { disableGutters: true }, nodes: ['3HB6XUZteX'] },\n '3HB6XUZteX': { componentId: 'muiListItemText', props: { primary: 'Wills & Trusts', secondary: 'Drafting and updating wills and trusts that reflect your intentions and minimize complications for your heirs.' } },\n bCzxZHTbtH: { componentId: 'muiListItem', props: { disableGutters: true }, nodes: ['iafJroc-Eo'] },\n 'iafJroc-Eo': { componentId: 'muiListItemText', props: { primary: 'Powers of Attorney', secondary: 'Naming trusted decision-makers for your finances and healthcare should you become unable to act for yourself.' } },\n '3mnFquSlTC': { componentId: 'muiListItem', props: { disableGutters: true }, nodes: ['t-qUET--I-'] },\n 't-qUET--I-': { componentId: 'muiListItemText', props: { primary: 'Estate Administration', secondary: 'Guiding families through probate and trust administration with clarity and steady support.' } },\n 'Z-lpdjMIZb': { componentId: 'muiListItem', props: { disableGutters: true, divider: false }, nodes: ['p2GOnWVIgq'] },\n p2GOnWVIgq: { componentId: 'muiListItemText' },\n },\n}\n\n/**\n * RECORDED, NOT GOLDEN: the two answers the fourth live recording of the Free\n * page brief gave its first section, \"hero introduction\"\n * (`page-free-law-firm-about`, 2026-09-17 06:54Z, claude-sonnet-5), exactly\n * as the tool received them (AGL-3097). The first left its \"Request a\n * Consultation\" button with no destination and wrote a subhead past the 120\n * characters its heading style holds. The re-ask was answered with the button\n * sent to \"#consultation-form\", an anchor to the form section the plan places\n * last. The palette validator drops a bare anchor, rule 10 refused the answer\n * again, and the page stopped at its first section.\n */\nexport const AI_FREE_PAGE_HERO_ANSWERS: readonly [{ tree: string }, { tree: string }] = [\n {\n tree: '{\"rootId\":\"root\",\"nodes\":{\\n\"root\":{\"componentId\":\"div\",\"props\":{},\"nodes\":[\"section1\"]},\\n\"section1\":{\"componentId\":\"section\",\"props\":{\"element\":\"section\",\"ariaLabel\":\"Introduction\"},\"nodes\":[\"container1\"]},\\n\"container1\":{\"componentId\":\"muiContainer\",\"props\":{\"maxWidth\":\"md\"},\"nodes\":[\"stack1\"]},\\n\"stack1\":{\"componentId\":\"muiStack\",\"props\":{\"direction\":\"column\",\"alignItems\":\"flex-start\",\"justifyContent\":\"center\"},\"sx\":{\"gap\":3,\"py\":8},\"nodes\":[\"eyebrow\",\"h1\",\"subtitle\",\"ctaStack\"]},\\n\"eyebrow\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Brightwater Law\",\"variant\":\"subtitle1\",\"component\":\"p\"},\"sx\":{\"color\":\"secondary.main\"}},\\n\"h1\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Counsel you can trust, from people who listen first\",\"variant\":\"h1\",\"component\":\"h1\"}},\\n\"subtitle\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"We\\'re a client-focused firm built on plain talk, careful strategy, and steady advocacy — for individuals and businesses facing decisions that matter.\",\"variant\":\"h5\",\"component\":\"p\"},\"sx\":{\"color\":\"text.secondary\",\"fontWeight\":400}},\\n\"ctaStack\":{\"componentId\":\"muiStack\",\"props\":{\"direction\":\"row\",\"alignItems\":\"center\"},\"sx\":{\"gap\":2,\"pt\":1},\"nodes\":[\"ctaButton\"]},\\n\"ctaButton\":{\"componentId\":\"muiButton\",\"props\":{\"children\":\"Request a Consultation\",\"color\":\"primary\",\"variant\":\"contained\",\"size\":\"large\",\"target\":\"_self\"}}\\n}}',\n },\n {\n tree: '{\"rootId\":\"root\",\"nodes\":{\"root\":{\"componentId\":\"div\",\"nodes\":[\"section1\"]},\"section1\":{\"componentId\":\"section\",\"props\":{\"element\":\"section\",\"ariaLabel\":\"Hero introduction\"},\"nodes\":[\"container1\"]},\"container1\":{\"componentId\":\"muiContainer\",\"props\":{\"maxWidth\":\"md\"},\"sx\":{\"paddingY\":8},\"nodes\":[\"stack1\"]},\"stack1\":{\"componentId\":\"muiStack\",\"props\":{\"direction\":\"column\",\"alignItems\":\"center\"},\"sx\":{\"gap\":3,\"textAlign\":\"center\"},\"nodes\":[\"eyebrow\",\"h1\",\"subtitle\",\"ctaLink\"]},\"eyebrow\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Brightwater Law\",\"variant\":\"subtitle1\",\"component\":\"p\"},\"sx\":{\"color\":\"secondary.main\",\"fontWeight\":600}},\"h1\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Plain talk. Careful strategy. Steady advocacy.\",\"variant\":\"h1\",\"component\":\"h1\"}},\"subtitle\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"A client-focused firm for individuals and businesses facing decisions that matter.\",\"variant\":\"h5\",\"component\":\"p\"},\"sx\":{\"color\":\"text.secondary\",\"fontWeight\":400}},\"ctaLink\":{\"componentId\":\"muiButton\",\"props\":{\"children\":\"Request a Consultation\",\"color\":\"primary\",\"variant\":\"contained\",\"size\":\"large\",\"href\":\"#consultation-form\"}}}}',\n },\n]\n"],"names":["CANVAS_ROOT_ELEMENT_ID","AI_FREE_PAGE_RECORDED_PLAN","reuse","create","kind","name","why","duplicateOf","fields","screens","title","slug","layout","template","nav","seoTitle","seoDescription","sections","uses","items","AI_FREE_PAGE_STOPPED_RECORDING","caseId","candidate","source","scope","step","model","effort","plan","answer","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","steps","estCostUsd","credits","note","rubric","structure","copy","grader","notes","AI_FREE_PAGE_BUILT_PLAN","AI_FREE_PAGE_BUILT_SECTIONS","rootId","nodes","componentId","props","element","ariaLabel","kUk5o1Ekv0","direction","alignItems","justifyContent","sx","py","gap","ggok0RX2Xx","children","variant","component","color","Nh44I1DEPm","IxdLHxS6ds","maxWidth","Jgquy_nSMC","size","target","Aa5nKvtAXS","IdsVKwRA9T","ZgUV9bogTg","disableGutters","primary","secondary","bCzxZHTbtH","divider","p2GOnWVIgq","AI_FREE_PAGE_HERO_ANSWERS","tree"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/jobs/fixtures/ai-free-page-recording.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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AiBuildPlan } from '../../model/ai-build-plan'\nimport type { AiDoctrineTree } from '../../runtime/ai-doctrine-validators'\nimport type { AiEvalRecording } from '../../runtime/ai-eval'\n\n/**\n * RECORDED, NOT GOLDEN: the first live recording of the Free page brief\n * (`page-free-law-firm-about`, 2026-09-16), kept as the shape it was written\n * in so the specs can hold the rules to it (AGL-3040). A recording file is\n * never committed; this is the one a spec needs, copied here by hand.\n *\n * Its plan created only the layout, then placed a consultation form it never\n * declared and listed the layout among the hero section's placements. No\n * plan rule refused either, so the plan was kept, and the page step's first\n * pass stopped before building anything: the recording metered the plan\n * alone, and still fit the 300-credit wall it could prove nothing about.\n */\n\n/** The plan the plan step kept, exactly as recorded. */\nexport const AI_FREE_PAGE_RECORDED_PLAN: AiBuildPlan = {\n reuse: [],\n create: [\n {\n kind: 'layout',\n name: 'main-layout',\n why: 'Site has no layouts; header, nav and footer must live in a layout rather than on the page.',\n duplicateOf: null,\n fields: [\n 'layoutSlot:slot',\n 'header:region (logo, nav links)',\n 'footer:region (firm name, address, phone, copyright)',\n ],\n },\n ],\n screens: [\n {\n title: 'About Us',\n slug: '/about',\n layout: 'new:main-layout',\n template: null,\n record: null,\n duplicateOf: null,\n nav: true,\n seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',\n seoDescription:\n 'Meet Brightwater Law: our approach, the four areas we practice, how we work with clients, and how to request a consultation.',\n sections: [\n { name: 'hero', uses: ['new:main-layout'], items: 0 },\n { name: 'who we are', uses: [], items: 0 },\n { name: 'practice areas', uses: [], items: 4 },\n { name: 'how we work with clients', uses: [], items: 3 },\n { name: 'consultation request form', uses: ['new:consultation-form'], items: 0 },\n ],\n },\n ],\n}\n\n/** The whole recording, as the live run wrote it. */\nexport const AI_FREE_PAGE_STOPPED_RECORDING: AiEvalRecording = {\n caseId: 'page-free-law-firm-about',\n candidate: {\n source: 'recorded',\n scope: 'plan',\n step: 'job.page',\n model: 'claude-sonnet-5',\n effort: null,\n plan: AI_FREE_PAGE_RECORDED_PLAN,\n answer: null,\n usage: { inputTokens: 1_064, outputTokens: 1_388, cacheReadTokens: 4_264, cacheWriteTokens: 4_264 },\n steps: [\n {\n step: 'job.plan',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 1_064, outputTokens: 1_388, cacheReadTokens: 4_264, cacheWriteTokens: 4_264 },\n estCostUsd: 0.041281,\n credits: 42,\n },\n {\n step: 'job.layout',\n model: 'claude-sonnet-5',\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0,\n credits: 0,\n },\n ],\n note: 'stopped: This page needs what the site does not have yet. Create the component “consultation-form” on the Components page, then describe the page again.',\n rubric: {\n structure: 2,\n copy: 4,\n reuse: 2,\n grader: 'claude-opus-5',\n notes:\n 'Structure: the consultation form is placed as \"new:consultation-form\" but never declared in the create list with fields and a rationale, so the brief\\'s form is proposed without being created, and the hero section wrongly lists the layout among what it places (the layout already belongs at screen level). Reuse: the layout creation is justified, but the four repeating practice-area blocks and three how-we-work blocks are left as loose items instead of one reusable component each with typed props filled by instances.',\n },\n },\n}\n\n/**\n * RECORDED, NOT GOLDEN: the plan the third live recording of the Free page\n * brief kept (`page-free-law-firm-about`, 2026-09-17), exactly as recorded. It\n * asked for eight sections beside the layout it creates, where the Free\n * wall's worst case fits six (AGL-3070), and it split \"the four areas we\n * practice\" into four sections of one item each (AGL-3071).\n */\nexport const AI_FREE_PAGE_BUILT_PLAN: AiBuildPlan = {\n reuse: [],\n create: [\n {\n kind: 'layout',\n name: 'main-layout',\n why: 'Site has no layout yet; header, navigation and footer must live in a layout rather than on the page.',\n duplicateOf: null,\n fields: [\n 'header: logo + nav links (Home, About, Practice Areas, Contact)',\n 'footer: firm name, address, phone, copyright',\n ],\n },\n ],\n screens: [\n {\n title: 'About Brightwater Law',\n slug: '/about',\n layout: 'new:main-layout',\n template: null,\n record: null,\n duplicateOf: null,\n nav: true,\n seoTitle: 'About Brightwater Law | Our Firm & Practice Areas',\n seoDescription:\n 'Learn who Brightwater Law is, the four areas we practice, how we work with clients, and request a consultation.',\n sections: [\n { name: 'hero', uses: [], items: 0 },\n { name: 'who we are', uses: [], items: 0 },\n { name: 'practice area: business & corporate law', uses: [], items: 1 },\n { name: 'practice area: real estate law', uses: [], items: 1 },\n { name: 'practice area: family law', uses: [], items: 1 },\n { name: 'practice area: estate planning', uses: [], items: 1 },\n { name: 'how we work with clients', uses: [], items: 3 },\n { name: 'request a consultation form', uses: [], items: 0 },\n ],\n },\n ],\n}\n\n/**\n * RECORDED, NOT GOLDEN: two sections of the page the third live recording of\n * the Free page brief built (`page-free-law-firm-about`, 2026-09-17), copied\n * by hand exactly as the page stored them, under the page's own root. The\n * hero's subhead ends \"…that matter most, with\", its \"Request a Consultation\"\n * button names no destination, and the estate planning list ends on a List\n * Item whose text is empty. Every validator passed the page; the rules of\n * AGL-3072 refuse each of the three, and nothing else on it.\n */\nexport const AI_FREE_PAGE_BUILT_SECTIONS: AiDoctrineTree = {\n rootId: CANVAS_ROOT_ELEMENT_ID,\n nodes: {\n [CANVAS_ROOT_ELEMENT_ID]: { componentId: 'div', nodes: ['ai-eval-page-fr-section-1', 'ai-eval-page-fr-section-6'] },\n 'ai-eval-page-fr-section-1': { componentId: 'section', props: { element: 'section', ariaLabel: 'Hero' }, nodes: ['kUk5o1Ekv0'] },\n kUk5o1Ekv0: { componentId: 'muiStack', props: { direction: 'column', alignItems: 'flex-start', justifyContent: 'center' }, sx: { py: 8, gap: 2 }, nodes: ['ggok0RX2Xx', 'Nh44I1DEPm', 'IxdLHxS6ds', 'Jgquy_nSMC'] },\n ggok0RX2Xx: { componentId: 'muiTypography', props: { children: 'Brightwater Law', variant: 'subtitle1', component: 'p' }, sx: { color: 'secondary.main' } },\n Nh44I1DEPm: { componentId: 'muiTypography', props: { children: 'About Brightwater Law', variant: 'h1', component: 'h1' } },\n IxdLHxS6ds: { componentId: 'muiTypography', props: { children: 'We are a client-focused law firm guiding individuals, families and businesses through the moments that matter most, with', variant: 'h5', component: 'p' }, sx: { color: 'text.secondary', maxWidth: 'sm' } },\n Jgquy_nSMC: { componentId: 'muiButton', props: { children: 'Request a Consultation', color: 'primary', variant: 'contained', size: 'large', target: '_self' } },\n 'ai-eval-page-fr-section-6': { componentId: 'section', props: { element: 'section', ariaLabel: 'Practice Area: Estate Planning' }, nodes: ['2De3Q98WRY'] },\n '2De3Q98WRY': { componentId: 'muiContainer', props: { maxWidth: 'lg' }, nodes: ['Aa5nKvtAXS'] },\n Aa5nKvtAXS: { componentId: 'muiStack', props: { direction: 'column', alignItems: 'stretch' }, sx: { gap: 2, py: 6 }, nodes: ['ERmFwLkC0-', 'IdsVKwRA9T', 'ZgUV9bogTg'] },\n 'ERmFwLkC0-': { componentId: 'muiTypography', props: { variant: 'h2', component: 'h2', children: 'Estate Planning' } },\n IdsVKwRA9T: { componentId: 'muiTypography', props: { variant: 'body1', children: 'We help you plan for the future with clear, practical documents that protect what matters most to you. From wills and trusts to powers of attorney, we make sure your wishes are honored and your loved ones are spared unnecessary stress.' } },\n ZgUV9bogTg: { componentId: 'muiList', nodes: ['6m3W_Nwuxt', 'bCzxZHTbtH', '3mnFquSlTC', 'Z-lpdjMIZb'] },\n '6m3W_Nwuxt': { componentId: 'muiListItem', props: { disableGutters: true }, nodes: ['3HB6XUZteX'] },\n '3HB6XUZteX': { componentId: 'muiListItemText', props: { primary: 'Wills & Trusts', secondary: 'Drafting and updating wills and trusts that reflect your intentions and minimize complications for your heirs.' } },\n bCzxZHTbtH: { componentId: 'muiListItem', props: { disableGutters: true }, nodes: ['iafJroc-Eo'] },\n 'iafJroc-Eo': { componentId: 'muiListItemText', props: { primary: 'Powers of Attorney', secondary: 'Naming trusted decision-makers for your finances and healthcare should you become unable to act for yourself.' } },\n '3mnFquSlTC': { componentId: 'muiListItem', props: { disableGutters: true }, nodes: ['t-qUET--I-'] },\n 't-qUET--I-': { componentId: 'muiListItemText', props: { primary: 'Estate Administration', secondary: 'Guiding families through probate and trust administration with clarity and steady support.' } },\n 'Z-lpdjMIZb': { componentId: 'muiListItem', props: { disableGutters: true, divider: false }, nodes: ['p2GOnWVIgq'] },\n p2GOnWVIgq: { componentId: 'muiListItemText' },\n },\n}\n\n/**\n * RECORDED, NOT GOLDEN: the two answers the fourth live recording of the Free\n * page brief gave its first section, \"hero introduction\"\n * (`page-free-law-firm-about`, 2026-09-17 06:54Z, claude-sonnet-5), exactly\n * as the tool received them (AGL-3097). The first left its \"Request a\n * Consultation\" button with no destination and wrote a subhead past the 120\n * characters its heading style holds. The re-ask was answered with the button\n * sent to \"#consultation-form\", an anchor to the form section the plan places\n * last. The palette validator drops a bare anchor, rule 10 refused the answer\n * again, and the page stopped at its first section.\n */\nexport const AI_FREE_PAGE_HERO_ANSWERS: readonly [{ tree: string }, { tree: string }] = [\n {\n tree: '{\"rootId\":\"root\",\"nodes\":{\\n\"root\":{\"componentId\":\"div\",\"props\":{},\"nodes\":[\"section1\"]},\\n\"section1\":{\"componentId\":\"section\",\"props\":{\"element\":\"section\",\"ariaLabel\":\"Introduction\"},\"nodes\":[\"container1\"]},\\n\"container1\":{\"componentId\":\"muiContainer\",\"props\":{\"maxWidth\":\"md\"},\"nodes\":[\"stack1\"]},\\n\"stack1\":{\"componentId\":\"muiStack\",\"props\":{\"direction\":\"column\",\"alignItems\":\"flex-start\",\"justifyContent\":\"center\"},\"sx\":{\"gap\":3,\"py\":8},\"nodes\":[\"eyebrow\",\"h1\",\"subtitle\",\"ctaStack\"]},\\n\"eyebrow\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Brightwater Law\",\"variant\":\"subtitle1\",\"component\":\"p\"},\"sx\":{\"color\":\"secondary.main\"}},\\n\"h1\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Counsel you can trust, from people who listen first\",\"variant\":\"h1\",\"component\":\"h1\"}},\\n\"subtitle\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"We\\'re a client-focused firm built on plain talk, careful strategy, and steady advocacy — for individuals and businesses facing decisions that matter.\",\"variant\":\"h5\",\"component\":\"p\"},\"sx\":{\"color\":\"text.secondary\",\"fontWeight\":400}},\\n\"ctaStack\":{\"componentId\":\"muiStack\",\"props\":{\"direction\":\"row\",\"alignItems\":\"center\"},\"sx\":{\"gap\":2,\"pt\":1},\"nodes\":[\"ctaButton\"]},\\n\"ctaButton\":{\"componentId\":\"muiButton\",\"props\":{\"children\":\"Request a Consultation\",\"color\":\"primary\",\"variant\":\"contained\",\"size\":\"large\",\"target\":\"_self\"}}\\n}}',\n },\n {\n tree: '{\"rootId\":\"root\",\"nodes\":{\"root\":{\"componentId\":\"div\",\"nodes\":[\"section1\"]},\"section1\":{\"componentId\":\"section\",\"props\":{\"element\":\"section\",\"ariaLabel\":\"Hero introduction\"},\"nodes\":[\"container1\"]},\"container1\":{\"componentId\":\"muiContainer\",\"props\":{\"maxWidth\":\"md\"},\"sx\":{\"paddingY\":8},\"nodes\":[\"stack1\"]},\"stack1\":{\"componentId\":\"muiStack\",\"props\":{\"direction\":\"column\",\"alignItems\":\"center\"},\"sx\":{\"gap\":3,\"textAlign\":\"center\"},\"nodes\":[\"eyebrow\",\"h1\",\"subtitle\",\"ctaLink\"]},\"eyebrow\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Brightwater Law\",\"variant\":\"subtitle1\",\"component\":\"p\"},\"sx\":{\"color\":\"secondary.main\",\"fontWeight\":600}},\"h1\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"Plain talk. Careful strategy. Steady advocacy.\",\"variant\":\"h1\",\"component\":\"h1\"}},\"subtitle\":{\"componentId\":\"muiTypography\",\"props\":{\"children\":\"A client-focused firm for individuals and businesses facing decisions that matter.\",\"variant\":\"h5\",\"component\":\"p\"},\"sx\":{\"color\":\"text.secondary\",\"fontWeight\":400}},\"ctaLink\":{\"componentId\":\"muiButton\",\"props\":{\"children\":\"Request a Consultation\",\"color\":\"primary\",\"variant\":\"contained\",\"size\":\"large\",\"href\":\"#consultation-form\"}}}}',\n },\n]\n"],"names":["CANVAS_ROOT_ELEMENT_ID","AI_FREE_PAGE_RECORDED_PLAN","reuse","create","kind","name","why","duplicateOf","fields","screens","title","slug","layout","template","record","nav","seoTitle","seoDescription","sections","uses","items","AI_FREE_PAGE_STOPPED_RECORDING","caseId","candidate","source","scope","step","model","effort","plan","answer","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","steps","estCostUsd","credits","note","rubric","structure","copy","grader","notes","AI_FREE_PAGE_BUILT_PLAN","AI_FREE_PAGE_BUILT_SECTIONS","rootId","nodes","componentId","props","element","ariaLabel","kUk5o1Ekv0","direction","alignItems","justifyContent","sx","py","gap","ggok0RX2Xx","children","variant","component","color","Nh44I1DEPm","IxdLHxS6ds","maxWidth","Jgquy_nSMC","size","target","Aa5nKvtAXS","IdsVKwRA9T","ZgUV9bogTg","disableGutters","primary","secondary","bCzxZHTbtH","divider","p2GOnWVIgq","AI_FREE_PAGE_HERO_ANSWERS","tree"],"mappings":"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"}
|
|
@@ -478,6 +478,7 @@ function brief(input) {
|
|
|
478
478
|
slug: input.slug,
|
|
479
479
|
layout: input.layout,
|
|
480
480
|
template: null,
|
|
481
|
+
record: null,
|
|
481
482
|
duplicateOf: null,
|
|
482
483
|
nav: input.nav,
|
|
483
484
|
seoTitle: input.seo.title,
|
|
@@ -1681,6 +1682,7 @@ export const AI_PAGE_CREATION_FIXTURE = (()=>{
|
|
|
1681
1682
|
slug: '/roof-repair',
|
|
1682
1683
|
layout: 'new:Harbor Roofing site',
|
|
1683
1684
|
template: null,
|
|
1685
|
+
record: null,
|
|
1684
1686
|
duplicateOf: null,
|
|
1685
1687
|
nav: true,
|
|
1686
1688
|
seoTitle: seo.title,
|