@aglyn/plugins-ai 1.0.0-beta.217 → 1.0.0-beta.219
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/README.md +2 -2
- package/package.json +17 -17
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/activity/ai-activity-actions.js +10 -2
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider.component.js +2 -2
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.js +3 -3
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +1 -1
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +8 -0
- package/src/lib/components/ai-job-plan.component.js +21 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
- package/src/lib/components/ai-permissions-on-host.component.js +12 -3
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
- package/src/lib/components/ai-product-import-option.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +124 -0
- package/src/lib/components/ai-product-zones.js +32 -0
- package/src/lib/components/ai-product-zones.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-seo-audit-card.component.js +35 -92
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +21 -15
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -2
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
- package/src/lib/jobs/ai-email-bindings.js +75 -42
- package/src/lib/jobs/ai-email-bindings.js.map +1 -1
- package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-campaign-step.js +5 -10
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
- package/src/lib/jobs/ai-job-component-step.js +9 -3
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
- package/src/lib/jobs/ai-job-drafts.js +12 -5
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-email-step.js +1 -3
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +1 -1
- package/src/lib/jobs/ai-job-generation.js +10 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +1 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
- package/src/lib/jobs/ai-job-page-sections.js +7 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +7 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-conformance.js +33 -0
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-plan-step.js +29 -17
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
- package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-products-step.js +54 -32
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
- package/src/lib/jobs/ai-job-seo-step.js +35 -108
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +1 -0
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-theme-step.js +1 -1
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +17 -9
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +19 -11
- package/src/lib/jobs/ai-workflow-records.js +60 -27
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-allotments.d.ts +11 -3
- package/src/lib/model/ai-allotments.js +21 -4
- package/src/lib/model/ai-allotments.js.map +1 -1
- package/src/lib/model/ai-automation-outline.d.ts +21 -2
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +57 -0
- package/src/lib/model/ai-build-plan.js +116 -7
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-seo.d.ts +11 -51
- package/src/lib/model/ai-seo.js +0 -17
- package/src/lib/model/ai-seo.js.map +1 -1
- package/src/lib/model/ai-site-inventory.js +1 -1
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-template-subjects.js +2 -2
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/model/ai-theme-proposal.d.ts +1 -1
- package/src/lib/model/ai-theme-proposal.js +1 -1
- package/src/lib/model/ai-theme-proposal.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +1 -3
- package/src/lib/model/ai-workflow-job.js +1 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +149 -0
- package/src/lib/plan-entitlements.js +168 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
- package/src/lib/runtime/ai-doctrine-validators.js +179 -23
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +29 -14
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +8 -1
- package/src/lib/runtime/ai-eval.js +29 -2
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +3 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +21 -255
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +7 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +13 -63
- package/src/lib/runtime/seo-audit.js +22 -207
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +1 -1
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +4 -25
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +2 -19
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/server/ai-assist-prompts.js +3 -3
- package/src/lib/server/ai-assist-prompts.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +8 -1
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +11 -10
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.js +23 -23
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
- package/src/lib/testing/stand-in-automation-index.js +102 -0
- package/src/lib/testing/stand-in-automation-index.js.map +1 -0
- package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
- package/src/lib/testing/stand-in-list-send-time.js +77 -0
- package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
- package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
- package/src/lib/testing/stand-in-pipeline-index.js +59 -0
- package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
- package/src/lib/testing/stand-in-product-index.d.ts +20 -0
- package/src/lib/testing/stand-in-product-index.js +105 -0
- package/src/lib/testing/stand-in-product-index.js.map +1 -0
- package/src/lib/tools/ai-component-tool.js +2 -2
- package/src/lib/tools/ai-component-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +6 -18
- package/src/lib/tools/ai-seo-tool.js +7 -38
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
- package/src/lib/tools/ai-workflow-tool.js +1 -1
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.d.ts +3 -0
- package/src/lib/usage/ai-allotment-alerts.js +20 -8
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +2 -1
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-signal-mining.js +1 -1
- package/src/lib/usage/assist-signal-mining.js.map +1 -1
- package/src/lib/usage-axes.d.ts +39 -0
- package/src/lib/usage-axes.js +70 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +0 -61
- package/src/lib/runtime/seo-page-facts.js +0 -150
- package/src/lib/runtime/seo-page-facts.js.map +0 -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/workflows'\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 AI_WORKFLOWS_PLUGIN_ID,\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 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(firestore, { 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/** Whether the workflows plugin runs on this site: switched on, and past its release flag. */\nasync function workflowsPluginRefusal(\n context: Parameters<AiJobAdmission>[0],\n hostId: string,\n): Promise<AiJobAdmissionRefusal | null> {\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([AI_WORKFLOWS_PLUGIN_ID], {\n orgId: context.orgId,\n authorization: null,\n })\n return released.includes(AI_WORKFLOWS_PLUGIN_ID) && isHostPluginEnabled(org, host, AI_WORKFLOWS_PLUGIN_ID)\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, pluginId: AI_WORKFLOWS_PLUGIN_ID, 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(context.firestore, { 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","AI_WORKFLOWS_PLUGIN_ID","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","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","released","authorization","includes","createAiWorkflowJobAdmission","kind","drafts","pluginId","owner","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 {\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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"}
|
|
@@ -15,17 +15,21 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import type { HostAction } from '@aglyn/aglyn/app-utils/actions';
|
|
18
|
-
import type { HostWorkflow } from '@aglyn/aglyn/app-utils/workflows';
|
|
19
18
|
import type { AiAutomationNamedRecord, AiAutomationRecords } from '../model/ai-automation-draft';
|
|
20
|
-
import type { AiRunRecord } from '../model/ai-automation-outline';
|
|
19
|
+
import type { AiIndexedWorkflow, AiRunRecord } from '../model/ai-automation-outline';
|
|
21
20
|
import type { AiWorkflowTargetType } from '../model/ai-workflow-job';
|
|
22
21
|
/**
|
|
23
|
-
* What a `workflow` job reads (AGL-2919),
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* What a `workflow` job reads (AGL-2919), scoped to the job's own site and
|
|
23
|
+
* org: the records a drafted automation's words are looked up among, the
|
|
24
|
+
* automation an explanation reads, and the run it explains. Every read is
|
|
25
|
+
* windowed the way the Actions editor's pickers are, so a lookup never reads
|
|
26
|
+
* more than the editor would offer.
|
|
27
|
+
*
|
|
28
|
+
* The site's workflows, webhooks and saved actions are the workflows plugin's
|
|
29
|
+
* records, so they are read through the indexes that plugin publishes
|
|
30
|
+
* (`workflow`, `webhook`, `action` — AGL-3080), never from its collections:
|
|
31
|
+
* where no plugin keeps a kind, there are none to name. The rest are read
|
|
32
|
+
* through the Admin SDK as projections of the fields named here.
|
|
29
33
|
*/
|
|
30
34
|
type Firestore = FirebaseFirestore.Firestore;
|
|
31
35
|
/** Records of one kind a lookup reads: the Actions editor's picker window. */
|
|
@@ -55,10 +59,14 @@ export type AiWorkflowTarget = {
|
|
|
55
59
|
type: 'workflow';
|
|
56
60
|
id: string;
|
|
57
61
|
name: string;
|
|
58
|
-
workflow:
|
|
62
|
+
workflow: AiIndexedWorkflow;
|
|
59
63
|
};
|
|
60
|
-
/**
|
|
61
|
-
|
|
64
|
+
/**
|
|
65
|
+
* A saved automation of the site, read through the index of the plugin that
|
|
66
|
+
* keeps it, or `null` for one that does not exist, was deleted, or that no
|
|
67
|
+
* plugin here keeps.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readAiWorkflowTarget(input: {
|
|
62
70
|
hostId: string;
|
|
63
71
|
type: AiWorkflowTargetType;
|
|
64
72
|
id: string;
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/ import {
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { actionRunResult } from "@aglyn/aglyn/app-utils/activity-presenter";
|
|
17
18
|
import { datasetDisplayName } from "@aglyn/aglyn/app-utils/datasets";
|
|
18
19
|
import { isFormArchived } from "@aglyn/aglyn/app-utils/forms";
|
|
20
|
+
import { pluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
|
|
19
21
|
import { campaignPlacedOnHost } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
|
|
20
22
|
import { scopedToHost } from "@aglyn/tenant-data-admin/server/organizations";
|
|
21
23
|
/** Records of one kind a lookup reads: the Actions editor's picker window. */ export const AI_WORKFLOW_RECORDS_WINDOW = 100;
|
|
@@ -37,6 +39,21 @@ async function named(query, fields, keep, nameOf) {
|
|
|
37
39
|
})).filter((record)=>record.name);
|
|
38
40
|
}
|
|
39
41
|
const live = (data)=>data['deletedAt'] == null;
|
|
42
|
+
/**
|
|
43
|
+
* The live records of a kind another plugin keeps, through its index, in the
|
|
44
|
+
* editor's window; none where no plugin keeps the kind here.
|
|
45
|
+
*/ async function indexed(kind, hostId, keep = ()=>true) {
|
|
46
|
+
const owner = pluginRecordIndex(kind);
|
|
47
|
+
if (!owner) return [];
|
|
48
|
+
const { records } = await owner.index.list({
|
|
49
|
+
hostId,
|
|
50
|
+
limit: AI_WORKFLOW_RECORDS_WINDOW
|
|
51
|
+
});
|
|
52
|
+
return records.filter(keep).map(({ id, name })=>({
|
|
53
|
+
id,
|
|
54
|
+
name
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
40
57
|
/** The site's forms that are not archived, with the field names their submissions carry. */ async function readForms(host) {
|
|
41
58
|
const snapshot = await host.collection('forms').select('displayName', 'slug', 'fields', 'archivedAt').limit(AI_WORKFLOW_RECORDS_WINDOW).get();
|
|
42
59
|
return snapshot.docs.filter((doc)=>{
|
|
@@ -82,19 +99,11 @@ const live = (data)=>data['deletedAt'] == null;
|
|
|
82
99
|
], (data)=>live(data) && campaignPlacedOnHost({
|
|
83
100
|
visibleTo: data['visibleTo']
|
|
84
101
|
}, input.hostId), (data)=>text(data['name'])),
|
|
85
|
-
!wanted('workflows') ? none :
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
'name',
|
|
91
|
-
'direction',
|
|
92
|
-
'deletedAt'
|
|
93
|
-
], (data)=>live(data) && data['direction'] === 'outbound', (data)=>text(data['name'])),
|
|
94
|
-
input.crm && wanted('stages') ? scopedToHost(org.collection('pipelines'), input.hostId).select('stages', 'archivedAt').limit(AI_WORKFLOW_PIPELINES_WINDOW).get().then((snapshot)=>snapshot.docs.filter((doc)=>!(Number(doc.get('archivedAt')) > 0)).flatMap((doc)=>Array.isArray(doc.get('stages')) ? doc.get('stages') : []).map((stage)=>({
|
|
95
|
-
id: text(stage == null ? void 0 : stage['id']),
|
|
96
|
-
name: text(stage == null ? void 0 : stage['name'])
|
|
97
|
-
})).filter((stage)=>stage.id && stage.name)) : none
|
|
102
|
+
!wanted('workflows') ? none : indexed('workflow', input.hostId),
|
|
103
|
+
// Only a webhook that posts OUT is a step's target; an inbound one is an
|
|
104
|
+
// endpoint that runs a workflow.
|
|
105
|
+
!wanted('webhooks') ? none : indexed('webhook', input.hostId, (record)=>record.facts['direction'] === 'outbound'),
|
|
106
|
+
input.crm && wanted('stages') ? readStages(input.orgId, input.hostId) : none
|
|
98
107
|
]);
|
|
99
108
|
return {
|
|
100
109
|
forms,
|
|
@@ -106,29 +115,53 @@ const live = (data)=>data['deletedAt'] == null;
|
|
|
106
115
|
stages
|
|
107
116
|
};
|
|
108
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* The stages of the pipelines the site can see, asked of the plugin that keeps
|
|
120
|
+
* pipelines (its `pipeline` record index), never read from its collection. No
|
|
121
|
+
* index means no plugin keeps pipelines in this process: no stage to name.
|
|
122
|
+
*/ async function readStages(orgId, hostId) {
|
|
123
|
+
const pipelines = pluginRecordIndex('pipeline');
|
|
124
|
+
if (!pipelines) return [];
|
|
125
|
+
const { records } = await pipelines.index.list({
|
|
126
|
+
orgId,
|
|
127
|
+
hostId,
|
|
128
|
+
limit: AI_WORKFLOW_PIPELINES_WINDOW
|
|
129
|
+
});
|
|
130
|
+
return records.flatMap((record)=>Array.isArray(record.facts['stages']) ? record.facts['stages'] : []).map((stage)=>({
|
|
131
|
+
id: text(stage == null ? void 0 : stage['id']),
|
|
132
|
+
name: text(stage == null ? void 0 : stage['name'])
|
|
133
|
+
})).filter((stage)=>stage.id && stage.name);
|
|
134
|
+
}
|
|
109
135
|
/** The site's functions, for a workflow outline that says which of its calls still resolve. */ export async function readAiWorkflowFunctions(firestore, hostId) {
|
|
110
136
|
return named(firestore.collection('hosts').doc(hostId).collection('functions'), [
|
|
111
137
|
'name',
|
|
112
138
|
'deletedAt'
|
|
113
139
|
], live, (data)=>text(data['name']));
|
|
114
140
|
}
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
141
|
+
/**
|
|
142
|
+
* A saved automation of the site, read through the index of the plugin that
|
|
143
|
+
* keeps it, or `null` for one that does not exist, was deleted, or that no
|
|
144
|
+
* plugin here keeps.
|
|
145
|
+
*/ export async function readAiWorkflowTarget(input) {
|
|
146
|
+
const owner = pluginRecordIndex(input.type);
|
|
147
|
+
const record = owner ? await owner.index.get({
|
|
148
|
+
hostId: input.hostId,
|
|
149
|
+
id: input.id
|
|
150
|
+
}) : null;
|
|
151
|
+
if (!record) return null;
|
|
152
|
+
const stored = _extends({
|
|
153
|
+
name: record.name
|
|
154
|
+
}, record.facts);
|
|
122
155
|
return input.type === 'action' ? {
|
|
123
156
|
type: 'action',
|
|
124
|
-
id:
|
|
125
|
-
name,
|
|
126
|
-
action:
|
|
157
|
+
id: record.id,
|
|
158
|
+
name: record.name,
|
|
159
|
+
action: stored
|
|
127
160
|
} : {
|
|
128
161
|
type: 'workflow',
|
|
129
|
-
id:
|
|
130
|
-
name,
|
|
131
|
-
workflow:
|
|
162
|
+
id: record.id,
|
|
163
|
+
name: record.name,
|
|
164
|
+
workflow: stored
|
|
132
165
|
};
|
|
133
166
|
}
|
|
134
167
|
/**
|
|
@@ -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 { datasetDisplayName } from '@aglyn/aglyn/app-utils/datasets'\nimport { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport type { HostWorkflow } from '@aglyn/aglyn/app-utils/workflows'\nimport { campaignPlacedOnHost } from '@aglyn/shared-ui-email-campaigns/model/campaign-container'\nimport { scopedToHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport type {\n AiAutomationForm,\n AiAutomationNamedRecord,\n AiAutomationRecords,\n} from '../model/ai-automation-draft'\nimport type { AiRunRecord } from '../model/ai-automation-outline'\nimport type { AiWorkflowTargetType } from '../model/ai-workflow-job'\n\n/**\n * What a `workflow` job reads (AGL-2919), through the Admin SDK and scoped to\n * the job's own site and org: the records a drafted automation's words are\n * looked up among, the automation an explanation reads, and the run it\n * explains. Every read is a projection of the fields named here, windowed the\n * way the Actions editor's pickers are, so a lookup never reads more than the\n * editor would offer.\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 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 !wanted('datasets')\n ? none\n : named(\n scopedToHost(org.collection('datasets'), input.hostId),\n ['displayName', 'name', 'deletedAt'],\n live,\n (data, id) => datasetDisplayName(data) || id,\n ),\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.\n !wanted('campaigns')\n ? none\n : named(\n org.collection('emailCampaigns'),\n ['name', 'deletedAt', 'visibleTo'],\n (data) =>\n live(data) &&\n campaignPlacedOnHost({ visibleTo: data['visibleTo'] as string[] | undefined }, input.hostId),\n (data) => text(data['name']),\n ),\n !wanted('workflows')\n ? none\n : named(host.collection('workflows'), ['name', 'deletedAt'], live, (data) => text(data['name'])),\n !wanted('webhooks')\n ? none\n : named(\n host.collection('webhooks'),\n ['name', 'direction', 'deletedAt'],\n (data) => live(data) && data['direction'] === 'outbound',\n (data) => text(data['name']),\n ),\n input.crm && wanted('stages')\n ? scopedToHost(org.collection('pipelines'), input.hostId)\n .select('stages', 'archivedAt')\n .limit(AI_WORKFLOW_PIPELINES_WINDOW)\n .get()\n .then((snapshot) =>\n snapshot.docs\n .filter((doc) => !(Number(doc.get('archivedAt')) > 0))\n .flatMap((doc) => (Array.isArray(doc.get('stages')) ? (doc.get('stages') as Data[]) : []))\n .map((stage) => ({ id: text(stage?.['id']), name: text(stage?.['name']) }))\n .filter((stage) => stage.id && stage.name),\n )\n : none,\n ])\n return { forms, datasets, lists, campaigns, workflows, webhooks, stages }\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: HostWorkflow }\n\n/** A saved automation of the site, or `null` for one that does not exist or was deleted. */\nexport async function readAiWorkflowTarget(\n firestore: Firestore,\n input: { hostId: string; type: AiWorkflowTargetType; id: string },\n): Promise<AiWorkflowTarget | null> {\n const collection = input.type === 'action' ? 'actions' : 'workflows'\n const snapshot = await firestore.collection('hosts').doc(input.hostId).collection(collection).doc(input.id).get()\n const data = snapshot.exists ? ((snapshot.data() ?? {}) as Data) : null\n if (!data || data['deletedAt'] != null) return null\n const name = text(data['name']) || input.id\n return input.type === 'action'\n ? { type: 'action', id: snapshot.id, name, action: data as unknown as HostAction }\n : { type: 'workflow', id: snapshot.id, name, workflow: data as unknown as HostWorkflow }\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","datasetDisplayName","isFormArchived","campaignPlacedOnHost","scopedToHost","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","readForms","host","collection","Array","isArray","field","Boolean","readAiAutomationRecords","firestore","input","hostId","org","orgId","wanted","kind","only","includes","none","Promise","resolve","forms","datasets","lists","campaigns","workflows","webhooks","stages","all","visibleTo","crm","then","Number","flatMap","stage","readAiWorkflowFunctions","readAiWorkflowTarget","type","exists","action","workflow","readAiWorkflowRun","runId","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 type { HostAction } from '@aglyn/aglyn/app-utils/actions'\nimport { actionRunResult } from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { datasetDisplayName } from '@aglyn/aglyn/app-utils/datasets'\nimport { isFormArchived } from '@aglyn/aglyn/app-utils/forms'\nimport {\n pluginRecordIndex,\n type PluginIndexedRecord,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { campaignPlacedOnHost } from '@aglyn/shared-ui-email-campaigns/model/campaign-container'\nimport { scopedToHost } from '@aglyn/tenant-data-admin/server/organizations'\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, so they are read through the indexes that plugin publishes\n * (`workflow`, `webhook`, `action` — AGL-3080), never from its collections:\n * where no plugin keeps a kind, there are none to name. The rest are read\n * through the Admin SDK as 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/**\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 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({ hostId, 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 !wanted('datasets')\n ? none\n : named(\n scopedToHost(org.collection('datasets'), input.hostId),\n ['displayName', 'name', 'deletedAt'],\n live,\n (data, id) => datasetDisplayName(data) || id,\n ),\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.\n !wanted('campaigns')\n ? none\n : named(\n org.collection('emailCampaigns'),\n ['name', 'deletedAt', 'visibleTo'],\n (data) =>\n live(data) &&\n campaignPlacedOnHost({ visibleTo: data['visibleTo'] as string[] | undefined }, input.hostId),\n (data) => text(data['name']),\n ),\n !wanted('workflows') ? none : indexed('workflow', 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', 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","datasetDisplayName","isFormArchived","pluginRecordIndex","campaignPlacedOnHost","scopedToHost","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","indexed","kind","hostId","owner","records","index","list","readForms","host","collection","Array","isArray","field","Boolean","readAiAutomationRecords","firestore","input","org","orgId","wanted","only","includes","none","Promise","resolve","forms","datasets","lists","campaigns","workflows","webhooks","stages","all","visibleTo","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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"}
|
|
@@ -68,8 +68,13 @@ export type AiCreditAllotmentScope = Exclude<AiAllotmentScope, 'org'>;
|
|
|
68
68
|
* is the plugin's rung: core names the ceilings core's billing sells.
|
|
69
69
|
*/
|
|
70
70
|
export type AiRefusedBy = AssistRefusedBy | 'allotment';
|
|
71
|
-
/**
|
|
72
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The steps a soft allotment announces — the same 75, 80, 90 and 100 every
|
|
73
|
+
* usage quota steps through (AGL-3431). Only the highest step reached is
|
|
74
|
+
* sent, each once a month.
|
|
75
|
+
*/
|
|
76
|
+
export declare const AI_ALLOTMENT_ALERT_BANDS: readonly [75, 80, 90, 100];
|
|
77
|
+
export type AiAllotmentThreshold = (typeof AI_ALLOTMENT_ALERT_BANDS)[number];
|
|
73
78
|
/** The share of an allotment at which the strip warns and a soft one alerts. */
|
|
74
79
|
export declare const AI_ALLOTMENT_WARN_SHARE = 0.8;
|
|
75
80
|
/**
|
|
@@ -152,7 +157,10 @@ export interface AiAllotmentStanding {
|
|
|
152
157
|
export type AiAllotmentState = 'ok' | 'warn' | 'reached';
|
|
153
158
|
/** `reached` at or past the allotment, `warn` from 80% of it. */
|
|
154
159
|
export declare function aiAllotmentState(used: number, credits: number): AiAllotmentState;
|
|
155
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* The highest step a standing has reached, for the alert pipeline — compared
|
|
162
|
+
* as `used × 100 ≥ step × credits`, so 9 of 10 is exactly 90%.
|
|
163
|
+
*/
|
|
156
164
|
export declare function aiAllotmentThreshold(used: number, credits: number): AiAllotmentThreshold | null;
|
|
157
165
|
/**
|
|
158
166
|
* Whether a standing admits a request.
|
|
@@ -54,6 +54,16 @@ import { ORG_PERMISSIONS } from "@aglyn/aglyn/app-utils/org-permissions";
|
|
|
54
54
|
* cards read one definition of a subject, a standing and a refusal sentence.
|
|
55
55
|
*/ /** `orgs/{orgId}/{this}/{subject}`. */ export const AI_ALLOTMENTS_COLLECTION = 'aiAllotments';
|
|
56
56
|
/** The subject id of the org-wide model restriction. */ export const AI_ALLOTMENT_ORG_SUBJECT = 'org';
|
|
57
|
+
/**
|
|
58
|
+
* The steps a soft allotment announces — the same 75, 80, 90 and 100 every
|
|
59
|
+
* usage quota steps through (AGL-3431). Only the highest step reached is
|
|
60
|
+
* sent, each once a month.
|
|
61
|
+
*/ export const AI_ALLOTMENT_ALERT_BANDS = [
|
|
62
|
+
75,
|
|
63
|
+
80,
|
|
64
|
+
90,
|
|
65
|
+
100
|
|
66
|
+
];
|
|
57
67
|
/** The share of an allotment at which the strip warns and a soft one alerts. */ export const AI_ALLOTMENT_WARN_SHARE = 0.8;
|
|
58
68
|
/**
|
|
59
69
|
* The largest monthly allotment a write may carry. A bound on a typing
|
|
@@ -162,9 +172,16 @@ const ID_SEGMENT = /^[A-Za-z0-9_-]{1,128}$/;
|
|
|
162
172
|
if (used >= credits * AI_ALLOTMENT_WARN_SHARE) return 'warn';
|
|
163
173
|
return 'ok';
|
|
164
174
|
}
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The highest step a standing has reached, for the alert pipeline — compared
|
|
177
|
+
* as `used × 100 ≥ step × credits`, so 9 of 10 is exactly 90%.
|
|
178
|
+
*/ export function aiAllotmentThreshold(used, credits) {
|
|
179
|
+
if (!(credits > 0) || !Number.isFinite(used) || used < 0) return null;
|
|
180
|
+
for(let index = AI_ALLOTMENT_ALERT_BANDS.length - 1; index >= 0; index -= 1){
|
|
181
|
+
const band = AI_ALLOTMENT_ALERT_BANDS[index];
|
|
182
|
+
if (used * 100 >= band * credits) return band;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
168
185
|
}
|
|
169
186
|
/**
|
|
170
187
|
* Whether a standing admits a request.
|
|
@@ -261,7 +278,7 @@ const scopeRank = (scope)=>SCOPE_ORDER.indexOf(scope);
|
|
|
261
278
|
};
|
|
262
279
|
}
|
|
263
280
|
return {
|
|
264
|
-
title: `${who} is past ${
|
|
281
|
+
title: `${who} is past ${input.threshold}% of ${whose} AI allotment${where}`,
|
|
265
282
|
body: `${figures} The allotment is soft, so nothing stops at the line.`
|
|
266
283
|
};
|
|
267
284
|
}
|