@oxygen-agent/cli 1.1003.12 → 1.1010.1
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 +1 -1
- package/dist/column-decision-options.d.ts +20 -0
- package/dist/column-decision-options.js +54 -0
- package/dist/command-manifest.js +15 -2
- package/dist/functions-commands.js +11 -11
- package/dist/index.js +1222 -159
- package/dist/search-ai-filter-notice.d.ts +17 -0
- package/dist/search-ai-filter-notice.js +38 -0
- package/dist/skills.js +34 -10
- package/dist/util.d.ts +9 -0
- package/dist/util.js +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/billing.js +45 -0
- package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +223 -13
- package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +5 -23
- package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +9 -4
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +11 -8
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +4 -4
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +4 -4
- package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +13 -23
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/index.js +2 -0
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +407 -14
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +29 -4
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +189 -36
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +21 -2
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +21 -1
- package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +5 -1
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
- package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +19 -1
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
- package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
- package/node_modules/@oxygen/shared/dist/version.js +8 -1
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
- package/node_modules/@oxygen/shared/package.json +9 -0
- package/package.json +2 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Stripe subscriptions are NOT an OXYGEN plan.
|
|
3
|
+
*
|
|
4
|
+
* OXYGEN runs three recurring rails on the same Stripe customer, and only one of
|
|
5
|
+
* them is a plan: the PLAN subscription (born from a Checkout Session),
|
|
6
|
+
* EMAIL INFRASTRUCTURE (`infra-subscription.ts`) and SENDING SEATS
|
|
7
|
+
* (`seat-subscription.ts`). The two rails are separate on purpose — a workspace
|
|
8
|
+
* with no plan at all must be able to hold managed inboxes and buy a sending
|
|
9
|
+
* seat — so a rail event is not a smaller version of a plan event, it is a
|
|
10
|
+
* different subject entirely, and plan-shaped handling of one is always wrong.
|
|
11
|
+
*
|
|
12
|
+
* THE WITNESS IS `metadata.oxygen_kind`. Both rails stamp it at
|
|
13
|
+
* `subscriptions.create` (the only two such call sites in the repo), and a plan
|
|
14
|
+
* subscription never carries it: a plan is born from a Checkout Session whose
|
|
15
|
+
* `subscription_data.metadata` carries organization_id/clerk_org_id and no
|
|
16
|
+
* `oxygen_kind`. (The third `oxygen_kind` in the codebase, `credit_topup`, rides
|
|
17
|
+
* a one-off `mode: "payment"` Checkout Session and never produces a
|
|
18
|
+
* subscription at all.) So the key's PRESENCE is a positive witness of "not a
|
|
19
|
+
* plan".
|
|
20
|
+
*
|
|
21
|
+
* PRESENCE, NOT A VALUE LIST, deliberately. Every previous gate matched the one
|
|
22
|
+
* value `"email_infra"`, which is why the seat rail — shipped later — walked
|
|
23
|
+
* straight into the plan handlers: adding a rail silently opted it INTO plan
|
|
24
|
+
* treatment, the wrong default for a safety gate. Testing presence means the
|
|
25
|
+
* next rail needs no edit here. This is the same reasoning `orphan-reconcile.ts`
|
|
26
|
+
* measured in production on 2026-09-06, where all 24 of the 24 daily
|
|
27
|
+
* `billing.subscription_health.unknown_plan_price` error lines were the same 4
|
|
28
|
+
* live sending-seat subscriptions x 6 cron runs.
|
|
29
|
+
*
|
|
30
|
+
* DELIBERATELY NARROW. This answers "did the subscription declare itself a
|
|
31
|
+
* non-plan rail", never "does it carry a recognised plan price". The catalog
|
|
32
|
+
* question stays with `tierFromPriceId` / `recognizedStripePlanItems`, and
|
|
33
|
+
* callers that need both keep asking both — `orphan-reconcile.ts` reclassifies
|
|
34
|
+
* only the zero-recognised-item half for exactly that reason.
|
|
35
|
+
*/
|
|
36
|
+
/** The declared non-plan rail (`email_infra`, `sending_seats`, …), or null for a plan. */
|
|
37
|
+
export function nonPlanSubscriptionKind(metadata) {
|
|
38
|
+
const kind = metadata?.oxygen_kind;
|
|
39
|
+
return typeof kind === "string" && kind.length > 0 ? kind : null;
|
|
40
|
+
}
|
|
41
|
+
/** True when the subscription declared itself one of OXYGEN's non-plan rails. */
|
|
42
|
+
export function isNonPlanSubscriptionKind(metadata) {
|
|
43
|
+
return nonPlanSubscriptionKind(metadata) !== null;
|
|
44
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare const MAX_TABLE_ACTION_RUN_ROWS = 500000;
|
|
2
|
+
/** Only runs whose resolved steps are all provider-free formulas may use these ceilings. */
|
|
3
|
+
export declare const MAX_FORMULA_ACTION_RUN_ROWS = 1000000;
|
|
4
|
+
export declare const MAX_FORMULA_ACTION_RUN_ITEMS = 5000000;
|
|
2
5
|
export declare const MAX_WORKSPACE_ROW_DELETE_ROWS = 50000;
|
|
3
6
|
/**
|
|
4
7
|
* Platform ceiling for a table action run's `max_concurrency` (T-27).
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Shared request/action ceilings used by tenant-db enforcement and every
|
|
2
2
|
// surface that resolves a symbolic row selection before invoking it.
|
|
3
3
|
export const MAX_TABLE_ACTION_RUN_ROWS = 500_000;
|
|
4
|
+
/** Only runs whose resolved steps are all provider-free formulas may use these ceilings. */
|
|
5
|
+
export const MAX_FORMULA_ACTION_RUN_ROWS = 1_000_000;
|
|
6
|
+
export const MAX_FORMULA_ACTION_RUN_ITEMS = 5_000_000;
|
|
4
7
|
export const MAX_WORKSPACE_ROW_DELETE_ROWS = 50_000;
|
|
5
8
|
/**
|
|
6
9
|
* Platform ceiling for a table action run's `max_concurrency` (T-27).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DiagLogLevel, type DiagLogger } from "@opentelemetry/api";
|
|
2
|
+
/**
|
|
3
|
+
* Makes a failing OTLP trace/metric export visible in LOGS.
|
|
4
|
+
*
|
|
5
|
+
* The SDK exporters report a failed export only through their result callback,
|
|
6
|
+
* and the processors hand that to `diag`, which has no logger unless one is
|
|
7
|
+
* installed — so a broken collector path (SigNoz over the private network) and a
|
|
8
|
+
* healthy one look identical from the sending process. This wraps an exporter so
|
|
9
|
+
* a failed export becomes one throttled `telemetry.export_failed` line and the
|
|
10
|
+
* first success afterwards one `telemetry.export_recovered` line, both through
|
|
11
|
+
* `log()` and therefore through whichever log sinks the process has.
|
|
12
|
+
*
|
|
13
|
+
* No recursion: `log()` records go to the LOG sinks, never to a trace or metric
|
|
14
|
+
* exporter, so a failing trace/metric lane cannot feed itself.
|
|
15
|
+
*
|
|
16
|
+
* Never throws, never changes what the SDK sees: the inner result is passed to
|
|
17
|
+
* the SDK callback untouched, every other exporter method is forwarded, and a
|
|
18
|
+
* synchronous throw from the inner exporter is recorded and rethrown exactly as
|
|
19
|
+
* it would have been without the wrapper.
|
|
20
|
+
*
|
|
21
|
+
* Nothing from the error's text is logged. OTLP errors carry receiver response
|
|
22
|
+
* bodies (`OTLPExporterError.data`) and transport errors can carry URLs; the
|
|
23
|
+
* failure is reduced to an HTTP status and a fixed code (see
|
|
24
|
+
* `classifyTelemetryExportError`), and the endpoint to origin + path.
|
|
25
|
+
*
|
|
26
|
+
* Structural types, not `@opentelemetry/core`/`sdk-metrics` imports: this
|
|
27
|
+
* package depends on neither, and the shapes used here (`{ code, error }`, the
|
|
28
|
+
* `ResourceMetrics` tree) are the SDK's stable public contract.
|
|
29
|
+
*/
|
|
30
|
+
export type TelemetryExportSignal = "traces" | "metrics";
|
|
31
|
+
export type TelemetryExportSurface = "web" | "worker";
|
|
32
|
+
type ExportResultLike = {
|
|
33
|
+
code: number;
|
|
34
|
+
error?: Error | undefined;
|
|
35
|
+
};
|
|
36
|
+
type ObservableExporter = {
|
|
37
|
+
export(items: unknown, resultCallback: (result: ExportResultLike) => void): void;
|
|
38
|
+
shutdown(): Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
export type TelemetryExportObserverOptions = {
|
|
41
|
+
signal: TelemetryExportSignal;
|
|
42
|
+
/** The exporter's full URL; only origin + path is ever logged. */
|
|
43
|
+
endpoint: string;
|
|
44
|
+
surface: TelemetryExportSurface;
|
|
45
|
+
/** At most one `telemetry.export_failed` line per window. Default 60 s. */
|
|
46
|
+
windowMs?: number | undefined;
|
|
47
|
+
now?: (() => number) | undefined;
|
|
48
|
+
};
|
|
49
|
+
/** Origin + path only: never query strings, fragments or URL credentials. */
|
|
50
|
+
export declare function telemetryEndpointForLog(endpoint: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Reduces an OTLP export error to `{ status?, error_code }`. `status` is the
|
|
53
|
+
* HTTP status an `OTLPExporterError` carries in `code` for a non-retryable
|
|
54
|
+
* response; a retryable one (429/502/503/504) that exhausted its retries carries
|
|
55
|
+
* none, hence `http_retryable`. The message is matched against the exporter's
|
|
56
|
+
* own fixed strings and never copied.
|
|
57
|
+
*/
|
|
58
|
+
export declare function classifyTelemetryExportError(error: unknown): {
|
|
59
|
+
status?: number;
|
|
60
|
+
error_code: string;
|
|
61
|
+
};
|
|
62
|
+
export declare function observeTelemetryExporter<E extends ObservableExporter>(inner: E, options: TelemetryExportObserverOptions): E;
|
|
63
|
+
export type TelemetryDropDiagLoggerOptions = {
|
|
64
|
+
surface: TelemetryExportSurface;
|
|
65
|
+
/** An existing diagnostics logger that still receives every call unchanged. */
|
|
66
|
+
next?: DiagLogger | undefined;
|
|
67
|
+
windowMs?: number | undefined;
|
|
68
|
+
now?: (() => number) | undefined;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* A `diag` logger that recognises exactly one message: the BatchSpanProcessor's
|
|
72
|
+
* `Dropped N spans because maxQueueSize reached`, its only report of queue-full
|
|
73
|
+
* drops. Those become a throttled `telemetry.export_dropped` line carrying the
|
|
74
|
+
* count; every other diag call is ignored, because SDK diagnostics can carry
|
|
75
|
+
* receiver responses and exported payloads (the OTLP delegate logs the items it
|
|
76
|
+
* is about to send at debug). Nothing but the parsed count leaves this function.
|
|
77
|
+
*
|
|
78
|
+
* The count cannot name which BatchSpanProcessor dropped: diag is process-wide,
|
|
79
|
+
* so when a process also runs the Axiom processor, a drop line covers both.
|
|
80
|
+
*
|
|
81
|
+
* `next` keeps an operator-enabled diagnostics logger working: it receives every
|
|
82
|
+
* call as before, and this logger only adds the drop count.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createTelemetryDropDiagLogger(options: TelemetryDropDiagLoggerOptions): DiagLogger;
|
|
85
|
+
/**
|
|
86
|
+
* Installs `createTelemetryDropDiagLogger` as the global diag logger, once per
|
|
87
|
+
* process. Returns false when it was already installed. Callers install it only
|
|
88
|
+
* when an OTLP collector endpoint is configured, so a process without one keeps
|
|
89
|
+
* its diag configuration exactly as it was.
|
|
90
|
+
*/
|
|
91
|
+
export declare function installTelemetryDropDiagLogger(options: TelemetryDropDiagLoggerOptions & {
|
|
92
|
+
logLevel?: DiagLogLevel | undefined;
|
|
93
|
+
}): boolean;
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { diag, DiagLogLevel } from "@opentelemetry/api";
|
|
2
|
+
import { log } from "./log.js";
|
|
3
|
+
// @opentelemetry/core ExportResultCode.SUCCESS / FAILED.
|
|
4
|
+
const EXPORT_RESULT_SUCCESS = 0;
|
|
5
|
+
const DEFAULT_WINDOW_MS = 60_000;
|
|
6
|
+
// Methods the SDK may call on an exporter besides `export`. Forwarded only when
|
|
7
|
+
// the inner exporter has them, so a reader that probes for an optional method
|
|
8
|
+
// (PeriodicExportingMetricReader reads selectAggregationTemporality) sees
|
|
9
|
+
// exactly what it would have seen on the unwrapped exporter.
|
|
10
|
+
const FORWARDED_METHODS = [
|
|
11
|
+
"forceFlush",
|
|
12
|
+
"selectAggregationTemporality",
|
|
13
|
+
"selectAggregation",
|
|
14
|
+
];
|
|
15
|
+
/** Origin + path only: never query strings, fragments or URL credentials. */
|
|
16
|
+
export function telemetryEndpointForLog(endpoint) {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(endpoint);
|
|
19
|
+
return `${url.origin}${url.pathname}`;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "invalid_url";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const SYSTEM_ERROR_CODES = {
|
|
26
|
+
ETIMEDOUT: "timeout",
|
|
27
|
+
ESOCKETTIMEDOUT: "timeout",
|
|
28
|
+
ECONNREFUSED: "connection_refused",
|
|
29
|
+
ECONNRESET: "connection_reset",
|
|
30
|
+
EPIPE: "connection_reset",
|
|
31
|
+
ENOTFOUND: "dns_failed",
|
|
32
|
+
EAI_AGAIN: "dns_failed",
|
|
33
|
+
EHOSTUNREACH: "host_unreachable",
|
|
34
|
+
ENETUNREACH: "host_unreachable",
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Reduces an OTLP export error to `{ status?, error_code }`. `status` is the
|
|
38
|
+
* HTTP status an `OTLPExporterError` carries in `code` for a non-retryable
|
|
39
|
+
* response; a retryable one (429/502/503/504) that exhausted its retries carries
|
|
40
|
+
* none, hence `http_retryable`. The message is matched against the exporter's
|
|
41
|
+
* own fixed strings and never copied.
|
|
42
|
+
*/
|
|
43
|
+
export function classifyTelemetryExportError(error) {
|
|
44
|
+
if (!error || typeof error !== "object")
|
|
45
|
+
return { error_code: "unknown" };
|
|
46
|
+
const candidate = error;
|
|
47
|
+
const code = candidate.code;
|
|
48
|
+
if (typeof code === "number" && Number.isInteger(code) && code >= 100 && code <= 599) {
|
|
49
|
+
return { status: code, error_code: code >= 500 ? "http_5xx" : `http_${code}` };
|
|
50
|
+
}
|
|
51
|
+
const systemCode = typeof code === "string"
|
|
52
|
+
? code
|
|
53
|
+
: typeof candidate.cause?.code === "string"
|
|
54
|
+
? candidate.cause.code
|
|
55
|
+
: undefined;
|
|
56
|
+
if (systemCode)
|
|
57
|
+
return { error_code: SYSTEM_ERROR_CODES[systemCode] ?? "transport_failed" };
|
|
58
|
+
const message = typeof candidate.message === "string" ? candidate.message : "";
|
|
59
|
+
if (candidate.name === "TimeoutError" || /timed out|timeout/i.test(message))
|
|
60
|
+
return { error_code: "timeout" };
|
|
61
|
+
if (/concurren(?:t|cy) (?:export )?limit/i.test(message))
|
|
62
|
+
return { error_code: "concurrency_limit" };
|
|
63
|
+
if (/retryable status/i.test(message))
|
|
64
|
+
return { error_code: "http_retryable" };
|
|
65
|
+
if (/exceeded size limit/i.test(message))
|
|
66
|
+
return { error_code: "response_too_large" };
|
|
67
|
+
return { error_code: "transport_failed" };
|
|
68
|
+
}
|
|
69
|
+
function countItems(signal, items) {
|
|
70
|
+
if (signal === "traces")
|
|
71
|
+
return Array.isArray(items) ? items.length : 0;
|
|
72
|
+
// Metric data points (= live series under cumulative temporality).
|
|
73
|
+
let points = 0;
|
|
74
|
+
const scopeMetrics = items?.scopeMetrics;
|
|
75
|
+
if (!Array.isArray(scopeMetrics))
|
|
76
|
+
return 0;
|
|
77
|
+
for (const scope of scopeMetrics) {
|
|
78
|
+
const metrics = scope?.metrics;
|
|
79
|
+
if (!Array.isArray(metrics))
|
|
80
|
+
continue;
|
|
81
|
+
for (const metric of metrics) {
|
|
82
|
+
const dataPoints = metric?.dataPoints;
|
|
83
|
+
if (Array.isArray(dataPoints))
|
|
84
|
+
points += dataPoints.length;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return points;
|
|
88
|
+
}
|
|
89
|
+
export function observeTelemetryExporter(inner, options) {
|
|
90
|
+
const endpoint = telemetryEndpointForLog(options.endpoint);
|
|
91
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
92
|
+
const now = options.now ?? Date.now;
|
|
93
|
+
const { signal, surface } = options;
|
|
94
|
+
let consecutiveFailures = 0;
|
|
95
|
+
let outageStartedAt = 0;
|
|
96
|
+
let outageDropped = 0;
|
|
97
|
+
// Only an outage that produced an export_failed line gets a recovery line, so
|
|
98
|
+
// a flapping endpoint cannot bypass the failure throttle through recoveries.
|
|
99
|
+
let outageReported = false;
|
|
100
|
+
let lastFailureLogAt = Number.NEGATIVE_INFINITY;
|
|
101
|
+
// Accumulated since the last emitted export_failed line, reported on the next.
|
|
102
|
+
let suppressedFailures = 0;
|
|
103
|
+
let pendingDropped = 0;
|
|
104
|
+
const recordFailure = (error, dropped) => {
|
|
105
|
+
try {
|
|
106
|
+
const at = now();
|
|
107
|
+
if (consecutiveFailures === 0) {
|
|
108
|
+
outageStartedAt = at;
|
|
109
|
+
outageDropped = 0;
|
|
110
|
+
outageReported = false;
|
|
111
|
+
}
|
|
112
|
+
consecutiveFailures += 1;
|
|
113
|
+
outageDropped += dropped;
|
|
114
|
+
pendingDropped += dropped;
|
|
115
|
+
if (at - lastFailureLogAt < windowMs) {
|
|
116
|
+
suppressedFailures += 1;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
lastFailureLogAt = at;
|
|
120
|
+
outageReported = true;
|
|
121
|
+
const { status, error_code } = classifyTelemetryExportError(error);
|
|
122
|
+
log("warn", "telemetry.export_failed", {
|
|
123
|
+
signal,
|
|
124
|
+
endpoint,
|
|
125
|
+
...(status === undefined ? {} : { status }),
|
|
126
|
+
error_code,
|
|
127
|
+
dropped: pendingDropped,
|
|
128
|
+
consecutive_failures: consecutiveFailures,
|
|
129
|
+
suppressed_failures: suppressedFailures,
|
|
130
|
+
surface,
|
|
131
|
+
});
|
|
132
|
+
pendingDropped = 0;
|
|
133
|
+
suppressedFailures = 0;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Observation must never change export semantics.
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const recordSuccess = () => {
|
|
140
|
+
try {
|
|
141
|
+
if (consecutiveFailures === 0)
|
|
142
|
+
return;
|
|
143
|
+
if (outageReported) {
|
|
144
|
+
log("info", "telemetry.export_recovered", {
|
|
145
|
+
signal,
|
|
146
|
+
endpoint,
|
|
147
|
+
failures: consecutiveFailures,
|
|
148
|
+
dropped: outageDropped,
|
|
149
|
+
outage_ms: Math.max(0, now() - outageStartedAt),
|
|
150
|
+
surface,
|
|
151
|
+
});
|
|
152
|
+
// Every suppressed failure since the last line belonged to this outage,
|
|
153
|
+
// and the recovery line has just accounted for it.
|
|
154
|
+
pendingDropped = 0;
|
|
155
|
+
suppressedFailures = 0;
|
|
156
|
+
}
|
|
157
|
+
consecutiveFailures = 0;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Observation must never change export semantics.
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const observed = {
|
|
164
|
+
export(items, resultCallback) {
|
|
165
|
+
let dropped = 0;
|
|
166
|
+
try {
|
|
167
|
+
dropped = countItems(signal, items);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Counting is best-effort; never let it block the export itself.
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
inner.export(items, (result) => {
|
|
174
|
+
if (result?.code === EXPORT_RESULT_SUCCESS)
|
|
175
|
+
recordSuccess();
|
|
176
|
+
else
|
|
177
|
+
recordFailure(result?.error, dropped);
|
|
178
|
+
resultCallback(result);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
recordFailure(error, dropped);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
shutdown() {
|
|
187
|
+
return inner.shutdown();
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
for (const method of FORWARDED_METHODS) {
|
|
191
|
+
const fn = inner[method];
|
|
192
|
+
if (typeof fn === "function")
|
|
193
|
+
observed[method] = fn.bind(inner);
|
|
194
|
+
}
|
|
195
|
+
return observed;
|
|
196
|
+
}
|
|
197
|
+
const DROPPED_SPANS_MESSAGE = /^Dropped (\d+) spans because maxQueueSize reached$/;
|
|
198
|
+
/**
|
|
199
|
+
* A `diag` logger that recognises exactly one message: the BatchSpanProcessor's
|
|
200
|
+
* `Dropped N spans because maxQueueSize reached`, its only report of queue-full
|
|
201
|
+
* drops. Those become a throttled `telemetry.export_dropped` line carrying the
|
|
202
|
+
* count; every other diag call is ignored, because SDK diagnostics can carry
|
|
203
|
+
* receiver responses and exported payloads (the OTLP delegate logs the items it
|
|
204
|
+
* is about to send at debug). Nothing but the parsed count leaves this function.
|
|
205
|
+
*
|
|
206
|
+
* The count cannot name which BatchSpanProcessor dropped: diag is process-wide,
|
|
207
|
+
* so when a process also runs the Axiom processor, a drop line covers both.
|
|
208
|
+
*
|
|
209
|
+
* `next` keeps an operator-enabled diagnostics logger working: it receives every
|
|
210
|
+
* call as before, and this logger only adds the drop count.
|
|
211
|
+
*/
|
|
212
|
+
export function createTelemetryDropDiagLogger(options) {
|
|
213
|
+
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
|
|
214
|
+
const now = options.now ?? Date.now;
|
|
215
|
+
const { next, surface } = options;
|
|
216
|
+
let pending = 0;
|
|
217
|
+
let lastEmitAt = Number.NEGATIVE_INFINITY;
|
|
218
|
+
let timer = null;
|
|
219
|
+
let emitting = false;
|
|
220
|
+
const emit = () => {
|
|
221
|
+
if (pending === 0 || emitting)
|
|
222
|
+
return;
|
|
223
|
+
emitting = true;
|
|
224
|
+
try {
|
|
225
|
+
lastEmitAt = now();
|
|
226
|
+
const dropped = pending;
|
|
227
|
+
pending = 0;
|
|
228
|
+
log("warn", "telemetry.export_dropped", { signal: "traces", dropped, reason: "queue_full", surface });
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
emitting = false;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const record = (dropped) => {
|
|
235
|
+
pending += dropped;
|
|
236
|
+
const remaining = windowMs - (now() - lastEmitAt);
|
|
237
|
+
if (remaining <= 0) {
|
|
238
|
+
emit();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// The BatchSpanProcessor reports a drop run only once it ends, so a
|
|
242
|
+
// suppressed count could otherwise wait forever for another drop to carry it.
|
|
243
|
+
if (timer)
|
|
244
|
+
return;
|
|
245
|
+
timer = setTimeout(() => {
|
|
246
|
+
timer = null;
|
|
247
|
+
try {
|
|
248
|
+
emit();
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Telemetry about telemetry never throws.
|
|
252
|
+
}
|
|
253
|
+
}, remaining);
|
|
254
|
+
timer.unref?.();
|
|
255
|
+
};
|
|
256
|
+
const forward = (level) => (message, ...args) => {
|
|
257
|
+
if (level === "warn") {
|
|
258
|
+
try {
|
|
259
|
+
const match = typeof message === "string" ? DROPPED_SPANS_MESSAGE.exec(message) : null;
|
|
260
|
+
const dropped = match ? Number(match[1]) : 0;
|
|
261
|
+
if (Number.isSafeInteger(dropped) && dropped > 0)
|
|
262
|
+
record(dropped);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Telemetry about telemetry never throws.
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
next?.[level](message, ...args);
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
error: forward("error"),
|
|
272
|
+
warn: forward("warn"),
|
|
273
|
+
info: forward("info"),
|
|
274
|
+
debug: forward("debug"),
|
|
275
|
+
verbose: forward("verbose"),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// globalThis, not a module-local: Next.js bundles can load this module more
|
|
279
|
+
// than once per process, and a second `diag.setLogger` would replace the first
|
|
280
|
+
// (and, without suppressOverrideMessage, log an override warning).
|
|
281
|
+
const DIAG_BRIDGE_SLOT = Symbol.for("oxygen.telemetry.diag_drop_bridge");
|
|
282
|
+
/**
|
|
283
|
+
* Installs `createTelemetryDropDiagLogger` as the global diag logger, once per
|
|
284
|
+
* process. Returns false when it was already installed. Callers install it only
|
|
285
|
+
* when an OTLP collector endpoint is configured, so a process without one keeps
|
|
286
|
+
* its diag configuration exactly as it was.
|
|
287
|
+
*/
|
|
288
|
+
export function installTelemetryDropDiagLogger(options) {
|
|
289
|
+
const carrier = globalThis;
|
|
290
|
+
if (carrier[DIAG_BRIDGE_SLOT])
|
|
291
|
+
return false;
|
|
292
|
+
carrier[DIAG_BRIDGE_SLOT] = true;
|
|
293
|
+
diag.setLogger(createTelemetryDropDiagLogger(options), {
|
|
294
|
+
logLevel: options.logLevel ?? DiagLogLevel.WARN,
|
|
295
|
+
suppressOverrideMessage: true,
|
|
296
|
+
});
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
@@ -3,6 +3,17 @@ export type WithTelemetrySpanOptions = {
|
|
|
3
3
|
isTransient?: (error: unknown) => boolean;
|
|
4
4
|
};
|
|
5
5
|
export declare function withTelemetrySpan<T>(tracerName: string, name: string, attributes: TelemetryAttributes | undefined, fn: () => Promise<T>, options?: WithTelemetrySpanOptions): Promise<T>;
|
|
6
|
+
/**
|
|
7
|
+
* The active global span's trace id: the join key into SigNoz/Axiom traces.
|
|
8
|
+
* Null when no valid span is active (a BullMQ job outside any span, a test).
|
|
9
|
+
*/
|
|
10
|
+
export declare function activeTelemetryTraceId(): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Run `fn` inside a new span only when no span is active, so work that already
|
|
13
|
+
* sits under a request/cycle span keeps that trace and uncorrelated background
|
|
14
|
+
* work (BullMQ jobs) still gets one trace id to cross-link from Langfuse.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withTelemetrySpanIfNone<T>(tracerName: string, name: string, attributes: TelemetryAttributes | undefined, fn: () => Promise<T>): Promise<T>;
|
|
6
17
|
export declare function setActiveTelemetryAttributes(attributes: TelemetryAttributes): void;
|
|
7
18
|
export declare function markActiveTelemetryError(message: string, attributes?: TelemetryAttributes): void;
|
|
8
19
|
export declare function recordTelemetryCounter(name: string, value?: number, attributes?: TelemetryAttributes): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SpanStatusCode, metrics, trace, } from "@opentelemetry/api";
|
|
1
|
+
import { SpanStatusCode, isSpanContextValid, metrics, trace, } from "@opentelemetry/api";
|
|
2
2
|
import { deployEnvTelemetryEnvironment, deployPlatformAttribute } from "./deploy-env.js";
|
|
3
3
|
import { errorId } from "./log.js";
|
|
4
4
|
import { normalizeTelemetryAttributes } from "./redaction.js";
|
|
@@ -39,6 +39,24 @@ export async function withTelemetrySpan(tracerName, name, attributes, fn, option
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The active global span's trace id: the join key into SigNoz/Axiom traces.
|
|
44
|
+
* Null when no valid span is active (a BullMQ job outside any span, a test).
|
|
45
|
+
*/
|
|
46
|
+
export function activeTelemetryTraceId() {
|
|
47
|
+
const spanContext = trace.getActiveSpan()?.spanContext();
|
|
48
|
+
return spanContext && isSpanContextValid(spanContext) ? spanContext.traceId : null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run `fn` inside a new span only when no span is active, so work that already
|
|
52
|
+
* sits under a request/cycle span keeps that trace and uncorrelated background
|
|
53
|
+
* work (BullMQ jobs) still gets one trace id to cross-link from Langfuse.
|
|
54
|
+
*/
|
|
55
|
+
export async function withTelemetrySpanIfNone(tracerName, name, attributes, fn) {
|
|
56
|
+
if (activeTelemetryTraceId())
|
|
57
|
+
return await fn();
|
|
58
|
+
return await withTelemetrySpan(tracerName, name, attributes, fn);
|
|
59
|
+
}
|
|
42
60
|
export function setActiveTelemetryAttributes(attributes) {
|
|
43
61
|
const span = trace.getActiveSpan();
|
|
44
62
|
if (!span)
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
/** UGC is a Publishing capability. These are projections, never a second Posts store. */
|
|
2
2
|
export type UgcApprovalMode = "creator" | "delegated";
|
|
3
|
+
/** Per calendar week or month, or `total`: N posts over the program's lifetime. */
|
|
4
|
+
export type UgcPostingTargetPeriod = "week" | "month" | "total";
|
|
5
|
+
export type UgcPostingTarget = {
|
|
6
|
+
count: number;
|
|
7
|
+
period: UgcPostingTargetPeriod;
|
|
8
|
+
};
|
|
9
|
+
export declare const UGC_POSTING_TARGET_MAX = 100;
|
|
10
|
+
export declare const UGC_POSTING_TARGET_PERIODS: readonly ["week", "month", "total"];
|
|
11
|
+
/** The operator-written creator brief is short by design (Philipp, 2026-09-23). */
|
|
12
|
+
export declare const UGC_PROGRAM_BRIEF_MAX_CHARS = 1200;
|
|
13
|
+
/** The program's target as its API shape, or null when none is set. */
|
|
14
|
+
export declare function ugcPostingTarget(program: {
|
|
15
|
+
postingTargetCount?: number | null | undefined;
|
|
16
|
+
postingTargetPeriod?: UgcPostingTargetPeriod | null | undefined;
|
|
17
|
+
}): UgcPostingTarget | null;
|
|
3
18
|
export type UgcProgram = {
|
|
4
19
|
id: string;
|
|
5
20
|
name: string;
|
|
@@ -10,20 +25,17 @@ export type UgcProgram = {
|
|
|
10
25
|
peerEngagementEnabled?: boolean;
|
|
11
26
|
peerEngagementVersion?: number;
|
|
12
27
|
peerEngagementEffectiveAt?: Date | null;
|
|
28
|
+
/** The product's icon, found from its site at create; null for older programs. */
|
|
29
|
+
iconUrl?: string | null;
|
|
30
|
+
/** Short operator-written creator brief; drafts are grounded in it. */
|
|
31
|
+
brief?: string | null;
|
|
32
|
+
/** Posts each creator is expected to publish per period; both null when unset. */
|
|
33
|
+
postingTargetCount?: number | null;
|
|
34
|
+
postingTargetPeriod?: UgcPostingTargetPeriod | null;
|
|
13
35
|
status: "active" | "archived";
|
|
14
36
|
createdAt: Date;
|
|
15
37
|
updatedAt: Date;
|
|
16
38
|
};
|
|
17
|
-
export type UgcBrief = {
|
|
18
|
-
id: string;
|
|
19
|
-
programId: string;
|
|
20
|
-
title: string;
|
|
21
|
-
prompt: string;
|
|
22
|
-
questions: string[];
|
|
23
|
-
cycle: string | null;
|
|
24
|
-
createdAt: Date;
|
|
25
|
-
updatedAt: Date;
|
|
26
|
-
};
|
|
27
39
|
export type UgcPostProjection = {
|
|
28
40
|
/** Public author identity from the exact participation, never a private profile corpus. */
|
|
29
41
|
authorLinkedinUrl?: string;
|
|
@@ -84,7 +96,6 @@ export type UgcPostLink = {
|
|
|
84
96
|
masterOrgId: string;
|
|
85
97
|
brandApprovalRequired: boolean;
|
|
86
98
|
sourcePostId: string;
|
|
87
|
-
briefId: string | null;
|
|
88
99
|
attribution: Record<string, unknown>;
|
|
89
100
|
projection: UgcPostProjection;
|
|
90
101
|
sourceRevision: string;
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
export const UGC_POSTING_TARGET_MAX = 100;
|
|
2
|
+
export const UGC_POSTING_TARGET_PERIODS = ["week", "month", "total"];
|
|
3
|
+
/** The operator-written creator brief is short by design (Philipp, 2026-09-23). */
|
|
4
|
+
export const UGC_PROGRAM_BRIEF_MAX_CHARS = 1200;
|
|
5
|
+
/** The program's target as its API shape, or null when none is set. */
|
|
6
|
+
export function ugcPostingTarget(program) {
|
|
7
|
+
return program.postingTargetCount && program.postingTargetPeriod
|
|
8
|
+
? { count: program.postingTargetCount, period: program.postingTargetPeriod }
|
|
9
|
+
: null;
|
|
10
|
+
}
|
|
1
11
|
export const UGC_MONTHLY_CREATOR_CREDITS = 10_000;
|
|
2
12
|
export const UGC_SPONSORED_LINKEDIN_SEATS = 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const OXYGEN_BUILD_VERSION = "1.1010.1";
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { OXYGEN_BUILD_VERSION } from "./version.generated.js";
|
|
1
2
|
// Release metadata is a string API: a new version must not change the exported
|
|
2
3
|
// declaration type and invalidate every dependent package's compiler state.
|
|
3
|
-
|
|
4
|
+
//
|
|
5
|
+
// The value is DERIVED, never pinned per commit: the repo-root `VERSION` file
|
|
6
|
+
// holds the series (`1.<minor>`) and the patch is the distance from the commit
|
|
7
|
+
// that last touched it. scripts/ci/version-stamp.mjs writes
|
|
8
|
+
// ./version.generated.ts (gitignored) at build, test and publish time. Change
|
|
9
|
+
// the series by editing VERSION; there is nothing to bump here.
|
|
10
|
+
export const OXYGEN_VERSION = OXYGEN_BUILD_VERSION;
|
|
4
11
|
// The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
|
|
5
12
|
// operational route. Raising it hard-rejects every older CLI from the entire
|
|
6
13
|
// product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
|
|
@@ -3,7 +3,6 @@ import { CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS, CRM_WORKFLOW_EVENT_SOURCE, } fro
|
|
|
3
3
|
import { SEQUENCE_CONTACT_ACTIVITY_EVENT, SEQUENCE_CRM_EVENT_DEFINITIONS, SEQUENCE_WORKFLOW_EVENT_SOURCE, } from "./sequence-crm-events.js";
|
|
4
4
|
import { SEQUENCE_ENROLLMENT_TERMINAL_EVENT, SEQUENCE_TERMINAL_EVENT_SOURCE, SEQUENCE_TERMINAL_STATUSES, } from "./sequence-terminal-events.js";
|
|
5
5
|
import { NOTETAKER_MEETING_COMPLETED_EVENT, NOTETAKER_MEETING_COMPLETED_PAYLOAD_FIELDS, NOTETAKER_WORKFLOW_EVENT_SOURCE, } from "./notetaker-events.js";
|
|
6
|
-
import { PLAIN_SUPPORT_MESSAGE_RECEIVED_EVENT, PLAIN_SUPPORT_MESSAGE_RECEIVED_PAYLOAD_FIELDS, PLAIN_SUPPORT_WORKFLOW_EVENT_SOURCE, } from "./plain-support-events.js";
|
|
7
6
|
export const WORKSPACE_EVENT_PRODUCER_READINESS_REQUIREMENTS = [
|
|
8
7
|
"always",
|
|
9
8
|
"notetaker_enabled",
|
|
@@ -107,15 +106,6 @@ export const WORKSPACE_EVENT_PRODUCERS = [
|
|
|
107
106
|
readiness: "notetaker_enabled",
|
|
108
107
|
payloadFields: NOTETAKER_MEETING_COMPLETED_PAYLOAD_FIELDS,
|
|
109
108
|
},
|
|
110
|
-
{
|
|
111
|
-
id: "plain_support.message_received",
|
|
112
|
-
source: PLAIN_SUPPORT_WORKFLOW_EVENT_SOURCE,
|
|
113
|
-
event: PLAIN_SUPPORT_MESSAGE_RECEIVED_EVENT,
|
|
114
|
-
delivery: "direct_durable_write",
|
|
115
|
-
idempotency: "The verified Plain event id deduplicates metadata-only support delivery.",
|
|
116
|
-
readiness: "always",
|
|
117
|
-
payloadFields: PLAIN_SUPPORT_MESSAGE_RECEIVED_PAYLOAD_FIELDS,
|
|
118
|
-
},
|
|
119
109
|
];
|
|
120
110
|
const workspaceEventProducersById = new Map(WORKSPACE_EVENT_PRODUCERS.map((producer) => [producer.id, producer]));
|
|
121
111
|
export function getWorkspaceEventProducer(producerId) {
|
|
@@ -224,18 +214,6 @@ const crmSignalEntries = CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS.map((definition)
|
|
|
224
214
|
description: definition.description,
|
|
225
215
|
payloadFields: definition.payloadFields,
|
|
226
216
|
}));
|
|
227
|
-
const plainSupportEntries = [
|
|
228
|
-
{
|
|
229
|
-
id: "plain_support.message_received",
|
|
230
|
-
producerId: "plain_support.message_received",
|
|
231
|
-
source: PLAIN_SUPPORT_WORKFLOW_EVENT_SOURCE,
|
|
232
|
-
event: PLAIN_SUPPORT_MESSAGE_RECEIVED_EVENT,
|
|
233
|
-
group: "Support · Plain",
|
|
234
|
-
label: "Customer message received",
|
|
235
|
-
description: "A customer sent a Chat, email, Slack, Microsoft Teams, or Discord message into a canonical Plain Thread. The event contains routing metadata only; customer content remains in Plain.",
|
|
236
|
-
payloadFields: PLAIN_SUPPORT_MESSAGE_RECEIVED_PAYLOAD_FIELDS,
|
|
237
|
-
},
|
|
238
|
-
];
|
|
239
217
|
const notetakerEntries = [
|
|
240
218
|
{
|
|
241
219
|
id: "notetaker.meeting_completed",
|
|
@@ -257,6 +235,5 @@ export const WORKSPACE_EVENT_CATALOG = [
|
|
|
257
235
|
...sequenceTerminalEntries,
|
|
258
236
|
...crmActivityEntries,
|
|
259
237
|
...crmSignalEntries,
|
|
260
|
-
...plainSupportEntries,
|
|
261
238
|
...notetakerEntries,
|
|
262
239
|
];
|