@oxygen-agent/cli 1.720.7 → 1.739.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +27 -12
  3. package/dist/index.js +227 -63
  4. package/dist/skills.js +2 -2
  5. package/node_modules/@oxygen/shared/dist/cli-result.js +2 -0
  6. package/node_modules/@oxygen/shared/dist/crm-activity-events.d.ts +20 -0
  7. package/node_modules/@oxygen/shared/dist/crm-activity-events.js +5 -0
  8. package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +27 -0
  9. package/node_modules/@oxygen/shared/dist/future-signup-events.js +90 -0
  10. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +320 -0
  11. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1233 -0
  12. package/node_modules/@oxygen/shared/dist/index.d.ts +6 -0
  13. package/node_modules/@oxygen/shared/dist/index.js +6 -0
  14. package/node_modules/@oxygen/shared/dist/notetaker-events.d.ts +4 -0
  15. package/node_modules/@oxygen/shared/dist/notetaker-events.js +23 -0
  16. package/node_modules/@oxygen/shared/dist/plain-support-events.d.ts +40 -0
  17. package/node_modules/@oxygen/shared/dist/plain-support-events.js +43 -0
  18. package/node_modules/@oxygen/shared/dist/redaction.js +6 -1
  19. package/node_modules/@oxygen/shared/dist/user-capability-routing.d.ts +2 -0
  20. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +23 -0
  21. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  22. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  23. package/node_modules/@oxygen/shared/dist/webhook-headers.d.ts +10 -0
  24. package/node_modules/@oxygen/shared/dist/webhook-headers.js +48 -0
  25. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.d.ts +31 -1
  26. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +112 -1
  27. package/node_modules/@oxygen/workflows/dist/graph/expression.js +7 -5
  28. package/node_modules/@oxygen/workflows/dist/graph/lint.d.ts +9 -0
  29. package/node_modules/@oxygen/workflows/dist/graph/lint.js +409 -7
  30. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +513 -0
  31. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +104 -1
  32. package/node_modules/@oxygen/workflows/dist/graph/params.d.ts +2 -0
  33. package/node_modules/@oxygen/workflows/dist/graph/params.js +15 -0
  34. package/node_modules/@oxygen/workflows/dist/graph/remap.js +12 -3
  35. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +9 -0
  36. package/node_modules/@oxygen/workflows/dist/graph/topology.js +33 -1
  37. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +123 -16
  38. package/node_modules/@oxygen/workflows/dist/graph/types.js +45 -0
  39. package/node_modules/@oxygen/workflows/dist/index.d.ts +94 -2
  40. package/node_modules/@oxygen/workflows/dist/index.js +169 -26
  41. package/node_modules/@oxygen/workflows/dist/portable.js +3 -1
  42. package/package.json +1 -1
@@ -5,7 +5,9 @@ export * from "./billing.js";
5
5
  export * from "./billing-anchors.js";
6
6
  export * from "./budget-scopes.js";
7
7
  export * from "./capability-discovery.js";
8
+ export * from "./user-capability-routing.js";
8
9
  export * from "./plan-limits.js";
10
+ export * from "./plain-support-events.js";
9
11
  export * from "./spend-safety.js";
10
12
  export * from "./cell-format.js";
11
13
  export * from "./cli-envelope.js";
@@ -22,6 +24,8 @@ export * from "./directory.js";
22
24
  export * from "./email-tracking-token.js";
23
25
  export * from "./email-unsubscribe-token.js";
24
26
  export * from "./error-redaction.js";
27
+ export * from "./future-signup-events.js";
28
+ export * from "./future-signup-lifecycle-projection.js";
25
29
  export * from "./feature-gates.js";
26
30
  export * from "./hosted-ai.js";
27
31
  export * from "./identifiers.js";
@@ -64,8 +68,10 @@ export * from "./timing.js";
64
68
  export * from "./type-guards.js";
65
69
  export * from "./worker-failures-queue.js";
66
70
  export * from "./workflow-mcp-tools.js";
71
+ export * from "./webhook-headers.js";
67
72
  export * from "./workflow-trigger-label.js";
68
73
  export * from "./workspace-event-catalog.js";
74
+ export * from "./notetaker-events.js";
69
75
  export * from "./workspace-agents.js";
70
76
  export declare const MAX_ROW_LOOP_WRITE_ROWS = 500;
