@aglyn/plugins-workflows 1.0.0-beta.219 → 1.0.0-beta.221
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 +7 -2
- package/package.json +13 -13
- package/src/lib/components/host-actions-card.component.js +3 -2
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/host-run-history-card.component.d.ts +2 -2
- package/src/lib/components/host-run-history-card.component.js +2 -1
- package/src/lib/components/host-run-history-card.component.js.map +1 -1
- package/src/lib/components/host-webhooks-card.component.js +2 -1
- package/src/lib/components/host-webhooks-card.component.js.map +1 -1
- package/src/lib/components/host-workflows-card.component.js +2 -2
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/use-automation-step-pickers.js +50 -42
- package/src/lib/components/use-automation-step-pickers.js.map +1 -1
- package/src/lib/components/use-org-automation-step-pickers.js +23 -11
- package/src/lib/components/use-org-automation-step-pickers.js.map +1 -1
- package/src/lib/components/workflow-zones.d.ts +49 -0
- package/src/lib/components/workflow-zones.js +3 -0
- package/src/lib/components/workflow-zones.js.map +1 -1
- package/src/lib/engine/crm-action-steps.d.ts +14 -1
- package/src/lib/engine/crm-action-steps.js +15 -0
- package/src/lib/engine/crm-action-steps.js.map +1 -1
- package/src/lib/engine/run-event-actions.js +65 -24
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/run-event-workflows.js +2 -2
- package/src/lib/engine/run-event-workflows.js.map +1 -1
- package/src/lib/model/automation-record.d.ts +47 -0
- package/src/lib/model/automation-record.js +79 -0
- package/src/lib/model/automation-record.js.map +1 -0
- package/src/lib/model/step-outcomes.d.ts +42 -0
- package/src/lib/model/step-outcomes.js +70 -0
- package/src/lib/model/step-outcomes.js.map +1 -0
- package/src/lib/model/webhooks.d.ts +36 -0
- package/src/lib/model/webhooks.js +24 -0
- package/src/lib/model/webhooks.js.map +1 -0
- package/src/lib/model/workflows-record-lists.d.ts +18 -0
- package/src/lib/model/workflows-record-lists.js +45 -0
- package/src/lib/model/workflows-record-lists.js.map +1 -0
- package/src/lib/model/workflows.d.ts +16 -4
- package/src/lib/model/workflows.js +10 -9
- package/src/lib/model/workflows.js.map +1 -1
- package/src/lib/plugin.js +33 -2
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/automation-record-index.js +16 -47
- package/src/lib/server/automation-record-index.js.map +1 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-list.d.ts +28 -0
- package/src/lib/testing/stand-in-dataset-list.js +55 -0
- package/src/lib/testing/stand-in-dataset-list.js.map +1 -0
- package/src/lib/testing/stand-in-overlay-list.d.ts +26 -0
- package/src/lib/testing/stand-in-overlay-list.js +53 -0
- package/src/lib/testing/stand-in-overlay-list.js.map +1 -0
- package/src/lib/usage-axes.d.ts +1 -1
- package/src/lib/usage-axes.js +13 -2
- package/src/lib/usage-axes.js.map +1 -1
|
@@ -14,12 +14,12 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
17
|
+
*/ import { checkEntitlement, evaluateExpression, FLOW_TIMED_OUT_FIELD, resolveOrgEntitlements } from "@aglyn/aglyn/server";
|
|
18
18
|
import { activitySearchTokens } from "@aglyn/aglyn/app-utils/activity-search";
|
|
19
19
|
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
20
20
|
import { FieldValue } from "firebase-admin/firestore";
|
|
21
21
|
import { runSummaryFields } from "../model/run-history.js";
|
|
22
|
-
import { runWorkflow } from "../model/workflows.js";
|
|
22
|
+
import { ACTION_MAX_EVENT_DEPTH, runWorkflow } from "../model/workflows.js";
|
|
23
23
|
import { eventRunSuspension } from "./site-suspension.js";
|
|
24
24
|
import { deferFlowEnrollment, endFlowEnrollment } from "./flow-enrollments.js";
|
|
25
25
|
import { automationRunEnv, executeWorkflow, FLOW_CLAIM_RETRY_MS, stopFlowEnrollment } from "./run-event-actions.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/run-event-workflows.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_EVENT_DEPTH,\n checkEntitlement,\n evaluateExpression,\n FLOW_TIMED_OUT_FIELD,\n type HostActionAlert,\n type HostEventType,\n type HostFunction,\n type HostVariable,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/server'\nimport { activitySearchTokens } from '@aglyn/aglyn/app-utils/activity-search'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { runSummaryFields } from '../model/run-history'\nimport { type HostWorkflow, runWorkflow } from '../model/workflows'\nimport { eventRunSuspension } from './site-suspension'\nimport {\n deferFlowEnrollment,\n endFlowEnrollment,\n type FlowEnrollment,\n} from './flow-enrollments'\nimport {\n automationRunEnv,\n executeWorkflow,\n FLOW_CLAIM_RETRY_MS,\n stopFlowEnrollment,\n type WorkflowContext,\n type WorkflowExecution,\n} from './run-event-actions'\nimport {\n type AutomationWorkflow,\n workflowHasActionSteps,\n} from './workflow-steps'\nimport { runTriggeredByFields } from './run-trigger-actor'\n\n/** Bounded fan-out per event: at most this many triggered workflows run. */\nconst MAX_TRIGGERED_WORKFLOWS = 10\n\n/**\n * One workflow run's row in the site's activity feed, which is the run\n * history the Workflows tab reads.\n *\n * The one place a workflow's run is written, whichever door started it.\n */\nasync function recordWorkflowRun(\n hostRef: FirebaseFirestore.DocumentReference,\n row: Record<string, unknown>,\n): Promise<void> {\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n ...row,\n // The site log's search finds a run by its workflow's name (AGL-3321).\n searchTokens: activitySearchTokens({\n target: row['target'] as { name?: unknown } | undefined,\n }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n}\n\n/**\n * The history row for a run of a workflow with Actions steps, in the Actions\n * run's phrasing (AGL-2171) — what each step did, joined — so the run table\n * shows every step's outcome whichever engine the step came from. `status`\n * and `durationMs` ride along as they do on every workflow run, for the\n * dashboard feed that renders them.\n */\nexport function workflowRunRow(\n execution: Pick<WorkflowExecution, 'ending' | 'errors' | 'outcomes'>,\n input: {\n event: string\n workflowId: string\n workflowName: string\n durationMs: number\n },\n): Record<string, unknown> {\n const failed = execution.errors.length > 0\n const action = failed\n ? `Workflow ran on ${input.event} with errors: ${execution.errors.join('; ')}`.slice(\n 0,\n 300,\n )\n : execution.ending === 'waiting'\n ? `Workflow is waiting, on ${input.event}`\n : `Workflow ran on ${input.event}`\n return {\n action,\n result: failed ? 'failed' : 'succeeded',\n trigger: input.event,\n // The summary and its search tokens, written together (AGL-3321).\n ...runSummaryFields(\n (execution.outcomes.length\n ? execution.outcomes.join(' · ')\n : 'Ran'\n ).slice(0, 300),\n ),\n status: failed ? 'error' : 'ok',\n durationMs: input.durationMs,\n target: {\n type: 'workflow',\n id: input.workflowId,\n name: input.workflowName,\n },\n }\n}\n\n/**\n * Event-triggered workflow runner (AGL-128): loads workflows whose\n * `trigger.event` matches, evaluates optional trigger filters over the\n * event payload, runs each, and logs runs to the host activity feed.\n * Fire-and-forget from the tenant's event sites (form submit, pageview\n * collector, membership APIs) — it never throws, and a failure must not\n * break the request that emitted the event.\n *\n * A workflow of function calls runs through the pure `runWorkflow`\n * evaluator exactly as it always has. A workflow with Actions steps runs\n * through `executeWorkflow`, which performs them with the Actions executors;\n * the site alerts its steps raise are what this resolves.\n *\n * ONE RUN, METERED ONCE: either kind counts one run on the workflow meter,\n * `workflowRunsPerMonth`, however many steps it has and whatever they are.\n * Its Actions steps take the Actions tier gates step by step, and none of\n * them counts an action run.\n *\n * `depth` is the chain guard actions run under (`ACTION_MAX_EVENT_DEPTH`):\n * an event a workflow's own step raised — a stage it set — is heard one\n * level deeper, so a workflow that sets the stage it listens for stops.\n */\nexport async function runEventWorkflows(\n hostId: string,\n event: HostEventType,\n payload: HostEventPayload = {},\n depth = 0,\n): Promise<HostActionAlert[]> {\n const alerts: HostActionAlert[] = []\n if (depth > ACTION_MAX_EVENT_DEPTH) return alerts\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const triggered = await hostRef\n .collection('workflows')\n .where('trigger.event', '==', event)\n .limit(MAX_TRIGGERED_WORKFLOWS)\n .get()\n const workflows = triggered.docs.filter((doc) => !doc.get('deletedAt'))\n if (!workflows.length) return alerts\n // Full workflow map for nested function→workflow calls (AGL-129).\n const allWorkflowDocs = await hostRef\n .collection('workflows')\n .limit(100)\n .get()\n // Double-keyed by doc id AND name (AGL-261): id references are\n // rename-safe; legacy name references keep resolving.\n // Each carries its id, so a workflow a step names by its legacy name\n // still runs as the automation it is (see `WorkflowContext`).\n const workflowMap: WorkflowContext['workflows'] = {}\n for (const doc of allWorkflowDocs.docs) {\n const data = doc.data() as any\n if (data.deletedAt) continue\n const withId = { ...data, $id: doc.id }\n workflowMap[doc.id] = withId\n if (data?.name) workflowMap[data.name] = withId\n }\n\n // Monthly run cap by the owning org's plan (AGL-165) — dark-launch\n // rule: workspaces without a plan are uncapped, like every other gate.\n const monthKey = new Date().toISOString().slice(0, 7)\n const runCounterRef = hostRef.collection('counters').doc('workflowRuns')\n // Run caps ride the owning org's doc (AGL-238) — read through\n // `getOrgForHost` below. The host document itself is not consulted: the\n // cap, the counter and the workflow definitions all live elsewhere, so a\n // read of it here would be billed on every host event and used for\n // nothing.\n const owner = await getOrgForHost(hostId)\n // A suspended site runs nothing (AGL-3356): see `eventRunSuspension`.\n // Asked before the cap so a locked site's events bill no runs.\n if (await eventRunSuspension(hostRef, owner?.org)) return alerts\n {\n const org = owner?.org\n // Plan-less orgs resolve as free (AGL-247) — the cap always runs.\n const limit = resolveOrgEntitlements(\n org as any,\n ).workflowRunsPerMonth\n const counterSnapshot = await runCounterRef.get()\n const used = Number(counterSnapshot.get(monthKey) ?? 0)\n if (used + workflows.length > limit) return alerts\n }\n\n const [functionDocs, variableDocs] = await Promise.all([\n hostRef.collection('functions').limit(100).get(),\n hostRef.collection('variables').limit(100).get(),\n ])\n const functions: Record<string, HostFunction> = {}\n for (const doc of functionDocs.docs) {\n const definition = doc.data() as any\n if (definition.deletedAt) continue\n functions[doc.id] = definition\n if (definition.name) functions[definition.name] = definition\n }\n const variables: Record<string, HostVariable> = {}\n for (const doc of variableDocs.docs) {\n const variable = doc.data() as any\n if (!variable.deletedAt && variable.name) {\n variables[variable.name] = variable\n }\n }\n\n let executed = 0\n for (const doc of workflows) {\n const workflow = doc.data() as AutomationWorkflow\n const filter = workflow.trigger?.filter?.trim()\n if (filter) {\n try {\n const scope: HostEventPayload = { event, ...payload }\n if (!evaluateExpression(filter, scope)) continue\n } catch {\n continue // A broken filter never fires.\n }\n }\n const startedAt = Date.now()\n if (workflowHasActionSteps(workflow)) {\n /*\n * A WORKFLOW WITH ACTIONS STEPS is performed, not evaluated. The\n * maps above are this run's working set, so the executor reads them\n * rather than a second copy; the plan gates come off the org\n * document the cap already read.\n */\n const context: WorkflowContext = {\n functions,\n variables,\n workflows: workflowMap,\n }\n const env = automationRunEnv({\n hostId,\n hostRef,\n owner,\n depth,\n alerts,\n loadWorkflowContext: async () => context,\n })\n const execution = await executeWorkflow(\n env,\n { kind: 'workflow', id: doc.id, name: workflow.name ?? '' },\n workflow,\n event,\n payload,\n )\n executed += 1\n await recordWorkflowRun(\n hostRef,\n workflowRunRow(execution, {\n event,\n workflowId: doc.id,\n workflowName: workflow.name ?? '',\n durationMs: Date.now() - startedAt,\n }),\n )\n continue\n }\n const run = runWorkflow(\n workflow as HostWorkflow,\n functions,\n variables,\n { event, ...payload },\n { workflows: workflowMap },\n )\n const durationMs = Date.now() - startedAt\n executed += 1\n // `=== false` (not `!run.ok`): the union fails to narrow under the\n // stricter build tsconfig otherwise (same quirk as runWorkflow).\n const action =\n run.ok === false\n ? `Workflow failed on ${event}: ${run.error}`.slice(0, 300)\n : `Workflow ran on ${event}`\n /*\n * The run-history shape, not just `status` + `durationMs` (AGL-2222).\n *\n * `actionRunResult()` recognises a run by `result`, falling back to the\n * prose prefix `Action ran on`. This writer's prose begins\n * `Workflow ran on`, so every workflow execution was filtered out of the\n * very table AGL-2171 built — the Runs dialog on the Workflows tab read\n * \"No runs yet\" however many times a workflow had run. The `durationMs`\n * caption in that table could never render either, because the only rows\n * carrying a duration were the ones being dropped.\n *\n * `status` stays: the dashboard activity feed renders it, and removing\n * it would trade one silent gap for another.\n */\n await recordWorkflowRun(hostRef, {\n action,\n result: run.ok === false ? 'failed' : 'succeeded',\n trigger: event,\n ...runSummaryFields(\n run.ok === false ? String(run.error).slice(0, 300) : 'Ran',\n ),\n status: run.ok === false ? 'error' : 'ok',\n durationMs,\n target: { type: 'workflow', id: doc.id, name: workflow.name ?? '' },\n })\n }\n // Filtered-out workflows do not bill — only executed runs count.\n if (executed > 0) {\n await runCounterRef\n .set(\n { [monthKey]: FieldValue.increment(executed) },\n { merge: true },\n )\n .catch(() => undefined)\n }\n } catch (error) {\n console.error('runEventWorkflows failed', hostId, event, error)\n }\n return alerts\n}\n\n/**\n * Continues a WORKFLOW's enrollment from where its wait ended — the\n * workflow twin of the action resume, and asked the same questions.\n *\n * - The kill switch is the workflow document: deleted, and the people inside\n * it stop, exactly as they do when an action is deleted or switched off.\n * - The plan is re-asked: a wait is an Actions step, so a workspace whose\n * plan has lost the actions builder stops the flow rather than finishing\n * it on a plan that no longer carries it.\n * - The snapshot runs, not the workflow's current steps, and the results the\n * function calls bound before the wait ride in its payload.\n * - Counted on the workflow meter, never refused by it: the person is already\n * inside, and the gate belongs at enrollment.\n */\nexport async function resumeWorkflowEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n options: { timedOut?: boolean; nowMs: number },\n): Promise<'ran' | 'waiting' | 'exited' | 'deferred' | 'stopped'> {\n const { nowMs } = options\n const hostId = enrollment.hostId\n const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId)\n const doc = await hostRef\n .collection('workflows')\n .doc(enrollment.actionId)\n .get()\n .catch(() => null)\n if (!doc?.exists || doc.get('deletedAt')) {\n return await stopFlowEnrollment(\n enrollment,\n ref,\n 'the workflow was deleted',\n )\n }\n const workflow = doc.data() as AutomationWorkflow\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!checkEntitlement(owner?.org as any, 'actions')) {\n return await stopFlowEnrollment(\n enrollment,\n ref,\n 'this site’s plan no longer includes automations',\n )\n }\n const env = automationRunEnv({ hostId, hostRef, owner, depth: 0 })\n const payload: HostEventPayload = {\n ...((enrollment.payload ?? {}) as HostEventPayload),\n // The timeout BRANCH, as on an action's resume: the step after a\n // `waitForEvent` reads it to tell the clock from the event.\n ...(options.timedOut ? { [FLOW_TIMED_OUT_FIELD]: true } : {}),\n }\n const startedAt = Date.now()\n const execution = await executeWorkflow(\n env,\n { kind: 'workflow', id: doc.id, name: workflow.name ?? '' },\n workflow,\n enrollment.event,\n payload,\n {\n startIndex: enrollment.nextStepIndex,\n steps: enrollment.steps,\n enrollmentRef: ref,\n },\n )\n if (execution.ending === 'deferred') {\n await deferFlowEnrollment(ref, nowMs + FLOW_CLAIM_RETRY_MS, nowMs)\n return execution.ending\n }\n if (execution.ending !== 'waiting') await endFlowEnrollment(ref)\n await recordWorkflowRun(\n hostRef,\n workflowRunRow(execution, {\n event: enrollment.event,\n workflowId: doc.id,\n workflowName: workflow.name ?? '',\n durationMs: Date.now() - startedAt,\n }),\n )\n const monthKey = new Date(nowMs).toISOString().slice(0, 7)\n await hostRef\n .collection('counters')\n .doc('workflowRuns')\n .set({ [monthKey]: FieldValue.increment(1) }, { merge: true })\n .catch(() => undefined)\n return execution.ending\n}\n\nexport default runEventWorkflows\n"],"names":["ACTION_MAX_EVENT_DEPTH","checkEntitlement","evaluateExpression","FLOW_TIMED_OUT_FIELD","resolveOrgEntitlements","activitySearchTokens","firebaseAdmin","getOrgForHost","FieldValue","runSummaryFields","runWorkflow","eventRunSuspension","deferFlowEnrollment","endFlowEnrollment","automationRunEnv","executeWorkflow","FLOW_CLAIM_RETRY_MS","stopFlowEnrollment","workflowHasActionSteps","runTriggeredByFields","MAX_TRIGGERED_WORKFLOWS","recordWorkflowRun","hostRef","row","collection","add","actorId","actorEmail","searchTokens","target","createdAt","serverTimestamp","catch","undefined","workflowRunRow","execution","input","failed","errors","length","action","event","join","slice","ending","result","trigger","outcomes","status","durationMs","type","id","workflowId","name","workflowName","runEventWorkflows","hostId","payload","depth","alerts","firestore","app","doc","triggered","where","limit","get","workflows","docs","filter","allWorkflowDocs","workflowMap","data","deletedAt","withId","$id","monthKey","Date","toISOString","runCounterRef","owner","org","counterSnapshot","workflowRunsPerMonth","used","Number","functionDocs","variableDocs","Promise","all","functions","definition","variables","variable","executed","workflow","trim","scope","startedAt","now","context","env","loadWorkflowContext","kind","run","ok","error","String","set","increment","merge","console","resumeWorkflowEnrollment","enrollment","ref","options","nowMs","actionId","exists","timedOut","startIndex","nextStepIndex","steps","enrollmentRef"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/run-event-workflows.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 checkEntitlement,\n evaluateExpression,\n FLOW_TIMED_OUT_FIELD,\n type HostActionAlert,\n type HostEventType,\n type HostFunction,\n type HostVariable,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/server'\nimport { activitySearchTokens } from '@aglyn/aglyn/app-utils/activity-search'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { runSummaryFields } from '../model/run-history'\nimport { ACTION_MAX_EVENT_DEPTH, type HostWorkflow, runWorkflow } from '../model/workflows'\nimport { eventRunSuspension } from './site-suspension'\nimport {\n deferFlowEnrollment,\n endFlowEnrollment,\n type FlowEnrollment,\n} from './flow-enrollments'\nimport {\n automationRunEnv,\n executeWorkflow,\n FLOW_CLAIM_RETRY_MS,\n stopFlowEnrollment,\n type WorkflowContext,\n type WorkflowExecution,\n} from './run-event-actions'\nimport {\n type AutomationWorkflow,\n workflowHasActionSteps,\n} from './workflow-steps'\nimport { runTriggeredByFields } from './run-trigger-actor'\n\n/** Bounded fan-out per event: at most this many triggered workflows run. */\nconst MAX_TRIGGERED_WORKFLOWS = 10\n\n/**\n * One workflow run's row in the site's activity feed, which is the run\n * history the Workflows tab reads.\n *\n * The one place a workflow's run is written, whichever door started it.\n */\nasync function recordWorkflowRun(\n hostRef: FirebaseFirestore.DocumentReference,\n row: Record<string, unknown>,\n): Promise<void> {\n await hostRef\n .collection('activity')\n .add({\n actorId: null,\n actorEmail: null,\n // Who set this run off (AGL-3376).\n ...runTriggeredByFields(),\n ...row,\n // The site log's search finds a run by its workflow's name (AGL-3321).\n searchTokens: activitySearchTokens({\n target: row['target'] as { name?: unknown } | undefined,\n }),\n createdAt: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n}\n\n/**\n * The history row for a run of a workflow with Actions steps, in the Actions\n * run's phrasing (AGL-2171) — what each step did, joined — so the run table\n * shows every step's outcome whichever engine the step came from. `status`\n * and `durationMs` ride along as they do on every workflow run, for the\n * dashboard feed that renders them.\n */\nexport function workflowRunRow(\n execution: Pick<WorkflowExecution, 'ending' | 'errors' | 'outcomes'>,\n input: {\n event: string\n workflowId: string\n workflowName: string\n durationMs: number\n },\n): Record<string, unknown> {\n const failed = execution.errors.length > 0\n const action = failed\n ? `Workflow ran on ${input.event} with errors: ${execution.errors.join('; ')}`.slice(\n 0,\n 300,\n )\n : execution.ending === 'waiting'\n ? `Workflow is waiting, on ${input.event}`\n : `Workflow ran on ${input.event}`\n return {\n action,\n result: failed ? 'failed' : 'succeeded',\n trigger: input.event,\n // The summary and its search tokens, written together (AGL-3321).\n ...runSummaryFields(\n (execution.outcomes.length\n ? execution.outcomes.join(' · ')\n : 'Ran'\n ).slice(0, 300),\n ),\n status: failed ? 'error' : 'ok',\n durationMs: input.durationMs,\n target: {\n type: 'workflow',\n id: input.workflowId,\n name: input.workflowName,\n },\n }\n}\n\n/**\n * Event-triggered workflow runner (AGL-128): loads workflows whose\n * `trigger.event` matches, evaluates optional trigger filters over the\n * event payload, runs each, and logs runs to the host activity feed.\n * Fire-and-forget from the tenant's event sites (form submit, pageview\n * collector, membership APIs) — it never throws, and a failure must not\n * break the request that emitted the event.\n *\n * A workflow of function calls runs through the pure `runWorkflow`\n * evaluator exactly as it always has. A workflow with Actions steps runs\n * through `executeWorkflow`, which performs them with the Actions executors;\n * the site alerts its steps raise are what this resolves.\n *\n * ONE RUN, METERED ONCE: either kind counts one run on the workflow meter,\n * `workflowRunsPerMonth`, however many steps it has and whatever they are.\n * Its Actions steps take the Actions tier gates step by step, and none of\n * them counts an action run.\n *\n * `depth` is the chain guard actions run under (`ACTION_MAX_EVENT_DEPTH`):\n * an event a workflow's own step raised — a stage it set — is heard one\n * level deeper, so a workflow that sets the stage it listens for stops.\n */\nexport async function runEventWorkflows(\n hostId: string,\n event: HostEventType,\n payload: HostEventPayload = {},\n depth = 0,\n): Promise<HostActionAlert[]> {\n const alerts: HostActionAlert[] = []\n if (depth > ACTION_MAX_EVENT_DEPTH) return alerts\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const triggered = await hostRef\n .collection('workflows')\n .where('trigger.event', '==', event)\n .limit(MAX_TRIGGERED_WORKFLOWS)\n .get()\n const workflows = triggered.docs.filter((doc) => !doc.get('deletedAt'))\n if (!workflows.length) return alerts\n // Full workflow map for nested function→workflow calls (AGL-129).\n const allWorkflowDocs = await hostRef\n .collection('workflows')\n .limit(100)\n .get()\n // Double-keyed by doc id AND name (AGL-261): id references are\n // rename-safe; legacy name references keep resolving.\n // Each carries its id, so a workflow a step names by its legacy name\n // still runs as the automation it is (see `WorkflowContext`).\n const workflowMap: WorkflowContext['workflows'] = {}\n for (const doc of allWorkflowDocs.docs) {\n const data = doc.data() as any\n if (data.deletedAt) continue\n const withId = { ...data, $id: doc.id }\n workflowMap[doc.id] = withId\n if (data?.name) workflowMap[data.name] = withId\n }\n\n // Monthly run cap by the owning org's plan (AGL-165) — dark-launch\n // rule: workspaces without a plan are uncapped, like every other gate.\n const monthKey = new Date().toISOString().slice(0, 7)\n const runCounterRef = hostRef.collection('counters').doc('workflowRuns')\n // Run caps ride the owning org's doc (AGL-238) — read through\n // `getOrgForHost` below. The host document itself is not consulted: the\n // cap, the counter and the workflow definitions all live elsewhere, so a\n // read of it here would be billed on every host event and used for\n // nothing.\n const owner = await getOrgForHost(hostId)\n // A suspended site runs nothing (AGL-3356): see `eventRunSuspension`.\n // Asked before the cap so a locked site's events bill no runs.\n if (await eventRunSuspension(hostRef, owner?.org)) return alerts\n {\n const org = owner?.org\n // Plan-less orgs resolve as free (AGL-247) — the cap always runs.\n const limit = resolveOrgEntitlements(\n org as any,\n ).workflowRunsPerMonth\n const counterSnapshot = await runCounterRef.get()\n const used = Number(counterSnapshot.get(monthKey) ?? 0)\n if (used + workflows.length > limit) return alerts\n }\n\n const [functionDocs, variableDocs] = await Promise.all([\n hostRef.collection('functions').limit(100).get(),\n hostRef.collection('variables').limit(100).get(),\n ])\n const functions: Record<string, HostFunction> = {}\n for (const doc of functionDocs.docs) {\n const definition = doc.data() as any\n if (definition.deletedAt) continue\n functions[doc.id] = definition\n if (definition.name) functions[definition.name] = definition\n }\n const variables: Record<string, HostVariable> = {}\n for (const doc of variableDocs.docs) {\n const variable = doc.data() as any\n if (!variable.deletedAt && variable.name) {\n variables[variable.name] = variable\n }\n }\n\n let executed = 0\n for (const doc of workflows) {\n const workflow = doc.data() as AutomationWorkflow\n const filter = workflow.trigger?.filter?.trim()\n if (filter) {\n try {\n const scope: HostEventPayload = { event, ...payload }\n if (!evaluateExpression(filter, scope)) continue\n } catch {\n continue // A broken filter never fires.\n }\n }\n const startedAt = Date.now()\n if (workflowHasActionSteps(workflow)) {\n /*\n * A WORKFLOW WITH ACTIONS STEPS is performed, not evaluated. The\n * maps above are this run's working set, so the executor reads them\n * rather than a second copy; the plan gates come off the org\n * document the cap already read.\n */\n const context: WorkflowContext = {\n functions,\n variables,\n workflows: workflowMap,\n }\n const env = automationRunEnv({\n hostId,\n hostRef,\n owner,\n depth,\n alerts,\n loadWorkflowContext: async () => context,\n })\n const execution = await executeWorkflow(\n env,\n { kind: 'workflow', id: doc.id, name: workflow.name ?? '' },\n workflow,\n event,\n payload,\n )\n executed += 1\n await recordWorkflowRun(\n hostRef,\n workflowRunRow(execution, {\n event,\n workflowId: doc.id,\n workflowName: workflow.name ?? '',\n durationMs: Date.now() - startedAt,\n }),\n )\n continue\n }\n const run = runWorkflow(\n workflow as HostWorkflow,\n functions,\n variables,\n { event, ...payload },\n { workflows: workflowMap },\n )\n const durationMs = Date.now() - startedAt\n executed += 1\n // `=== false` (not `!run.ok`): the union fails to narrow under the\n // stricter build tsconfig otherwise (same quirk as runWorkflow).\n const action =\n run.ok === false\n ? `Workflow failed on ${event}: ${run.error}`.slice(0, 300)\n : `Workflow ran on ${event}`\n /*\n * The run-history shape, not just `status` + `durationMs` (AGL-2222).\n *\n * `actionRunResult()` recognises a run by `result`, falling back to the\n * prose prefix `Action ran on`. This writer's prose begins\n * `Workflow ran on`, so every workflow execution was filtered out of the\n * very table AGL-2171 built — the Runs dialog on the Workflows tab read\n * \"No runs yet\" however many times a workflow had run. The `durationMs`\n * caption in that table could never render either, because the only rows\n * carrying a duration were the ones being dropped.\n *\n * `status` stays: the dashboard activity feed renders it, and removing\n * it would trade one silent gap for another.\n */\n await recordWorkflowRun(hostRef, {\n action,\n result: run.ok === false ? 'failed' : 'succeeded',\n trigger: event,\n ...runSummaryFields(\n run.ok === false ? String(run.error).slice(0, 300) : 'Ran',\n ),\n status: run.ok === false ? 'error' : 'ok',\n durationMs,\n target: { type: 'workflow', id: doc.id, name: workflow.name ?? '' },\n })\n }\n // Filtered-out workflows do not bill — only executed runs count.\n if (executed > 0) {\n await runCounterRef\n .set(\n { [monthKey]: FieldValue.increment(executed) },\n { merge: true },\n )\n .catch(() => undefined)\n }\n } catch (error) {\n console.error('runEventWorkflows failed', hostId, event, error)\n }\n return alerts\n}\n\n/**\n * Continues a WORKFLOW's enrollment from where its wait ended — the\n * workflow twin of the action resume, and asked the same questions.\n *\n * - The kill switch is the workflow document: deleted, and the people inside\n * it stop, exactly as they do when an action is deleted or switched off.\n * - The plan is re-asked: a wait is an Actions step, so a workspace whose\n * plan has lost the actions builder stops the flow rather than finishing\n * it on a plan that no longer carries it.\n * - The snapshot runs, not the workflow's current steps, and the results the\n * function calls bound before the wait ride in its payload.\n * - Counted on the workflow meter, never refused by it: the person is already\n * inside, and the gate belongs at enrollment.\n */\nexport async function resumeWorkflowEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n options: { timedOut?: boolean; nowMs: number },\n): Promise<'ran' | 'waiting' | 'exited' | 'deferred' | 'stopped'> {\n const { nowMs } = options\n const hostId = enrollment.hostId\n const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId)\n const doc = await hostRef\n .collection('workflows')\n .doc(enrollment.actionId)\n .get()\n .catch(() => null)\n if (!doc?.exists || doc.get('deletedAt')) {\n return await stopFlowEnrollment(\n enrollment,\n ref,\n 'the workflow was deleted',\n )\n }\n const workflow = doc.data() as AutomationWorkflow\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!checkEntitlement(owner?.org as any, 'actions')) {\n return await stopFlowEnrollment(\n enrollment,\n ref,\n 'this site’s plan no longer includes automations',\n )\n }\n const env = automationRunEnv({ hostId, hostRef, owner, depth: 0 })\n const payload: HostEventPayload = {\n ...((enrollment.payload ?? {}) as HostEventPayload),\n // The timeout BRANCH, as on an action's resume: the step after a\n // `waitForEvent` reads it to tell the clock from the event.\n ...(options.timedOut ? { [FLOW_TIMED_OUT_FIELD]: true } : {}),\n }\n const startedAt = Date.now()\n const execution = await executeWorkflow(\n env,\n { kind: 'workflow', id: doc.id, name: workflow.name ?? '' },\n workflow,\n enrollment.event,\n payload,\n {\n startIndex: enrollment.nextStepIndex,\n steps: enrollment.steps,\n enrollmentRef: ref,\n },\n )\n if (execution.ending === 'deferred') {\n await deferFlowEnrollment(ref, nowMs + FLOW_CLAIM_RETRY_MS, nowMs)\n return execution.ending\n }\n if (execution.ending !== 'waiting') await endFlowEnrollment(ref)\n await recordWorkflowRun(\n hostRef,\n workflowRunRow(execution, {\n event: enrollment.event,\n workflowId: doc.id,\n workflowName: workflow.name ?? '',\n durationMs: Date.now() - startedAt,\n }),\n )\n const monthKey = new Date(nowMs).toISOString().slice(0, 7)\n await hostRef\n .collection('counters')\n .doc('workflowRuns')\n .set({ [monthKey]: FieldValue.increment(1) }, { merge: true })\n .catch(() => undefined)\n return execution.ending\n}\n\nexport default runEventWorkflows\n"],"names":["checkEntitlement","evaluateExpression","FLOW_TIMED_OUT_FIELD","resolveOrgEntitlements","activitySearchTokens","firebaseAdmin","getOrgForHost","FieldValue","runSummaryFields","ACTION_MAX_EVENT_DEPTH","runWorkflow","eventRunSuspension","deferFlowEnrollment","endFlowEnrollment","automationRunEnv","executeWorkflow","FLOW_CLAIM_RETRY_MS","stopFlowEnrollment","workflowHasActionSteps","runTriggeredByFields","MAX_TRIGGERED_WORKFLOWS","recordWorkflowRun","hostRef","row","collection","add","actorId","actorEmail","searchTokens","target","createdAt","serverTimestamp","catch","undefined","workflowRunRow","execution","input","failed","errors","length","action","event","join","slice","ending","result","trigger","outcomes","status","durationMs","type","id","workflowId","name","workflowName","runEventWorkflows","hostId","payload","depth","alerts","firestore","app","doc","triggered","where","limit","get","workflows","docs","filter","allWorkflowDocs","workflowMap","data","deletedAt","withId","$id","monthKey","Date","toISOString","runCounterRef","owner","org","counterSnapshot","workflowRunsPerMonth","used","Number","functionDocs","variableDocs","Promise","all","functions","definition","variables","variable","executed","workflow","trim","scope","startedAt","now","context","env","loadWorkflowContext","kind","run","ok","error","String","set","increment","merge","console","resumeWorkflowEnrollment","enrollment","ref","options","nowMs","actionId","exists","timedOut","startIndex","nextStepIndex","steps","enrollmentRef"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
|
|
18
|
+
/**
|
|
19
|
+
* One stored workflow, webhook or action as this plugin shares it with
|
|
20
|
+
* another (AGL-3080) — the one shape its server indexes
|
|
21
|
+
* (`automation-record-index.ts`) and its console list sources
|
|
22
|
+
* (`workflows-record-lists.ts`) both answer.
|
|
23
|
+
*
|
|
24
|
+
* A deleted record is left out (`null`), and so is an unnamed workflow or
|
|
25
|
+
* webhook.
|
|
26
|
+
*
|
|
27
|
+
* A `workflow`'s `facts`:
|
|
28
|
+
* `steps` (the stored step list: `functionId`, `functionName`, `args`,
|
|
29
|
+
* `resultName` each), `returnValue` (a scope name, or `null`) and `trigger`
|
|
30
|
+
* (`{ event, filter? }`, or `null` for a workflow run only by hand).
|
|
31
|
+
* A `webhook`'s `facts`:
|
|
32
|
+
* `direction` (`outbound` | `inbound`) and `enabled` (a boolean). Never its
|
|
33
|
+
* URL and never its secret.
|
|
34
|
+
* An `action`'s `facts`:
|
|
35
|
+
* `trigger`, `steps` and `enabled`, as stored. An action with no stored
|
|
36
|
+
* name is named by its id — the name its run history already gives it.
|
|
37
|
+
*/
|
|
38
|
+
/** The stored kinds, by their Firestore collection under a site. */
|
|
39
|
+
export declare const AUTOMATION_COLLECTIONS: {
|
|
40
|
+
readonly workflow: "workflows";
|
|
41
|
+
readonly webhook: "webhooks";
|
|
42
|
+
readonly action: "actions";
|
|
43
|
+
};
|
|
44
|
+
export type AutomationRecordKind = keyof typeof AUTOMATION_COLLECTIONS;
|
|
45
|
+
type Data = Readonly<Record<string, unknown>>;
|
|
46
|
+
export declare function automationIndexedRecord(kind: AutomationRecordKind, id: string, data: Data | undefined): PluginIndexedRecord | null;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* One stored workflow, webhook or action as this plugin shares it with
|
|
18
|
+
* another (AGL-3080) — the one shape its server indexes
|
|
19
|
+
* (`automation-record-index.ts`) and its console list sources
|
|
20
|
+
* (`workflows-record-lists.ts`) both answer.
|
|
21
|
+
*
|
|
22
|
+
* A deleted record is left out (`null`), and so is an unnamed workflow or
|
|
23
|
+
* webhook.
|
|
24
|
+
*
|
|
25
|
+
* A `workflow`'s `facts`:
|
|
26
|
+
* `steps` (the stored step list: `functionId`, `functionName`, `args`,
|
|
27
|
+
* `resultName` each), `returnValue` (a scope name, or `null`) and `trigger`
|
|
28
|
+
* (`{ event, filter? }`, or `null` for a workflow run only by hand).
|
|
29
|
+
* A `webhook`'s `facts`:
|
|
30
|
+
* `direction` (`outbound` | `inbound`) and `enabled` (a boolean). Never its
|
|
31
|
+
* URL and never its secret.
|
|
32
|
+
* An `action`'s `facts`:
|
|
33
|
+
* `trigger`, `steps` and `enabled`, as stored. An action with no stored
|
|
34
|
+
* name is named by its id — the name its run history already gives it.
|
|
35
|
+
*/ /** The stored kinds, by their Firestore collection under a site. */ export const AUTOMATION_COLLECTIONS = {
|
|
36
|
+
workflow: 'workflows',
|
|
37
|
+
webhook: 'webhooks',
|
|
38
|
+
action: 'actions'
|
|
39
|
+
};
|
|
40
|
+
const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
41
|
+
export function automationIndexedRecord(kind, id, data) {
|
|
42
|
+
var _data_trigger;
|
|
43
|
+
if (!data || data['deletedAt'] != null) return null;
|
|
44
|
+
const stored = str(data['name']);
|
|
45
|
+
if (kind === 'action') {
|
|
46
|
+
var _data_trigger1;
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
name: stored || id,
|
|
50
|
+
facts: {
|
|
51
|
+
trigger: (_data_trigger1 = data['trigger']) != null ? _data_trigger1 : null,
|
|
52
|
+
steps: Array.isArray(data['steps']) ? data['steps'] : [],
|
|
53
|
+
enabled: data['enabled'] !== false
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!stored) return null;
|
|
58
|
+
if (kind === 'webhook') {
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
name: stored,
|
|
62
|
+
facts: {
|
|
63
|
+
direction: data['direction'],
|
|
64
|
+
enabled: data['enabled'] !== false
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
name: stored,
|
|
71
|
+
facts: {
|
|
72
|
+
steps: Array.isArray(data['steps']) ? data['steps'] : [],
|
|
73
|
+
returnValue: str(data['returnValue']) || null,
|
|
74
|
+
trigger: (_data_trigger = data['trigger']) != null ? _data_trigger : null
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=automation-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/automation-record.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 { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index'\n\n/**\n * One stored workflow, webhook or action as this plugin shares it with\n * another (AGL-3080) — the one shape its server indexes\n * (`automation-record-index.ts`) and its console list sources\n * (`workflows-record-lists.ts`) both answer.\n *\n * A deleted record is left out (`null`), and so is an unnamed workflow or\n * webhook.\n *\n * A `workflow`'s `facts`:\n * `steps` (the stored step list: `functionId`, `functionName`, `args`,\n * `resultName` each), `returnValue` (a scope name, or `null`) and `trigger`\n * (`{ event, filter? }`, or `null` for a workflow run only by hand).\n * A `webhook`'s `facts`:\n * `direction` (`outbound` | `inbound`) and `enabled` (a boolean). Never its\n * URL and never its secret.\n * An `action`'s `facts`:\n * `trigger`, `steps` and `enabled`, as stored. An action with no stored\n * name is named by its id — the name its run history already gives it.\n */\n\n/** The stored kinds, by their Firestore collection under a site. */\nexport const AUTOMATION_COLLECTIONS = {\n workflow: 'workflows',\n webhook: 'webhooks',\n action: 'actions',\n} as const\n\nexport type AutomationRecordKind = keyof typeof AUTOMATION_COLLECTIONS\n\ntype Data = Readonly<Record<string, unknown>>\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nexport function automationIndexedRecord(\n kind: AutomationRecordKind,\n id: string,\n data: Data | undefined,\n): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const stored = str(data['name'])\n if (kind === 'action') {\n return {\n id,\n name: stored || id,\n facts: {\n trigger: data['trigger'] ?? null,\n steps: Array.isArray(data['steps']) ? data['steps'] : [],\n enabled: data['enabled'] !== false,\n },\n }\n }\n if (!stored) return null\n if (kind === 'webhook') {\n return {\n id,\n name: stored,\n facts: { direction: data['direction'], enabled: data['enabled'] !== false },\n }\n }\n return {\n id,\n name: stored,\n facts: {\n steps: Array.isArray(data['steps']) ? data['steps'] : [],\n returnValue: str(data['returnValue']) || null,\n trigger: data['trigger'] ?? null,\n },\n }\n}\n"],"names":["AUTOMATION_COLLECTIONS","workflow","webhook","action","str","value","trim","automationIndexedRecord","kind","id","data","stored","name","facts","trigger","steps","Array","isArray","enabled","direction","returnValue"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;;;;CAmBC,GAED,kEAAkE,GAClE,OAAO,MAAMA,yBAAyB;IACpCC,UAAU;IACVC,SAAS;IACTC,QAAQ;AACV,EAAU;AAMV,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,OAAO,SAASC,wBACdC,IAA0B,EAC1BC,EAAU,EACVC,IAAsB;QA6BTA;IA3Bb,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,SAASP,IAAIM,IAAI,CAAC,OAAO;IAC/B,IAAIF,SAAS,UAAU;YAKRE;QAJb,OAAO;YACLD;YACAG,MAAMD,UAAUF;YAChBI,OAAO;gBACLC,OAAO,GAAEJ,iBAAAA,IAAI,CAAC,UAAU,YAAfA,iBAAmB;gBAC5BK,OAAOC,MAAMC,OAAO,CAACP,IAAI,CAAC,QAAQ,IAAIA,IAAI,CAAC,QAAQ,GAAG,EAAE;gBACxDQ,SAASR,IAAI,CAAC,UAAU,KAAK;YAC/B;QACF;IACF;IACA,IAAI,CAACC,QAAQ,OAAO;IACpB,IAAIH,SAAS,WAAW;QACtB,OAAO;YACLC;YACAG,MAAMD;YACNE,OAAO;gBAAEM,WAAWT,IAAI,CAAC,YAAY;gBAAEQ,SAASR,IAAI,CAAC,UAAU,KAAK;YAAM;QAC5E;IACF;IACA,OAAO;QACLD;QACAG,MAAMD;QACNE,OAAO;YACLE,OAAOC,MAAMC,OAAO,CAACP,IAAI,CAAC,QAAQ,IAAIA,IAAI,CAAC,QAAQ,GAAG,EAAE;YACxDU,aAAahB,IAAIM,IAAI,CAAC,cAAc,KAAK;YACzCI,OAAO,GAAEJ,gBAAAA,IAAI,CAAC,UAAU,YAAfA,gBAAmB;QAC9B;IACF;AACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { HostActionStepType } from '@aglyn/aglyn/app-utils/actions';
|
|
18
|
+
/**
|
|
19
|
+
* Past-tense phrases for a run summary (AGL-2171).
|
|
20
|
+
*
|
|
21
|
+
* `HOST_ACTION_STEP_LABELS` (core `actions.ts`) names what a step WILL do, in the
|
|
22
|
+
* imperative, for a `Do` select — "Send an email". A run history is the
|
|
23
|
+
* opposite tense and a different audience: it says what already happened,
|
|
24
|
+
* on one line, several steps at a time. `/product/workflows` advertises
|
|
25
|
+
* exactly that shape — `Sent email · saved to Leads · webhook 200`.
|
|
26
|
+
*
|
|
27
|
+
* Two maps rather than one derived from the other, because "Send a webhook
|
|
28
|
+
* (Business)" cannot be mechanically turned into "webhook" — the plan
|
|
29
|
+
* suffix is part of the picker's label and has no business in a log line.
|
|
30
|
+
* Only the steps a SERVER run can perform appear here; the client-side
|
|
31
|
+
* steps run in the visitor's browser and never reach the activity write.
|
|
32
|
+
*/
|
|
33
|
+
export declare const HOST_ACTION_STEP_OUTCOMES: Partial<Record<HostActionStepType, string>>;
|
|
34
|
+
/**
|
|
35
|
+
* One step's line in a run summary, with the detail that makes it useful.
|
|
36
|
+
*
|
|
37
|
+
* `saved to Leads` beats `saved to dataset`, and `webhook 200` beats
|
|
38
|
+
* `webhook` — the status code is the entire reason anyone opens a run
|
|
39
|
+
* history after a webhook, and it was being discarded on the line it
|
|
40
|
+
* arrived.
|
|
41
|
+
*/
|
|
42
|
+
export declare function describeStepOutcome(type: HostActionStepType, detail?: string): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* Past-tense phrases for a run summary (AGL-2171).
|
|
18
|
+
*
|
|
19
|
+
* `HOST_ACTION_STEP_LABELS` (core `actions.ts`) names what a step WILL do, in the
|
|
20
|
+
* imperative, for a `Do` select — "Send an email". A run history is the
|
|
21
|
+
* opposite tense and a different audience: it says what already happened,
|
|
22
|
+
* on one line, several steps at a time. `/product/workflows` advertises
|
|
23
|
+
* exactly that shape — `Sent email · saved to Leads · webhook 200`.
|
|
24
|
+
*
|
|
25
|
+
* Two maps rather than one derived from the other, because "Send a webhook
|
|
26
|
+
* (Business)" cannot be mechanically turned into "webhook" — the plan
|
|
27
|
+
* suffix is part of the picker's label and has no business in a log line.
|
|
28
|
+
* Only the steps a SERVER run can perform appear here; the client-side
|
|
29
|
+
* steps run in the visitor's browser and never reach the activity write.
|
|
30
|
+
*/ export const HOST_ACTION_STEP_OUTCOMES = {
|
|
31
|
+
runWorkflow: 'ran workflow',
|
|
32
|
+
siteAlert: 'showed an alert',
|
|
33
|
+
customEvent: 'fired an event',
|
|
34
|
+
datasetAppend: 'saved to dataset',
|
|
35
|
+
updateDataset: 'updated dataset',
|
|
36
|
+
webhookPost: 'webhook',
|
|
37
|
+
sendEmail: 'sent email',
|
|
38
|
+
notifyAdmins: 'notified admins',
|
|
39
|
+
enrollList: 'enrolled in list',
|
|
40
|
+
assignCampaign: 'assigned to campaign',
|
|
41
|
+
wait: 'waiting',
|
|
42
|
+
waitForEvent: 'waiting for',
|
|
43
|
+
exitFlow: 'ended the flow',
|
|
44
|
+
setContactStage: 'set stage',
|
|
45
|
+
addContactTag: 'tagged',
|
|
46
|
+
assignContactOwner: 'assigned owner',
|
|
47
|
+
createCrmTask: 'created task',
|
|
48
|
+
logCrmActivity: 'logged activity'
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* One step's line in a run summary, with the detail that makes it useful.
|
|
52
|
+
*
|
|
53
|
+
* `saved to Leads` beats `saved to dataset`, and `webhook 200` beats
|
|
54
|
+
* `webhook` — the status code is the entire reason anyone opens a run
|
|
55
|
+
* history after a webhook, and it was being discarded on the line it
|
|
56
|
+
* arrived.
|
|
57
|
+
*/ export function describeStepOutcome(type, detail) {
|
|
58
|
+
var _HOST_ACTION_STEP_OUTCOMES_type;
|
|
59
|
+
const base = (_HOST_ACTION_STEP_OUTCOMES_type = HOST_ACTION_STEP_OUTCOMES[type]) != null ? _HOST_ACTION_STEP_OUTCOMES_type : String(type);
|
|
60
|
+
const trimmed = detail == null ? void 0 : detail.trim();
|
|
61
|
+
if (!trimmed) return base;
|
|
62
|
+
if (type === 'datasetAppend' || type === 'updateDataset') {
|
|
63
|
+
// `saved to dataset` + `Leads` reads as `saved to Leads`, not
|
|
64
|
+
// `saved to dataset Leads`.
|
|
65
|
+
return `${base.replace(/ dataset$/, '')} ${trimmed}`;
|
|
66
|
+
}
|
|
67
|
+
return `${base} ${trimmed}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=step-outcomes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/step-outcomes.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 { HostActionStepType } from '@aglyn/aglyn/app-utils/actions'\n\n/**\n * Past-tense phrases for a run summary (AGL-2171).\n *\n * `HOST_ACTION_STEP_LABELS` (core `actions.ts`) names what a step WILL do, in the\n * imperative, for a `Do` select — \"Send an email\". A run history is the\n * opposite tense and a different audience: it says what already happened,\n * on one line, several steps at a time. `/product/workflows` advertises\n * exactly that shape — `Sent email · saved to Leads · webhook 200`.\n *\n * Two maps rather than one derived from the other, because \"Send a webhook\n * (Business)\" cannot be mechanically turned into \"webhook\" — the plan\n * suffix is part of the picker's label and has no business in a log line.\n * Only the steps a SERVER run can perform appear here; the client-side\n * steps run in the visitor's browser and never reach the activity write.\n */\nexport const HOST_ACTION_STEP_OUTCOMES: Partial<\n Record<HostActionStepType, string>\n> = {\n runWorkflow: 'ran workflow',\n siteAlert: 'showed an alert',\n customEvent: 'fired an event',\n datasetAppend: 'saved to dataset',\n updateDataset: 'updated dataset',\n webhookPost: 'webhook',\n sendEmail: 'sent email',\n notifyAdmins: 'notified admins',\n enrollList: 'enrolled in list',\n assignCampaign: 'assigned to campaign',\n wait: 'waiting',\n waitForEvent: 'waiting for',\n exitFlow: 'ended the flow',\n setContactStage: 'set stage',\n addContactTag: 'tagged',\n assignContactOwner: 'assigned owner',\n createCrmTask: 'created task',\n logCrmActivity: 'logged activity',\n}\n\n/**\n * One step's line in a run summary, with the detail that makes it useful.\n *\n * `saved to Leads` beats `saved to dataset`, and `webhook 200` beats\n * `webhook` — the status code is the entire reason anyone opens a run\n * history after a webhook, and it was being discarded on the line it\n * arrived.\n */\nexport function describeStepOutcome(\n type: HostActionStepType,\n detail?: string,\n): string {\n const base = HOST_ACTION_STEP_OUTCOMES[type] ?? String(type)\n const trimmed = detail?.trim()\n if (!trimmed) return base\n if (type === 'datasetAppend' || type === 'updateDataset') {\n // `saved to dataset` + `Leads` reads as `saved to Leads`, not\n // `saved to dataset Leads`.\n return `${base.replace(/ dataset$/, '')} ${trimmed}`\n }\n return `${base} ${trimmed}`\n}\n"],"names":["HOST_ACTION_STEP_OUTCOMES","runWorkflow","siteAlert","customEvent","datasetAppend","updateDataset","webhookPost","sendEmail","notifyAdmins","enrollList","assignCampaign","wait","waitForEvent","exitFlow","setContactStage","addContactTag","assignContactOwner","createCrmTask","logCrmActivity","describeStepOutcome","type","detail","base","String","trimmed","trim","replace"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMA,4BAET;IACFC,aAAa;IACbC,WAAW;IACXC,aAAa;IACbC,eAAe;IACfC,eAAe;IACfC,aAAa;IACbC,WAAW;IACXC,cAAc;IACdC,YAAY;IACZC,gBAAgB;IAChBC,MAAM;IACNC,cAAc;IACdC,UAAU;IACVC,iBAAiB;IACjBC,eAAe;IACfC,oBAAoB;IACpBC,eAAe;IACfC,gBAAgB;AAClB,EAAC;AAED;;;;;;;CAOC,GACD,OAAO,SAASC,oBACdC,IAAwB,EACxBC,MAAe;QAEFrB;IAAb,MAAMsB,QAAOtB,kCAAAA,yBAAyB,CAACoB,KAAK,YAA/BpB,kCAAmCuB,OAAOH;IACvD,MAAMI,UAAUH,0BAAAA,OAAQI,IAAI;IAC5B,IAAI,CAACD,SAAS,OAAOF;IACrB,IAAIF,SAAS,mBAAmBA,SAAS,iBAAiB;QACxD,8DAA8D;QAC9D,4BAA4B;QAC5B,OAAO,GAAGE,KAAKI,OAAO,CAAC,aAAa,IAAI,CAAC,EAAEF,SAAS;IACtD;IACA,OAAO,GAAGF,KAAK,CAAC,EAAEE,SAAS;AAC7B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries
|
|
19
|
+
* are targets a `webhookPost` action step delivers to (HMAC-signed);
|
|
20
|
+
* inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run
|
|
21
|
+
* a workflow with the posted JSON in scope. Business tier (`webhooks`
|
|
22
|
+
* flag); Pro can be enabled per-tenant via entitlement overrides.
|
|
23
|
+
*/
|
|
24
|
+
export interface HostWebhook {
|
|
25
|
+
name: string;
|
|
26
|
+
direction: 'outbound' | 'inbound';
|
|
27
|
+
/** Outbound delivery URL (https only; checked again at send time). */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** Shared secret: signs outbound bodies, verifies inbound callers. */
|
|
30
|
+
secret?: string;
|
|
31
|
+
/** Inbound: workflow (by name) enrolled with the payload in scope. */
|
|
32
|
+
workflowName?: string;
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Outbound URLs must be public https — first-line SSRF guard. */
|
|
36
|
+
export declare const WEBHOOK_URL_PATTERN: RegExp;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries
|
|
18
|
+
* are targets a `webhookPost` action step delivers to (HMAC-signed);
|
|
19
|
+
* inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run
|
|
20
|
+
* a workflow with the posted JSON in scope. Business tier (`webhooks`
|
|
21
|
+
* flag); Pro can be enabled per-tenant via entitlement overrides.
|
|
22
|
+
*/ /** Outbound URLs must be public https — first-line SSRF guard. */ export const WEBHOOK_URL_PATTERN = /^https:\/\/(?!localhost)(?!127\.)(?!0\.)(?!10\.)(?!172\.(1[6-9]|2\d|3[01])\.)(?!192\.168\.)(?!169\.254\.)[^\s]+$/i;
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/webhooks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries\n * are targets a `webhookPost` action step delivers to (HMAC-signed);\n * inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run\n * a workflow with the posted JSON in scope. Business tier (`webhooks`\n * flag); Pro can be enabled per-tenant via entitlement overrides.\n */\nexport interface HostWebhook {\n name: string\n direction: 'outbound' | 'inbound'\n /** Outbound delivery URL (https only; checked again at send time). */\n url?: string\n /** Shared secret: signs outbound bodies, verifies inbound callers. */\n secret?: string\n /** Inbound: workflow (by name) enrolled with the payload in scope. */\n workflowName?: string\n enabled?: boolean\n}\n\n/** Outbound URLs must be public https — first-line SSRF guard. */\nexport const WEBHOOK_URL_PATTERN =\n /^https:\\/\\/(?!localhost)(?!127\\.)(?!0\\.)(?!10\\.)(?!172\\.(1[6-9]|2\\d|3[01])\\.)(?!192\\.168\\.)(?!169\\.254\\.)[^\\s]+$/i\n"],"names":["WEBHOOK_URL_PATTERN"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GAaD,gEAAgE,GAChE,OAAO,MAAMA,sBACX,oHAAmH"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */
|
|
18
|
+
export declare function registerWorkflowsRecordLists(): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { registerPluginRecordListSource } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
17
|
+
import { collection, documentId, limit, orderBy, query } from "firebase/firestore";
|
|
18
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
19
|
+
import { AUTOMATION_COLLECTIONS, automationIndexedRecord } from "./automation-record.js";
|
|
20
|
+
/**
|
|
21
|
+
* A site's workflows, webhooks and actions, listed in the console for another
|
|
22
|
+
* plugin's picker or check (AGL-3080) — a computed variable choosing the
|
|
23
|
+
* workflow that fills it, a reference check confirming one still exists.
|
|
24
|
+
*
|
|
25
|
+
* All three are a site's: asked at the organization, a source answers none,
|
|
26
|
+
* and none is ever installed from a listing. Ordered by document id, so the
|
|
27
|
+
* window is the same one every bounded list reads. Each record is
|
|
28
|
+
* `automationIndexedRecord`'s, the server indexes' shape.
|
|
29
|
+
*/ function automationListSource(kind) {
|
|
30
|
+
return {
|
|
31
|
+
query (firestore, request) {
|
|
32
|
+
return request.hostId && !request.installedFrom ? query(collection(firestore, 'hosts', request.hostId, AUTOMATION_COLLECTIONS[kind]), orderBy(documentId()), limit(request.limit)) : null;
|
|
33
|
+
},
|
|
34
|
+
record: (id, data)=>automationIndexedRecord(kind, id, data)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Called from the console registrar, owner named for a spec that calls it directly. */ export function registerWorkflowsRecordLists() {
|
|
38
|
+
for (const kind of Object.keys(AUTOMATION_COLLECTIONS)){
|
|
39
|
+
registerPluginRecordListSource(kind, automationListSource(kind), {
|
|
40
|
+
pluginId: BUNDLE_ID
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=workflows-record-lists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/workflows-record-lists.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 registerPluginRecordListSource,\n type PluginRecordListSource,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport { collection, documentId, limit, orderBy, query } from 'firebase/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n AUTOMATION_COLLECTIONS,\n automationIndexedRecord,\n type AutomationRecordKind,\n} from './automation-record'\n\n/**\n * A site's workflows, webhooks and actions, listed in the console for another\n * plugin's picker or check (AGL-3080) — a computed variable choosing the\n * workflow that fills it, a reference check confirming one still exists.\n *\n * All three are a site's: asked at the organization, a source answers none,\n * and none is ever installed from a listing. Ordered by document id, so the\n * window is the same one every bounded list reads. Each record is\n * `automationIndexedRecord`'s, the server indexes' shape.\n */\nfunction automationListSource(kind: AutomationRecordKind): PluginRecordListSource {\n return {\n query(firestore, request) {\n return request.hostId && !request.installedFrom\n ? query(\n collection(firestore, 'hosts', request.hostId, AUTOMATION_COLLECTIONS[kind]),\n orderBy(documentId()),\n limit(request.limit),\n )\n : null\n },\n record: (id, data) => automationIndexedRecord(kind, id, data),\n }\n}\n\n/** Called from the console registrar, owner named for a spec that calls it directly. */\nexport function registerWorkflowsRecordLists(): void {\n for (const kind of Object.keys(AUTOMATION_COLLECTIONS) as AutomationRecordKind[]) {\n registerPluginRecordListSource(kind, automationListSource(kind), { pluginId: BUNDLE_ID })\n }\n}\n"],"names":["registerPluginRecordListSource","collection","documentId","limit","orderBy","query","BUNDLE_ID","AUTOMATION_COLLECTIONS","automationIndexedRecord","automationListSource","kind","firestore","request","hostId","installedFrom","record","id","data","registerWorkflowsRecordLists","Object","keys","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,8BAA8B,QAEzB,kDAAiD;AACxD,SAASC,UAAU,EAAEC,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,QAAQ,qBAAoB;AAClF,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SACEC,sBAAsB,EACtBC,uBAAuB,QAElB,yBAAqB;AAE5B;;;;;;;;;CASC,GACD,SAASC,qBAAqBC,IAA0B;IACtD,OAAO;QACLL,OAAMM,SAAS,EAAEC,OAAO;YACtB,OAAOA,QAAQC,MAAM,IAAI,CAACD,QAAQE,aAAa,GAC3CT,MACEJ,WAAWU,WAAW,SAASC,QAAQC,MAAM,EAAEN,sBAAsB,CAACG,KAAK,GAC3EN,QAAQF,eACRC,MAAMS,QAAQT,KAAK,KAErB;QACN;QACAY,QAAQ,CAACC,IAAIC,OAAST,wBAAwBE,MAAMM,IAAIC;IAC1D;AACF;AAEA,sFAAsF,GACtF,OAAO,SAASC;IACd,KAAK,MAAMR,QAAQS,OAAOC,IAAI,CAACb,wBAAmD;QAChFP,+BAA+BU,MAAMD,qBAAqBC,OAAO;YAAEW,UAAUf;QAAU;IACzF;AACF"}
|
|
@@ -60,6 +60,8 @@ export interface HostWorkflow {
|
|
|
60
60
|
export declare const WORKFLOW_MAX_STEPS = 25;
|
|
61
61
|
/** Max nesting across workflow→function→workflow cross-calls (AGL-129). */
|
|
62
62
|
export declare const CROSS_MAX_DEPTH = 3;
|
|
63
|
+
/** How deep one custom event may chain into another automation's run. */
|
|
64
|
+
export declare const ACTION_MAX_EVENT_DEPTH = 3;
|
|
63
65
|
export type WorkflowRunResult = {
|
|
64
66
|
ok: true;
|
|
65
67
|
value: number | string | boolean;
|
|
@@ -82,9 +84,19 @@ export declare function runWorkflow(workflow: HostWorkflow, functions: Record<st
|
|
|
82
84
|
*/
|
|
83
85
|
extraScope?: Record<string, number | string | boolean>, context?: WorkflowRunContext): WorkflowRunResult;
|
|
84
86
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* by the
|
|
87
|
+
* The reference a computed site variable carries to the workflow that
|
|
88
|
+
* computes it: the workflow's document id (AGL-261), and the name it had when
|
|
89
|
+
* it was picked, which a variable computed before ids existed carries alone.
|
|
90
|
+
* Written by the variables editor; read here.
|
|
91
|
+
*/
|
|
92
|
+
export interface WorkflowComputedVariable extends HostVariable {
|
|
93
|
+
workflowId?: string;
|
|
94
|
+
workflowName?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Computed variables (AGL-129): a variable naming a workflow takes that
|
|
98
|
+
* workflow's result as its value at compose time — by id first, then by
|
|
99
|
+
* name; failures keep the stored fallback value. Each computed variable
|
|
100
|
+
* evaluates once, bounded by the shared depth guard.
|
|
89
101
|
*/
|
|
90
102
|
export declare function resolveComputedVariables(variables: Record<string, HostVariable>, functions: Record<string, HostFunction>, workflows: Record<string, HostWorkflow>): Record<string, HostVariable>;
|
|
@@ -17,6 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
17
17
|
*/ import { evaluateExpression, evaluateHostFunction } from "@aglyn/aglyn/app-utils/functions";
|
|
18
18
|
export const WORKFLOW_MAX_STEPS = 25;
|
|
19
19
|
/** Max nesting across workflow→function→workflow cross-calls (AGL-129). */ export const CROSS_MAX_DEPTH = 3;
|
|
20
|
+
/** How deep one custom event may chain into another automation's run. */ export const ACTION_MAX_EVENT_DEPTH = 3;
|
|
20
21
|
/** Variable values as a typed expression scope (mirrors resolveBindings). */ function variableScope(variables) {
|
|
21
22
|
const scope = {};
|
|
22
23
|
for (const [key, variable] of Object.entries(variables)){
|
|
@@ -127,21 +128,21 @@ export function runWorkflow(workflow, functions, variables = {}, /**
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
/**
|
|
130
|
-
* Computed variables (AGL-129): a variable
|
|
131
|
-
*
|
|
132
|
-
* stored fallback value. Each computed variable
|
|
133
|
-
* by the shared depth guard.
|
|
131
|
+
* Computed variables (AGL-129): a variable naming a workflow takes that
|
|
132
|
+
* workflow's result as its value at compose time — by id first, then by
|
|
133
|
+
* name; failures keep the stored fallback value. Each computed variable
|
|
134
|
+
* evaluates once, bounded by the shared depth guard.
|
|
134
135
|
*/ export function resolveComputedVariables(variables, functions, workflows) {
|
|
135
136
|
const resolved = {};
|
|
136
137
|
// Lookup maps are double-keyed by id and name (AGL-185), so the same doc
|
|
137
138
|
// can appear under two keys — evaluate each workflow once per doc.
|
|
138
139
|
const memo = new Map();
|
|
139
140
|
for (const [name, variable] of Object.entries(variables)){
|
|
140
|
-
var _ref
|
|
141
|
-
|
|
142
|
-
const workflowId =
|
|
143
|
-
const workflowName =
|
|
144
|
-
const workflow = (
|
|
141
|
+
var _ref;
|
|
142
|
+
const source = variable;
|
|
143
|
+
const workflowId = typeof source.workflowId === 'string' ? source.workflowId.trim() : '';
|
|
144
|
+
const workflowName = typeof source.workflowName === 'string' ? source.workflowName.trim() : '';
|
|
145
|
+
const workflow = (_ref = workflowId ? workflows[workflowId] : undefined) != null ? _ref : workflowName ? workflows[workflowName] : undefined;
|
|
145
146
|
if (!workflow) {
|
|
146
147
|
resolved[name] = variable;
|
|
147
148
|
continue;
|