71
77
  export type SemanticVersion = {
@@ -5,7 +5,9 @@ export * from "./billing.js";
5
5
  export * from "./billing-anchors.js";
6
6
  export * from "./budget-scopes.js";
7
7
  export * from "./capability-discovery.js";
8
+ export * from "./user-capability-routing.js";
8
9
  export * from "./plan-limits.js";
10
+ export * from "./plain-support-events.js";
9
11
  export * from "./spend-safety.js";
10
12
  export * from "./cell-format.js";
11
13
  export * from "./cli-envelope.js";
@@ -22,6 +24,8 @@ export * from "./directory.js";
22
24
  export * from "./email-tracking-token.js";
23
25
  export * from "./email-unsubscribe-token.js";
24
26
  export * from "./error-redaction.js";
27
+ export * from "./future-signup-events.js";
28
+ export * from "./future-signup-lifecycle-projection.js";
25
29
  export * from "./feature-gates.js";
26
30
  export * from "./hosted-ai.js";
27
31
  export * from "./identifiers.js";
@@ -75,8 +79,10 @@ export * from "./timing.js";
75
79
  export * from "./type-guards.js";
76
80
  export * from "./worker-failures-queue.js";
77
81
  export * from "./workflow-mcp-tools.js";
82
+ export * from "./webhook-headers.js";
78
83
  export * from "./workflow-trigger-label.js";
79
84
  export * from "./workspace-event-catalog.js";
85
+ export * from "./notetaker-events.js";
80
86
  export * from "./workspace-agents.js";
81
87
  // Maximum rows a single row-loop write (insert/upsert/preview) may process. The
82
88
  // row-loop engine issues one DB round-trip per row, so a 500-row write already
@@ -0,0 +1,4 @@
1
+ /** Canonical workflow-event contract emitted after a Meeting Notetaker run finishes. */
2
+ export declare const NOTETAKER_WORKFLOW_EVENT_SOURCE = "oxygen.notetaker";
3
+ export declare const NOTETAKER_MEETING_COMPLETED_EVENT = "meeting.completed";
4
+ export declare const NOTETAKER_MEETING_COMPLETED_PAYLOAD_FIELDS: readonly ["provider", "source", "event", "session.id", "session.title", "session.status", "session.capture_mode", "session.scheduled_start_at", "session.started_at", "session.ended_at", "session.completed_at", "session.credits_used", "session.web_url", "meeting_note.markdown", "meeting_note.web_url", "crm.activity_id", "crm.links", "artifacts"];
@@ -0,0 +1,23 @@
1
+ /** Canonical workflow-event contract emitted after a Meeting Notetaker run finishes. */
2
+ export const NOTETAKER_WORKFLOW_EVENT_SOURCE = "oxygen.notetaker";
3
+ export const NOTETAKER_MEETING_COMPLETED_EVENT = "meeting.completed";
4
+ export const NOTETAKER_MEETING_COMPLETED_PAYLOAD_FIELDS = [
5
+ "provider",
6
+ "source",
7
+ "event",
8
+ "session.id",
9
+ "session.title",
10
+ "session.status",
11
+ "session.capture_mode",
12
+ "session.scheduled_start_at",
13
+ "session.started_at",
14
+ "session.ended_at",
15
+ "session.completed_at",
16
+ "session.credits_used",
17
+ "session.web_url",
18
+ "meeting_note.markdown",
19
+ "meeting_note.web_url",
20
+ "crm.activity_id",
21
+ "crm.links",
22
+ "artifacts",
23
+ ];
@@ -0,0 +1,40 @@
1
+ export declare const PLAIN_SUPPORT_WORKFLOW_EVENT_SOURCE = "plain_support";
2
+ export declare const PLAIN_SUPPORT_MESSAGE_RECEIVED_EVENT = "message_received";
3
+ export declare const PLAIN_SUPPORT_WEBHOOK_VERSION = "2026-02-11";
4
+ export declare const PLAIN_SUPPORT_MESSAGE_RECEIVED_PAYLOAD_FIELDS: readonly ["event_id", "event_type", "occurred_at", "workspace_id", "webhook_target_id", "webhook_target_version", "thread_id", "thread_ref", "timeline_entry_id", "channel", "status", "priority", "tenant_external_id", "has_assignee", "assignee_kind", "label_external_ids", "support_fields", "thread_created_at", "thread_updated_at", "automation_mode", "customer_content_retained"];
5
+ export declare const PLAIN_SUPPORT_INBOUND_EVENT_TYPES: readonly ["thread.chat_received", "thread.email_received", "thread.slack_message_received", "thread.ms_teams_message_received", "thread.discord_message_received"];
6
+ export type PlainSupportInboundEventType = (typeof PLAIN_SUPPORT_INBOUND_EVENT_TYPES)[number];
7
+ export declare const PLAIN_SUPPORT_CHANNELS: readonly ["chat", "email", "slack", "ms_teams", "discord"];
8
+ export type PlainSupportChannel = (typeof PLAIN_SUPPORT_CHANNELS)[number];
9
+ /**
10
+ * Metadata-only contract persisted in OXYGEN's Workflow event/run ledger.
11
+ *
12
+ * Plain remains the canonical and only customer-content store. Never add message
13
+ * text, Thread title/preview/description, customer identity, email, or attachment
14
+ * contents to this payload. A human or a separately governed support Agent reads
15
+ * that content from the canonical Plain Thread when it actually needs it.
16
+ */
17
+ export type PlainSupportMessageReceivedPayload = {
18
+ event_id: string;
19
+ event_type: PlainSupportInboundEventType;
20
+ occurred_at: string;
21
+ workspace_id: string;
22
+ webhook_target_id: string;
23
+ webhook_target_version: typeof PLAIN_SUPPORT_WEBHOOK_VERSION;
24
+ thread_id: string;
25
+ thread_ref: string;
26
+ timeline_entry_id: string;
27
+ channel: PlainSupportChannel;
28
+ status: "TODO" | "SNOOZED" | "DONE";
29
+ priority: number;
30
+ tenant_external_id: string | null;
31
+ has_assignee: boolean;
32
+ assignee_kind: "user" | "machine_user" | "system" | null;
33
+ label_external_ids: string[];
34
+ support_fields: Record<string, string>;
35
+ thread_created_at: string;
36
+ thread_updated_at: string;
37
+ automation_mode: "observe";
38
+ customer_content_retained: false;
39
+ };
40
+ export declare function isPlainSupportInboundEventType(value: string): value is PlainSupportInboundEventType;
@@ -0,0 +1,43 @@
1
+ export const PLAIN_SUPPORT_WORKFLOW_EVENT_SOURCE = "plain_support";
2
+ export const PLAIN_SUPPORT_MESSAGE_RECEIVED_EVENT = "message_received";
3
+ export const PLAIN_SUPPORT_WEBHOOK_VERSION = "2026-02-11";
4
+ export const PLAIN_SUPPORT_MESSAGE_RECEIVED_PAYLOAD_FIELDS = [
5
+ "event_id",
6
+ "event_type",
7
+ "occurred_at",
8
+ "workspace_id",
9
+ "webhook_target_id",
10
+ "webhook_target_version",
11
+ "thread_id",
12
+ "thread_ref",
13
+ "timeline_entry_id",
14
+ "channel",
15
+ "status",
16
+ "priority",
17
+ "tenant_external_id",
18
+ "has_assignee",
19
+ "assignee_kind",
20
+ "label_external_ids",
21
+ "support_fields",
22
+ "thread_created_at",
23
+ "thread_updated_at",
24
+ "automation_mode",
25
+ "customer_content_retained",
26
+ ];
27
+ export const PLAIN_SUPPORT_INBOUND_EVENT_TYPES = [
28
+ "thread.chat_received",
29
+ "thread.email_received",
30
+ "thread.slack_message_received",
31
+ "thread.ms_teams_message_received",
32
+ "thread.discord_message_received",
33
+ ];
34
+ export const PLAIN_SUPPORT_CHANNELS = [
35
+ "chat",
36
+ "email",
37
+ "slack",
38
+ "ms_teams",
39
+ "discord",
40
+ ];
41
+ export function isPlainSupportInboundEventType(value) {
42
+ return PLAIN_SUPPORT_INBOUND_EVENT_TYPES.includes(value);
43
+ }
@@ -36,6 +36,10 @@ const BEARER_TOKEN_PATTERN = /\bBearer\s+(?!tokens?(?![\w.~+/=-]))[\w.~+/=-]+/gi
36
36
  // missing" diagnostics (its actual value is an `sk-…` string, already covered).
37
37
  const DB_URL_PATTERN = /(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^\s"'<>()]+/gi;
38
38
  const SK_TOKEN_PATTERN = /sk-[A-Za-z0-9_-]{20,}/g;
39
+ // PostHog personal (`phx_`) and project (`phc_`) keys can be echoed inside a
40
+ // provider hostname-validation error. They are secrets regardless of which
41
+ // ordinary string field carried them, so scrub both credential families.
42
+ const POSTHOG_TOKEN_PATTERN = /\bph[xc]_[A-Za-z0-9_-]{6,}/g;
39
43
  const MAX_LOG_STRING_LENGTH = 8_000;
40
44
  const MAX_TELEMETRY_STRING_LENGTH = 500;
41
45
  const MAX_ARRAY_LENGTH = 20;
@@ -151,7 +155,8 @@ export function redactSecretsInString(value) {
151
155
  return value
152
156
  .replace(BEARER_TOKEN_PATTERN, "Bearer[REDACTED]")
153
157
  .replace(DB_URL_PATTERN, "[REDACTED_DB_URL]")
154
- .replace(SK_TOKEN_PATTERN, "[REDACTED_SK]");
158
+ .replace(SK_TOKEN_PATTERN, "[REDACTED_SK]")
159
+ .replace(POSTHOG_TOKEN_PATTERN, "[REDACTED_POSTHOG_KEY]");
155
160
  }
156
161
  function truncateString(value, maxLength) {
157
162
  return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
@@ -0,0 +1,2 @@
1
+ import { type CapabilityRouteMatch } from "./capability-discovery.js";
2
+ export declare function inferUserCapabilityRoute(query: string): CapabilityRouteMatch | null;
@@ -0,0 +1,23 @@
1
+ import { getCapabilityRouteMatch, inferCapabilityRoute, } from "./capability-discovery.js";
2
+ // Provider brands are intentionally not duplicated into the static capability
3
+ // catalog. A short "connect <provider>" query would otherwise match Tables via
4
+ // its relationship-oriented "connect" term. Correct that narrow ambiguity at
5
+ // the user-facing boundary while preserving table joins and channel-specific
6
+ // connection routes.
7
+ export function inferUserCapabilityRoute(query) {
8
+ const route = inferCapabilityRoute(query);
9
+ if (route?.card.id !== "tables" || !isSimpleProviderConnectionIntent(query)) {
10
+ return route;
11
+ }
12
+ return getCapabilityRouteMatch("connected-integrations") ?? route;
13
+ }
14
+ function isSimpleProviderConnectionIntent(query) {
15
+ const normalized = query.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
16
+ if (/\b(table|tables|row|rows|column|columns|dataset|join|relationship|relate)\b/.test(normalized)) {
17
+ return false;
18
+ }
19
+ if (/\b(integration|provider|oauth|byok|api key|connected account)\b/.test(normalized)) {
20
+ return /\b(connect|reconnect|disconnect)\b/.test(normalized);
21
+ }
22
+ return /\b(?:connect|reconnect|disconnect)\s+(?:(?:my|our|the)\s+)?(?!(?:this|that|them|us|me)\b)[a-z0-9][a-z0-9.]*\s*$/.test(normalized);
23
+ }
@@ -1,3 +1,3 @@
1
- export declare const OXYGEN_VERSION = "1.720.7";
1
+ export declare const OXYGEN_VERSION = "1.739.0";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
3
  export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.720.7";
1
+ export const OXYGEN_VERSION = "1.739.0";
2
2
  // The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
3
3
  // operational route. Raising it hard-rejects every older CLI from the entire
4
4
  // product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
@@ -0,0 +1,10 @@
1
+ /** Prevent one attacker-controlled audit value from bloating every delivery. */
2
+ export declare const WEBHOOK_AUDIT_HEADER_VALUE_MAX_CHARS = 4096;
3
+ /**
4
+ * Return only non-secret webhook audit headers, normalized to lower-case keys.
5
+ *
6
+ * `blockedHeaderNames` remains useful to callers documenting a provider's
7
+ * authentication headers, even though an auth header is omitted by default
8
+ * unless it is explicitly part of the allowlist above.
9
+ */
10
+ export declare function sanitizeWebhookHeaderRecord(headers: Record<string, unknown> | null | undefined, blockedHeaderNames?: readonly string[]): Record<string, string>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Webhook headers that are useful for delivery correlation and payload parsing.
3
+ *
4
+ * Inbound requests also carry platform and authentication material (for example
5
+ * Vercel OIDC assertions, proxy signatures, cookies, and provider signatures).
6
+ * A denylist will always lag those providers, so persisted webhook audit data is
7
+ * intentionally an allowlist. Signature verification still receives the raw
8
+ * Headers object before this sanitizer runs.
9
+ */
10
+ const WEBHOOK_AUDIT_HEADER_NAMES = new Set([
11
+ "accept",
12
+ "content-length",
13
+ "content-type",
14
+ "idempotency-key",
15
+ "user-agent",
16
+ "webhook-id",
17
+ "webhook-timestamp",
18
+ "x-correlation-id",
19
+ "x-event-id",
20
+ "x-event-type",
21
+ "x-idempotency-key",
22
+ "x-oxygen-delivery-id",
23
+ "x-request-id",
24
+ "x-webhook-event",
25
+ "x-webhook-id",
26
+ ]);
27
+ /** Prevent one attacker-controlled audit value from bloating every delivery. */
28
+ export const WEBHOOK_AUDIT_HEADER_VALUE_MAX_CHARS = 4_096;
29
+ /**
30
+ * Return only non-secret webhook audit headers, normalized to lower-case keys.
31
+ *
32
+ * `blockedHeaderNames` remains useful to callers documenting a provider's
33
+ * authentication headers, even though an auth header is omitted by default
34
+ * unless it is explicitly part of the allowlist above.
35
+ */
36
+ export function sanitizeWebhookHeaderRecord(headers, blockedHeaderNames = []) {
37
+ const blocked = new Set(blockedHeaderNames.map((name) => name.trim().toLowerCase()));
38
+ const sanitized = {};
39
+ for (const [rawName, rawValue] of Object.entries(headers ?? {})) {
40
+ const name = rawName.trim().toLowerCase();
41
+ if (!WEBHOOK_AUDIT_HEADER_NAMES.has(name) || blocked.has(name))
42
+ continue;
43
+ if (typeof rawValue !== "string")
44
+ continue;
45
+ sanitized[name] = rawValue.slice(0, WEBHOOK_AUDIT_HEADER_VALUE_MAX_CHARS);
46
+ }
47
+ return sanitized;
48
+ }
@@ -1,3 +1,20 @@
1
+ import { CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS } from "./crm-reply-events.js";
2
+ export declare const WORKSPACE_EVENT_PRODUCER_READINESS_REQUIREMENTS: readonly ["always", "notetaker_enabled"];
3
+ export type WorkspaceEventProducerReadinessRequirement = (typeof WORKSPACE_EVENT_PRODUCER_READINESS_REQUIREMENTS)[number];
4
+ export type CrmSignalWorkflowEvent = (typeof CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS)[number]["event"];
5
+ export type WorkspaceEventProducerId = "sequencer.contact_activity" | "sequencer.enrollment_terminal" | "crm.activity_created" | `crm.${CrmSignalWorkflowEvent}` | "notetaker.meeting_completed" | "plain_support.message_received";
6
+ export type WorkspaceEventProducerDefinition = {
7
+ /** Stable producer identity used by typed internal dispatchers. */
8
+ id: WorkspaceEventProducerId;
9
+ source: string;
10
+ event: string;
11
+ /** Where the durable payload is recovered or deduplicated. */
12
+ delivery: "durable_ledger" | "transactional_outbox" | "direct_durable_write";
13
+ /** Human-readable idempotency contract surfaced to authors and operators. */
14
+ idempotency: string;
15
+ readiness: WorkspaceEventProducerReadinessRequirement;
16
+ payloadFields: readonly string[];
17
+ };
1
18
  export type WorkspaceEventCatalogFilter = {
2
19
  path: string;
3
20
  op: "eq";
@@ -6,6 +23,7 @@ export type WorkspaceEventCatalogFilter = {
6
23
  export type WorkspaceEventCatalogEntry = {
7
24
  /** Stable catalog identity; independent of the underlying source/event pair. */
8
25
  id: string;
26
+ producerId: string;
9
27
  source: string;
10
28
  event: string;
11
29
  group: string;
@@ -14,7 +32,19 @@ export type WorkspaceEventCatalogEntry = {
14
32
  filters?: readonly WorkspaceEventCatalogFilter[];
15
33
  payloadFields: readonly string[];
16
34
  };
17
- export declare const WORKSPACE_EVENT_CATALOG_VERSION = 1;
35
+ export declare const WORKSPACE_EVENT_CATALOG_VERSION = 2;
36
+ /**
37
+ * Every app-native event producer that is safe to bind to a durable Workflow.
38
+ * Catalog presets below may fan one producer into many filtered choices, but a
39
+ * producer exists exactly once and is the authority for source/event identity.
40
+ */
41
+ export declare const WORKSPACE_EVENT_PRODUCERS: readonly WorkspaceEventProducerDefinition[];
42
+ export declare function getWorkspaceEventProducer(producerId: string): WorkspaceEventProducerDefinition | undefined;
43
+ export declare function crmSignalWorkflowEventProducerId(event: CrmSignalWorkflowEvent): `crm.${CrmSignalWorkflowEvent}`;
44
+ export declare function findWorkspaceEventProducer(input: {
45
+ source: string;
46
+ event: string;
47
+ }): WorkspaceEventProducerDefinition | undefined;
18
48
  /**
19
49
  * Every app-native workspace event that has a durable or explicitly callable
20
50
  * producer. Integration-specific subscriptions are merged in at request time.
@@ -2,7 +2,13 @@ import { CRM_ACTIVITY_CREATED_EVENT, CRM_ACTIVITY_EVENT_DEFINITIONS, } from "./c
2
2
  import { CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS, CRM_WORKFLOW_EVENT_SOURCE, } from "./crm-reply-events.js";
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
- export const WORKSPACE_EVENT_CATALOG_VERSION = 1;
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
+ export const WORKSPACE_EVENT_PRODUCER_READINESS_REQUIREMENTS = [
8
+ "always",
9
+ "notetaker_enabled",
10
+ ];
11
+ export const WORKSPACE_EVENT_CATALOG_VERSION = 2;
6
12
  const sequenceActivityFields = [
7
13
  "event",
8
14
  "occurred_at",
@@ -49,6 +55,78 @@ const crmActivityFields = [
49
55
  "metadata",
50
56
  "links",
51
57
  ];
58
+ const crmSignalProducerDefinitions = CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS.map((definition) => ({
59
+ id: `crm.${definition.event}`,
60
+ source: CRM_WORKFLOW_EVENT_SOURCE,
61
+ event: definition.event,
62
+ delivery: "direct_durable_write",
63
+ idempotency: "Provider or conversation event identity is persisted before workflow fan-out.",
64
+ readiness: "always",
65
+ payloadFields: definition.payloadFields,
66
+ }));
67
+ /**
68
+ * Every app-native event producer that is safe to bind to a durable Workflow.
69
+ * Catalog presets below may fan one producer into many filtered choices, but a
70
+ * producer exists exactly once and is the authority for source/event identity.
71
+ */
72
+ export const WORKSPACE_EVENT_PRODUCERS = [
73
+ {
74
+ id: "sequencer.contact_activity",
75
+ source: SEQUENCE_WORKFLOW_EVENT_SOURCE,
76
+ event: SEQUENCE_CONTACT_ACTIVITY_EVENT,
77
+ delivery: "durable_ledger",
78
+ idempotency: "The Sequencer activity ledger identity deduplicates repeated bridge scans.",
79
+ readiness: "always",
80
+ payloadFields: sequenceActivityFields,
81
+ },
82
+ {
83
+ id: "sequencer.enrollment_terminal",
84
+ source: SEQUENCE_TERMINAL_EVENT_SOURCE,
85
+ event: SEQUENCE_ENROLLMENT_TERMINAL_EVENT,
86
+ delivery: "transactional_outbox",
87
+ idempotency: "The append-only terminal outbox and enrollment outcome form one stable event identity.",
88
+ readiness: "always",
89
+ payloadFields: sequenceTerminalFields,
90
+ },
91
+ {
92
+ id: "crm.activity_created",
93
+ source: CRM_WORKFLOW_EVENT_SOURCE,
94
+ event: CRM_ACTIVITY_CREATED_EVENT,
95
+ delivery: "durable_ledger",
96
+ idempotency: "The CRM activity id is the durable workflow-event identity.",
97
+ readiness: "always",
98
+ payloadFields: crmActivityFields,
99
+ },
100
+ ...crmSignalProducerDefinitions,
101
+ {
102
+ id: "notetaker.meeting_completed",
103
+ source: NOTETAKER_WORKFLOW_EVENT_SOURCE,
104
+ event: NOTETAKER_MEETING_COMPLETED_EVENT,
105
+ delivery: "direct_durable_write",
106
+ idempotency: "The Notetaker session and provider completion identities deduplicate completion delivery.",
107
+ readiness: "notetaker_enabled",
108
+ payloadFields: NOTETAKER_MEETING_COMPLETED_PAYLOAD_FIELDS,
109
+ },
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
+ ];
120
+ const workspaceEventProducersById = new Map(WORKSPACE_EVENT_PRODUCERS.map((producer) => [producer.id, producer]));
121
+ export function getWorkspaceEventProducer(producerId) {
122
+ return workspaceEventProducersById.get(producerId);
123
+ }
124
+ export function crmSignalWorkflowEventProducerId(event) {
125
+ return `crm.${event}`;
126
+ }
127
+ export function findWorkspaceEventProducer(input) {
128
+ return WORKSPACE_EVENT_PRODUCERS.find((producer) => producer.source === input.source && producer.event === input.event);
129
+ }
52
130
  function sequenceActivityDescription(key, label) {
53
131
  if (key === "email_bounced") {
54
132
  return "An outreach sequence enrollment reported an email bounce. Use the CRM timeline preset instead for any bounce activity logged on a CRM record.";
@@ -70,6 +148,7 @@ function crmActivityDescription(key, label) {
70
148
  const sequenceActivityEntries = [
71
149
  {
72
150
  id: "sequencer.contact_activity.any",
151
+ producerId: "sequencer.contact_activity",
73
152
  source: SEQUENCE_WORKFLOW_EVENT_SOURCE,
74
153
  event: SEQUENCE_CONTACT_ACTIVITY_EVENT,
75
154
  group: "Sequencer · All activity",
@@ -79,6 +158,7 @@ const sequenceActivityEntries = [
79
158
  },
80
159
  ...SEQUENCE_CRM_EVENT_DEFINITIONS.map((definition) => ({
81
160
  id: `sequencer.contact_activity.${definition.key}`,
161
+ producerId: "sequencer.contact_activity",
82
162
  source: SEQUENCE_WORKFLOW_EVENT_SOURCE,
83
163
  event: SEQUENCE_CONTACT_ACTIVITY_EVENT,
84
164
  group: `Sequencer · ${definition.group}`,
@@ -91,6 +171,7 @@ const sequenceActivityEntries = [
91
171
  const sequenceTerminalEntries = [
92
172
  {
93
173
  id: "sequencer.enrollment_terminal.any",
174
+ producerId: "sequencer.enrollment_terminal",
94
175
  source: SEQUENCE_TERMINAL_EVENT_SOURCE,
95
176
  event: SEQUENCE_ENROLLMENT_TERMINAL_EVENT,
96
177
  group: "Sequencer · Journey status",
@@ -100,6 +181,7 @@ const sequenceTerminalEntries = [
100
181
  },
101
182
  ...SEQUENCE_TERMINAL_STATUSES.map((status) => ({
102
183
  id: `sequencer.enrollment_terminal.${status}`,
184
+ producerId: "sequencer.enrollment_terminal",
103
185
  source: SEQUENCE_TERMINAL_EVENT_SOURCE,
104
186
  event: SEQUENCE_ENROLLMENT_TERMINAL_EVENT,
105
187
  group: "Sequencer · Journey status",
@@ -112,6 +194,7 @@ const sequenceTerminalEntries = [
112
194
  const crmActivityEntries = [
113
195
  {
114
196
  id: "crm.activity_created.any",
197
+ producerId: "crm.activity_created",
115
198
  source: CRM_WORKFLOW_EVENT_SOURCE,
116
199
  event: CRM_ACTIVITY_CREATED_EVENT,
117
200
  group: "CRM · All timeline activity",
@@ -121,6 +204,7 @@ const crmActivityEntries = [
121
204
  },
122
205
  ...CRM_ACTIVITY_EVENT_DEFINITIONS.map((definition) => ({
123
206
  id: `crm.activity_created.${definition.key}`,
207
+ producerId: "crm.activity_created",
124
208
  source: CRM_WORKFLOW_EVENT_SOURCE,
125
209
  event: CRM_ACTIVITY_CREATED_EVENT,
126
210
  group: `CRM · ${definition.group}`,
@@ -132,6 +216,7 @@ const crmActivityEntries = [
132
216
  ];
133
217
  const crmSignalEntries = CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS.map((definition) => ({
134
218
  id: `crm.${definition.event}`,
219
+ producerId: `crm.${definition.event}`,
135
220
  source: CRM_WORKFLOW_EVENT_SOURCE,
136
221
  event: definition.event,
137
222
  group: `CRM · ${definition.group}`,
@@ -139,6 +224,30 @@ const crmSignalEntries = CRM_SIGNAL_WORKFLOW_EVENT_DEFINITIONS.map((definition)
139
224
  description: definition.description,
140
225
  payloadFields: definition.payloadFields,
141
226
  }));
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
+ const notetakerEntries = [
240
+ {
241
+ id: "notetaker.meeting_completed",
242
+ producerId: "notetaker.meeting_completed",
243
+ source: NOTETAKER_WORKFLOW_EVENT_SOURCE,
244
+ event: NOTETAKER_MEETING_COMPLETED_EVENT,
245
+ group: "Meeting Notetaker",
246
+ label: "Meeting completed",
247
+ description: "A Meeting Notetaker session finished processing and its transcript and meeting note are ready.",
248
+ payloadFields: NOTETAKER_MEETING_COMPLETED_PAYLOAD_FIELDS,
249
+ },
250
+ ];
142
251
  /**
143
252
  * Every app-native workspace event that has a durable or explicitly callable
144
253
  * producer. Integration-specific subscriptions are merged in at request time.
@@ -148,4 +257,6 @@ export const WORKSPACE_EVENT_CATALOG = [
148
257
  ...sequenceTerminalEntries,
149
258
  ...crmActivityEntries,
150
259
  ...crmSignalEntries,
260
+ ...plainSupportEntries,
261
+ ...notetakerEntries,
151
262
  ];
@@ -279,11 +279,13 @@ function resolveTokensDeep(value, scope, depth = 0) {
279
279
  return value.map((entry) => resolveTokensDeep(entry, scope, depth + 1));
280
280
  }
281
281
  if (isStructured(value)) {
282
- const out = {};
283
- for (const [key, entry] of Object.entries(value)) {
284
- out[key] = resolveTokensDeep(entry, scope, depth + 1);
285
- }
286
- return out;
282
+ // Object.fromEntries defines own data properties even for `__proto__`.
283
+ // Assigning onto `{}` would mutate the clone's prototype and silently lose
284
+ // a valid JSON key before strict Code-input serialization can inspect it.
285
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
286
+ key,
287
+ resolveTokensDeep(entry, scope, depth + 1),
288
+ ]));
287
289
  }
288
290
  return value;
289
291
  }
@@ -1,4 +1,13 @@
1
1
  import { type WorkflowLintResult, type WorkflowManifestValidationOptions } from "../index.js";
2
+ import type { WorkflowCodeV1 } from "./types.js";
2
3
  import { type WorkflowGraphManifest } from "./types.js";
3
4
  export declare function lintWorkflowGraphManifest(value: unknown, options?: WorkflowManifestValidationOptions): WorkflowLintResult;
4
5
  export declare function assertWorkflowGraphManifest(value: unknown, options?: WorkflowManifestValidationOptions): asserts value is WorkflowGraphManifest;
6
+ /**
7
+ * Validate one nested oxygen-js-v1 envelope without requiring its surrounding
8
+ * graph. Runtime entry points call this again immediately before sandbox
9
+ * creation; ref syntax is checked here, while graph node reachability remains a
10
+ * manifest-lint concern.
11
+ */
12
+ export declare function lintWorkflowCodeV1Contract(value: unknown): WorkflowLintResult;
13
+ export declare function assertWorkflowCodeV1Contract(value: unknown): asserts value is WorkflowCodeV1;