@oxygen-agent/cli 1.1014.1465 → 1.1014.1597

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 (85) hide show
  1. package/README.md +1 -1
  2. package/dist/command-families/program.js +2 -0
  3. package/dist/command-manifest.js +12 -2
  4. package/dist/commands/budget.js +2 -2
  5. package/dist/commands/capabilities.js +3 -2
  6. package/dist/commands/columns.js +1 -1
  7. package/dist/commands/companies.js +3 -0
  8. package/dist/commands/domains.js +122 -5
  9. package/dist/commands/feedback.js +1 -1
  10. package/dist/commands/feeds.js +34 -17
  11. package/dist/commands/mailboxes.js +19 -9
  12. package/dist/commands/publishing.js +1 -1
  13. package/dist/commands/senders.js +2 -2
  14. package/dist/commands/sequences.js +75 -12
  15. package/dist/commands/subscriptions.d.ts +10 -0
  16. package/dist/commands/subscriptions.js +203 -0
  17. package/dist/commands/tags.js +4 -4
  18. package/dist/customer-operation-commands.js +17 -0
  19. package/dist/mailbox-fleet-output.d.ts +3 -0
  20. package/dist/mailbox-fleet-output.js +80 -0
  21. package/dist/native-chat-host.d.ts +2 -0
  22. package/dist/native-chat-host.js +6 -0
  23. package/dist/workspace-placement-commands.js +67 -2
  24. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +9 -0
  25. package/node_modules/@oxygen/shared/dist/capability-discovery.js +59 -11
  26. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +2 -2
  27. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +18 -0
  28. package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +4 -4
  29. package/node_modules/@oxygen/shared/dist/contracts/native-chat.d.ts +120 -0
  30. package/node_modules/@oxygen/shared/dist/contracts/native-chat.js +35 -0
  31. package/node_modules/@oxygen/shared/dist/contracts/sender-limits.d.ts +9 -0
  32. package/node_modules/@oxygen/shared/dist/contracts/sender-limits.js +6 -1
  33. package/node_modules/@oxygen/shared/dist/contracts/sequence-list.d.ts +5 -0
  34. package/node_modules/@oxygen/shared/dist/contracts/sequence-list.js +2 -1
  35. package/node_modules/@oxygen/shared/dist/contracts/signal-search-compatibility.d.ts +78 -0
  36. package/node_modules/@oxygen/shared/dist/contracts/signal-search-compatibility.js +32 -0
  37. package/node_modules/@oxygen/shared/dist/dnc-rules.js +14 -0
  38. package/node_modules/@oxygen/shared/dist/email-delivery.d.ts +150 -0
  39. package/node_modules/@oxygen/shared/dist/email-delivery.js +246 -0
  40. package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +1 -0
  41. package/node_modules/@oxygen/shared/dist/email-dsn.js +9 -6
  42. package/node_modules/@oxygen/shared/dist/email-health-summary.d.ts +29 -0
  43. package/node_modules/@oxygen/shared/dist/email-health-summary.js +28 -0
  44. package/node_modules/@oxygen/shared/dist/email-subscriptions.d.ts +50 -0
  45. package/node_modules/@oxygen/shared/dist/email-subscriptions.js +80 -0
  46. package/node_modules/@oxygen/shared/dist/email-unsubscribe-token.d.ts +28 -0
  47. package/node_modules/@oxygen/shared/dist/email-unsubscribe-token.js +47 -0
  48. package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +4 -16
  49. package/node_modules/@oxygen/shared/dist/error-redaction.js +8 -17
  50. package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +15 -1
  51. package/node_modules/@oxygen/shared/dist/guided-activation.js +3 -1
  52. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +2 -0
  53. package/node_modules/@oxygen/shared/dist/mailbox-standby-rollout.d.ts +5 -0
  54. package/node_modules/@oxygen/shared/dist/mailbox-standby-rollout.js +9 -0
  55. package/node_modules/@oxygen/shared/dist/mailbox-standby.d.ts +43 -0
  56. package/node_modules/@oxygen/shared/dist/mailbox-standby.js +52 -0
  57. package/node_modules/@oxygen/shared/dist/native-chat-discovery.d.ts +5 -0
  58. package/node_modules/@oxygen/shared/dist/native-chat-discovery.js +27 -0
  59. package/node_modules/@oxygen/shared/dist/native-chat.d.ts +124 -0
  60. package/node_modules/@oxygen/shared/dist/native-chat.js +1 -0
  61. package/node_modules/@oxygen/shared/dist/onboarding-plays.d.ts +14 -3
  62. package/node_modules/@oxygen/shared/dist/onboarding-plays.js +26 -13
  63. package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +11 -0
  64. package/node_modules/@oxygen/shared/dist/onboarding-research.js +8 -0
  65. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +6 -0
  66. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +6 -0
  67. package/node_modules/@oxygen/shared/dist/select-options.js +1 -0
  68. package/node_modules/@oxygen/shared/dist/sequence-failures.js +6 -5
  69. package/node_modules/@oxygen/shared/dist/sequences.d.ts +8 -0
  70. package/node_modules/@oxygen/shared/dist/sequences.js +44 -2
  71. package/node_modules/@oxygen/shared/dist/tags.d.ts +7 -3
  72. package/node_modules/@oxygen/shared/dist/tags.js +31 -6
  73. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  74. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  75. package/node_modules/@oxygen/shared/dist/workspace-placement-bulk.d.ts +80 -0
  76. package/node_modules/@oxygen/shared/dist/workspace-placement-bulk.js +68 -0
  77. package/node_modules/@oxygen/shared/dist/workspace-placement-view.d.ts +2 -1
  78. package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +11 -1
  79. package/node_modules/@oxygen/shared/dist/workspace-placement.js +15 -4
  80. package/node_modules/@oxygen/shared/dist/write-rollout.d.ts +11 -0
  81. package/node_modules/@oxygen/shared/dist/write-rollout.js +59 -0
  82. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +176 -2
  83. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +50 -1
  84. package/node_modules/@oxygen/workflows/dist/index.js +108 -3
  85. package/package.json +1 -1
@@ -0,0 +1,68 @@
1
+ import { createHash } from "node:crypto";
2
+ import { CliHttpError } from "./cli-http-error.js";
3
+ import { readWorkspacePlacementVariants, workspacePlacementId } from "./workspace-placement.js";
4
+ export const MAX_BULK_PLACEMENT_MESSAGES = 20_000;
5
+ export function bulkPlacementId(value) {
6
+ const h = createHash("sha256").update(value).digest("hex");
7
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-a${h.slice(17, 20)}-${h.slice(20, 32)}`;
8
+ }
9
+ export function readBulkPlacementProviders(value) {
10
+ if (value === undefined)
11
+ return ["google", "microsoft"];
12
+ if (!Array.isArray(value) || !value.length || value.length > 2 || new Set(value).size !== value.length || value.some(p => p !== "google" && p !== "microsoft"))
13
+ throw new CliHttpError(400, "invalid_request", "receiver_providers must select Google, Microsoft, or both.");
14
+ return [...value].sort();
15
+ }
16
+ /** Own-account rotation, never a sender × receiver Cartesian product. */
17
+ export function buildWorkspacePlacementBulkPlan(input) {
18
+ if (!workspacePlacementId(input.requestId) || !workspacePlacementId(input.organizationId))
19
+ throw new CliHttpError(400, "workspace_test_invalid", "The deliverability test request is invalid.");
20
+ const requestId = input.requestId.toLowerCase(), organizationId = input.organizationId.toLowerCase();
21
+ const variants = readWorkspacePlacementVariants(input.variants), providers = readBulkPlacementProviders(input.receiverProviders);
22
+ const senders = [...input.senders].sort((a, b) => a.id.localeCompare(b.id));
23
+ if (!senders.length)
24
+ throw new CliHttpError(409, "workspace_test_no_eligible_senders", "No eligible sending mailboxes are available. Connect mailboxes with send permission or explicitly include paused mailboxes.", { details: { skipped_senders: input.skippedSenders?.slice(0, 20) ?? [], skipped_sender_count: input.skippedSenders?.length ?? 0 } });
25
+ const count = senders.length * variants.length * providers.length;
26
+ if (count > MAX_BULK_PLACEMENT_MESSAGES)
27
+ throw new CliHttpError(400, "workspace_test_bulk_limit", "This batch exceeds 20,000 messages. Choose fewer copy variants or receiving providers.");
28
+ const coverage = { google: { receivers: 0, planned: 0 }, microsoft: { receivers: 0, planned: 0 } };
29
+ const pools = new Map(providers.map(provider => {
30
+ const rows = [...input.receivers].filter(m => m.provider === provider).sort((a, b) => a.id.localeCompare(b.id));
31
+ coverage[provider].receivers = rows.length;
32
+ return [provider, rows];
33
+ }));
34
+ // Check coverage before rendering signatures/tracking. One sole own receiver
35
+ // cannot cover its own sender, even when that provider has a nonempty pool.
36
+ const missing = senders.flatMap(sender => providers.filter(p => !pools.get(p).some(r => r.id !== sender.id && r.address.toLowerCase() !== sender.address.toLowerCase())).map(provider => ({ sender: sender.address, provider })));
37
+ if (missing.length)
38
+ throw new CliHttpError(409, "workspace_test_receiver_coverage_missing", "Connected receiving mailboxes cannot cover every sender and requested provider without self-sends. Connect another receiving mailbox or change receiver_providers.", { details: { missing_coverage: missing.slice(0, 20), missing_coverage_count: missing.length, coverage } });
39
+ const probes = [];
40
+ let renderedBytes = 0;
41
+ for (const [senderIndex, sender] of senders.entries())
42
+ for (const [variantIndex, original] of variants.entries())
43
+ for (const provider of providers) {
44
+ const pool = pools.get(provider).filter(r => r.id !== sender.id && r.address.toLowerCase() !== sender.address.toLowerCase());
45
+ const receiver = pool[(senderIndex * variants.length + variantIndex) % pool.length];
46
+ const id = bulkPlacementId(`${organizationId}:${requestId}:${sender.id}:${receiver.id}:${original.key}`);
47
+ const variant = input.renderVariant ? input.renderVariant(original, sender, id) : original;
48
+ renderedBytes += Buffer.byteLength(JSON.stringify(variant));
49
+ if (renderedBytes > 32 * 1024 * 1024)
50
+ throw new CliHttpError(400, "workspace_test_bulk_copy_limit", "Rendered copy exceeds the batch review limit. Shorten the copy or choose fewer variants.");
51
+ probes.push({ id, sender, receiver, variant });
52
+ coverage[provider].planned++;
53
+ }
54
+ const children = [];
55
+ for (let offset = 0; offset < probes.length; offset += 50) {
56
+ const child = { request_id: bulkPlacementId(`${organizationId}:${requestId}:child:${offset / 50}`), organization_id: organizationId, provider: "internal", billing_mode: "internal", credits: 0, allow_paused_probe: input.allowPausedProbe, receiver_scope: "workspace", rendered_at: input.renderedAt, rendering: input.rendering, probes: probes.slice(offset, offset + 50), excluded_self_sends: 0, message_count: Math.min(50, probes.length - offset) };
57
+ children.push({ ...child, plan_hash: createHash("sha256").update(JSON.stringify(child)).digest("hex") });
58
+ }
59
+ const plan = { request_id: requestId, organization_id: organizationId, rendered_at: input.renderedAt, message_count: count, child_count: children.length, sender_count: senders.length, receiver_providers: providers, skipped_senders: [...(input.skippedSenders ?? [])].sort((a, b) => a.id.localeCompare(b.id)), coverage, children };
60
+ return { ...plan, plan_hash: createHash("sha256").update(JSON.stringify(plan)).digest("hex") };
61
+ }
62
+ export function bulkPlacementSkippedPage(plan, cursor) {
63
+ if (cursor !== undefined && (typeof cursor !== "string" || !plan.skipped_senders.some(s => s.id === cursor)))
64
+ throw new CliHttpError(400, "workspace_test_page_invalid", "Use the skipped-sender cursor from this deliverability test.");
65
+ const start = cursor === undefined ? 0 : plan.skipped_senders.findIndex(s => s.id === cursor) + 1;
66
+ const skipped = plan.skipped_senders.slice(start, start + 20), more = start + skipped.length < plan.skipped_senders.length;
67
+ return { skipped_senders: skipped, skipped_sender_count: plan.skipped_senders.length, skipped_has_more: more, next_skipped_cursor: more ? skipped.at(-1).id : null };
68
+ }
@@ -123,7 +123,8 @@ export declare function workspacePlacementPreviewPage(plan: WorkspacePlacementPl
123
123
  billing_mode: "internal";
124
124
  credits: 0;
125
125
  allow_paused_probe: boolean;
126
- receiver_scope?: "workspace" | "network";
126
+ receiver_scope?: "workspace" | "network" | "staff";
127
+ staff?: import("./workspace-placement.js").StaffPlacementGrant;
127
128
  rendered_at?: string;
128
129
  rendering?: {
129
130
  tracking_opens: boolean;
@@ -13,6 +13,13 @@ export type WorkspacePlacementMailbox = {
13
13
  signature_html: string | null;
14
14
  organization_id?: string;
15
15
  network_id?: string;
16
+ environment?: "dev" | "prod";
17
+ };
18
+ export type StaffPlacementGrant = {
19
+ actor_id: string;
20
+ source_environment: "dev" | "prod";
21
+ receiver_environment: "dev" | "prod";
22
+ receiver_workspace_id: string;
16
23
  };
17
24
  export type WorkspacePlacementProbe = {
18
25
  id: string;
@@ -27,7 +34,8 @@ export type WorkspacePlacementPlan = {
27
34
  billing_mode: "internal";
28
35
  credits: 0;
29
36
  allow_paused_probe: boolean;
30
- receiver_scope?: "workspace" | "network";
37
+ receiver_scope?: "workspace" | "network" | "staff";
38
+ staff?: StaffPlacementGrant;
31
39
  rendered_at?: string;
32
40
  rendering?: {
33
41
  tracking_opens: boolean;
@@ -42,6 +50,7 @@ export type WorkspacePlacementPlan = {
42
50
  export declare function workspacePlacementId(value: unknown): value is string;
43
51
  export declare function readWorkspacePlacementVariants(value: unknown): WorkspacePlacementVariant[];
44
52
  export declare function readWorkspacePlacementMailboxIds(value: unknown): string[];
53
+ export declare function workspacePlacementPlanHash(plan: Omit<WorkspacePlacementPlan, "plan_hash">): string;
45
54
  export declare function buildWorkspacePlacementPlan(input: {
46
55
  requestId: string;
47
56
  organizationId: string;
@@ -52,6 +61,7 @@ export declare function buildWorkspacePlacementPlan(input: {
52
61
  renderedAt?: string;
53
62
  rendering?: WorkspacePlacementPlan["rendering"];
54
63
  network?: boolean;
64
+ staff?: StaffPlacementGrant;
55
65
  renderVariant?: (variant: WorkspacePlacementVariant, sender: WorkspacePlacementMailbox, probeId: string) => WorkspacePlacementVariant;
56
66
  }): WorkspacePlacementPlan;
57
67
  export type WorkspacePlacementObservation = {
@@ -38,6 +38,16 @@ function probeId(value) {
38
38
  const h = createHash("sha256").update(value).digest("hex");
39
39
  return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-a${h.slice(17, 20)}-${h.slice(20, 32)}`;
40
40
  }
41
+ function canonicalPlacementValue(value) {
42
+ if (Array.isArray(value))
43
+ return value.map(canonicalPlacementValue);
44
+ if (value && typeof value === "object")
45
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonicalPlacementValue(item)]));
46
+ return value;
47
+ }
48
+ export function workspacePlacementPlanHash(plan) {
49
+ return createHash("sha256").update(JSON.stringify(plan.receiver_scope === "staff" ? canonicalPlacementValue(plan) : plan)).digest("hex");
50
+ }
41
51
  export function buildWorkspacePlacementPlan(input) {
42
52
  if (!workspacePlacementId(input.requestId) || !workspacePlacementId(input.organizationId))
43
53
  invalid("The workspace test request is invalid.");
@@ -46,7 +56,8 @@ export function buildWorkspacePlacementPlan(input) {
46
56
  const variants = readWorkspacePlacementVariants(input.variants);
47
57
  const senders = [...input.senders].sort((a, b) => a.id.localeCompare(b.id));
48
58
  const receivers = [...input.receivers].sort((a, b) => a.id.localeCompare(b.id));
49
- const self = (sender, receiver) => sender.id === receiver.id || sender.address.toLowerCase() === receiver.address.toLowerCase();
59
+ const identity = (box) => `${box.environment ?? input.staff?.source_environment ?? ""}:${box.organization_id ?? organizationId}:${box.id}`;
60
+ const self = (sender, receiver) => identity(sender) === identity(receiver) || sender.address.toLowerCase() === receiver.address.toLowerCase();
50
61
  let excluded = 0, pairs = 0;
51
62
  for (const sender of senders)
52
63
  for (const receiver of receivers) {
@@ -72,7 +83,7 @@ export function buildWorkspacePlacementPlan(input) {
72
83
  if (group[sequence % group.length].id !== receiver.id)
73
84
  continue;
74
85
  }
75
- const id = probeId(`${organizationId}:${requestId}:${sender.id}:${receiver.id}:${original.key}`);
86
+ const id = probeId(input.staff ? `${organizationId}:${requestId}:${identity(sender)}:${identity(receiver)}:${original.key}` : `${organizationId}:${requestId}:${sender.id}:${receiver.id}:${original.key}`);
76
87
  const variant = input.renderVariant ? input.renderVariant(original, sender, id) : original;
77
88
  probes.push({ id, sender, receiver, variant });
78
89
  }
@@ -81,10 +92,10 @@ export function buildWorkspacePlacementPlan(input) {
81
92
  if (!probes.length || probes.length > 50)
82
93
  invalid("A workspace test must send between one and 50 messages after excluding self-sends.");
83
94
  const plan = { request_id: requestId, organization_id: organizationId, provider: "internal",
84
- ...(input.network ? { receiver_scope: "network" } : {}), billing_mode: "internal", credits: 0, allow_paused_probe: input.allowPausedProbe,
95
+ ...(input.network ? { receiver_scope: "network" } : input.staff ? { receiver_scope: "staff", staff: input.staff } : {}), billing_mode: "internal", credits: 0, allow_paused_probe: input.allowPausedProbe,
85
96
  ...(input.renderedAt ? { rendered_at: input.renderedAt } : {}), ...(input.rendering ? { rendering: input.rendering } : {}),
86
97
  probes, excluded_self_sends: excluded, message_count: probes.length };
87
- return { ...plan, plan_hash: createHash("sha256").update(JSON.stringify(plan)).digest("hex") };
98
+ return { ...plan, plan_hash: workspacePlacementPlanHash(plan) };
88
99
  }
89
100
  export function googleWorkspacePlacement(labels, messageId, observedAt) {
90
101
  const categories = ["PERSONAL", "PROMOTIONS", "SOCIAL", "UPDATES", "FORUMS"].filter(k => labels.includes(`CATEGORY_${k}`));
@@ -0,0 +1,11 @@
1
+ /** Writes become compatible only after every consuming role has adopted the new source. */
2
+ export declare function casePreservingTagWritesReady(env?: NodeJS.ProcessEnv): boolean;
3
+ export declare function campaignMetadataWritesReady(env?: NodeJS.ProcessEnv): boolean;
4
+ export declare function assertCampaignMetadataWritesReady(field: string, env?: NodeJS.ProcessEnv): void;
5
+ export declare function mailboxStandbyWritesReady(env?: NodeJS.ProcessEnv): boolean;
6
+ export declare function assertMailboxStandbyWritesReady(): void;
7
+ export declare function assertMailboxStandbyMetadataWritesReady(metadata: Record<string, unknown> | null | undefined): void;
8
+ export declare function staffPlacementWritesReady(env?: NodeJS.ProcessEnv): boolean;
9
+ export declare function assertStaffPlacementWritesReady(env?: NodeJS.ProcessEnv): void;
10
+ export declare function assertPublishingApprovalPolicyWriteReady(policy: unknown): void;
11
+ export declare function assertSequenceMetadataWritesReady(definition: unknown): void;
@@ -0,0 +1,59 @@
1
+ import { OxygenError } from "./cli-result.js";
2
+ import { isProductionData } from "./deploy-env.js";
3
+ /** Writes become compatible only after every consuming role has adopted the new source. */
4
+ export function casePreservingTagWritesReady(env = process.env) {
5
+ return !isProductionData(env) || env.OXYGEN_CASE_PRESERVING_TAG_WRITES_READY === "1";
6
+ }
7
+ export function campaignMetadataWritesReady(env = process.env) {
8
+ return !isProductionData(env) || env.OXYGEN_CAMPAIGN_METADATA_WRITES_READY === "1";
9
+ }
10
+ export function assertCampaignMetadataWritesReady(field, env = process.env) {
11
+ if (campaignMetadataWritesReady(env))
12
+ return;
13
+ throw new OxygenError("feature_rollout_pending", "This setting is not available yet.", {
14
+ details: { field, next_step: "Try again after the workspace update is available." }, exitCode: 1,
15
+ });
16
+ }
17
+ export function mailboxStandbyWritesReady(env = process.env) {
18
+ return campaignMetadataWritesReady(env)
19
+ && (!isProductionData(env) || env.OXYGEN_MAILBOX_STANDBY_WRITES_READY === "1");
20
+ }
21
+ export function assertMailboxStandbyWritesReady() {
22
+ if (mailboxStandbyWritesReady())
23
+ return;
24
+ throw new OxygenError("feature_rollout_pending", "Mailbox standby is not available yet.", {
25
+ details: { field: "mailbox_standby", next_step: "Try again after the workspace update is available." }, exitCode: 1,
26
+ });
27
+ }
28
+ export function assertMailboxStandbyMetadataWritesReady(metadata) {
29
+ if (Object.hasOwn(metadata ?? {}, "outreach_role") || Object.hasOwn(metadata ?? {}, "standby_promotion")) {
30
+ assertMailboxStandbyWritesReady();
31
+ }
32
+ }
33
+ export function staffPlacementWritesReady(env = process.env) {
34
+ return !isProductionData(env) || env.OXYGEN_STAFF_PLACEMENT_WRITES_READY === "1";
35
+ }
36
+ export function assertStaffPlacementWritesReady(env = process.env) {
37
+ if (staffPlacementWritesReady(env))
38
+ return;
39
+ throw new OxygenError("feature_rollout_pending", "Staff placement tests are not available yet.", {
40
+ details: { field: "staff_placement", next_step: "Try again after the workspace update is available." }, exitCode: 1,
41
+ });
42
+ }
43
+ export function assertPublishingApprovalPolicyWriteReady(policy) {
44
+ if (policy && typeof policy === "object" && "mode" in policy && policy.mode === "owner") {
45
+ assertCampaignMetadataWritesReady("publishing_approval");
46
+ }
47
+ }
48
+ export function assertSequenceMetadataWritesReady(definition) {
49
+ if (!definition || typeof definition !== "object" || !("steps" in definition) || !Array.isArray(definition.steps))
50
+ return;
51
+ for (const step of definition.steps) {
52
+ if (!step || typeof step !== "object")
53
+ continue;
54
+ if (step.kind === "wait" && step.working_days === true)
55
+ assertCampaignMetadataWritesReady("working_days");
56
+ if (step.kind === "wait_for_connection" && step.withdraw_on_timeout === true)
57
+ assertCampaignMetadataWritesReady("withdraw_on_timeout");
58
+ }
59
+ }
@@ -150,7 +150,7 @@ export declare const workflowCodeV1Schema: {
150
150
  export declare const workflowGraphManifestSchema: {
151
151
  readonly $schema: "https://json-schema.org/draft/2020-12/schema";
152
152
  readonly title: "OXYGEN Workflow Graph Manifest (oxygen-workflows-v2)";
153
- readonly description: "A node + edge workflow graph. Exactly one trigger node; every node reachable from it; cycles are illegal except a single back-edge from inside a loop's body to that loop node.";
153
+ readonly description: string;
154
154
  readonly type: "object";
155
155
  readonly additionalProperties: true;
156
156
  readonly properties: {
@@ -923,6 +923,93 @@ export declare const workflowGraphManifestSchema: {
923
923
  };
924
924
  };
925
925
  readonly required: readonly ["manifest_version", "workflow", "nodes", "edges", "source_hash", "compiler_version"];
926
+ readonly examples: readonly [{
927
+ readonly manifest_version: 2;
928
+ readonly compiler_version: "oxygen-workflows-v2";
929
+ readonly workflow: {
930
+ readonly id: "form-signups";
931
+ readonly name: "Form signups";
932
+ readonly status: "disabled";
933
+ };
934
+ readonly trigger: {
935
+ readonly type: "webhook";
936
+ readonly trigger_id: "form-signup";
937
+ readonly status: "disabled";
938
+ readonly secret_required: true;
939
+ };
940
+ readonly input_schema: {
941
+ readonly type: "object";
942
+ readonly properties: {
943
+ readonly name: {
944
+ readonly type: "string";
945
+ };
946
+ readonly email: {
947
+ readonly type: "string";
948
+ };
949
+ };
950
+ readonly required: readonly ["name", "email"];
951
+ };
952
+ readonly nodes: readonly [{
953
+ readonly id: "trigger";
954
+ readonly name: "Form signup";
955
+ readonly kind: "trigger";
956
+ readonly ui: {
957
+ readonly x: 0;
958
+ readonly y: 0;
959
+ };
960
+ }, {
961
+ readonly id: "row";
962
+ readonly name: "Map row";
963
+ readonly kind: "set";
964
+ readonly ui: {
965
+ readonly x: 240;
966
+ readonly y: 0;
967
+ };
968
+ readonly fields: readonly [{
969
+ readonly key: "name";
970
+ readonly value: {
971
+ readonly type: "ref";
972
+ readonly path: "trigger.input.name";
973
+ };
974
+ }, {
975
+ readonly key: "email";
976
+ readonly value: {
977
+ readonly type: "ref";
978
+ readonly path: "trigger.input.email";
979
+ };
980
+ }];
981
+ }, {
982
+ readonly id: "add_row";
983
+ readonly name: "Add row";
984
+ readonly kind: "tool";
985
+ readonly ui: {
986
+ readonly x: 480;
987
+ readonly y: 0;
988
+ };
989
+ readonly tool: "oxygen_table_rows_insert";
990
+ readonly effect: "external_write";
991
+ readonly params: {
992
+ readonly table: {
993
+ readonly type: "literal";
994
+ readonly value: "signups";
995
+ };
996
+ readonly values: {
997
+ readonly type: "ref";
998
+ readonly path: "steps.row.output";
999
+ };
1000
+ };
1001
+ }];
1002
+ readonly edges: readonly [{
1003
+ readonly id: "trigger-row";
1004
+ readonly source: "trigger";
1005
+ readonly target: "row";
1006
+ }, {
1007
+ readonly id: "row-add_row";
1008
+ readonly source: "row";
1009
+ readonly target: "add_row";
1010
+ }];
1011
+ readonly source_hash: "draft";
1012
+ }];
926
1013
  };
927
1014
  export declare const portableWorkflowDefinitionSchema: {
928
1015
  readonly $schema: "https://json-schema.org/draft/2020-12/schema";
@@ -940,7 +1027,7 @@ export declare const portableWorkflowDefinitionSchema: {
940
1027
  readonly graph: {
941
1028
  readonly $schema: "https://json-schema.org/draft/2020-12/schema";
942
1029
  readonly title: "OXYGEN Workflow Graph Manifest (oxygen-workflows-v2)";
943
- readonly description: "A node + edge workflow graph. Exactly one trigger node; every node reachable from it; cycles are illegal except a single back-edge from inside a loop's body to that loop node.";
1030
+ readonly description: string;
944
1031
  readonly type: "object";
945
1032
  readonly additionalProperties: true;
946
1033
  readonly properties: {
@@ -1713,6 +1800,93 @@ export declare const portableWorkflowDefinitionSchema: {
1713
1800
  };
1714
1801
  };
1715
1802
  readonly required: readonly ["manifest_version", "workflow", "nodes", "edges", "source_hash", "compiler_version"];
1803
+ readonly examples: readonly [{
1804
+ readonly manifest_version: 2;
1805
+ readonly compiler_version: "oxygen-workflows-v2";
1806
+ readonly workflow: {
1807
+ readonly id: "form-signups";
1808
+ readonly name: "Form signups";
1809
+ readonly status: "disabled";
1810
+ };
1811
+ readonly trigger: {
1812
+ readonly type: "webhook";
1813
+ readonly trigger_id: "form-signup";
1814
+ readonly status: "disabled";
1815
+ readonly secret_required: true;
1816
+ };
1817
+ readonly input_schema: {
1818
+ readonly type: "object";
1819
+ readonly properties: {
1820
+ readonly name: {
1821
+ readonly type: "string";
1822
+ };
1823
+ readonly email: {
1824
+ readonly type: "string";
1825
+ };
1826
+ };
1827
+ readonly required: readonly ["name", "email"];
1828
+ };
1829
+ readonly nodes: readonly [{
1830
+ readonly id: "trigger";
1831
+ readonly name: "Form signup";
1832
+ readonly kind: "trigger";
1833
+ readonly ui: {
1834
+ readonly x: 0;
1835
+ readonly y: 0;
1836
+ };
1837
+ }, {
1838
+ readonly id: "row";
1839
+ readonly name: "Map row";
1840
+ readonly kind: "set";
1841
+ readonly ui: {
1842
+ readonly x: 240;
1843
+ readonly y: 0;
1844
+ };
1845
+ readonly fields: readonly [{
1846
+ readonly key: "name";
1847
+ readonly value: {
1848
+ readonly type: "ref";
1849
+ readonly path: "trigger.input.name";
1850
+ };
1851
+ }, {
1852
+ readonly key: "email";
1853
+ readonly value: {
1854
+ readonly type: "ref";
1855
+ readonly path: "trigger.input.email";
1856
+ };
1857
+ }];
1858
+ }, {
1859
+ readonly id: "add_row";
1860
+ readonly name: "Add row";
1861
+ readonly kind: "tool";
1862
+ readonly ui: {
1863
+ readonly x: 480;
1864
+ readonly y: 0;
1865
+ };
1866
+ readonly tool: "oxygen_table_rows_insert";
1867
+ readonly effect: "external_write";
1868
+ readonly params: {
1869
+ readonly table: {
1870
+ readonly type: "literal";
1871
+ readonly value: "signups";
1872
+ };
1873
+ readonly values: {
1874
+ readonly type: "ref";
1875
+ readonly path: "steps.row.output";
1876
+ };
1877
+ };
1878
+ }];
1879
+ readonly edges: readonly [{
1880
+ readonly id: "trigger-row";
1881
+ readonly source: "trigger";
1882
+ readonly target: "row";
1883
+ }, {
1884
+ readonly id: "row-add_row";
1885
+ readonly source: "row";
1886
+ readonly target: "add_row";
1887
+ }];
1888
+ readonly source_hash: "draft";
1889
+ }];
1716
1890
  };
1717
1891
  readonly requirements: {
1718
1892
  readonly type: "object";
@@ -300,10 +300,58 @@ const edgeSchema = {
300
300
  },
301
301
  required: ["id", "source", "target"],
302
302
  };
303
+ // The smallest graph that lints clean in both phases and runs: a webhook adds
304
+ // one Table row. Agents that guessed the envelope burned their turn on lint
305
+ // round trips (status, source_hash, ui, edge ids, effect, bindings) or never
306
+ // found the row-insert tool at all (System 1 sweep 2, 2026-10-20). Pinned by a
307
+ // lint test so it cannot drift from what the linter accepts.
308
+ const minimalGraphManifestExample = {
309
+ manifest_version: WORKFLOW_GRAPH_MANIFEST_VERSION,
310
+ compiler_version: WORKFLOW_GRAPH_COMPILER_VERSION,
311
+ workflow: { id: "form-signups", name: "Form signups", status: "disabled" },
312
+ trigger: { type: "webhook", trigger_id: "form-signup", status: "disabled", secret_required: true },
313
+ input_schema: {
314
+ type: "object",
315
+ properties: { name: { type: "string" }, email: { type: "string" } },
316
+ required: ["name", "email"],
317
+ },
318
+ nodes: [
319
+ { id: "trigger", name: "Form signup", kind: "trigger", ui: { x: 0, y: 0 } },
320
+ {
321
+ id: "row",
322
+ name: "Map row",
323
+ kind: "set",
324
+ ui: { x: 240, y: 0 },
325
+ fields: [
326
+ { key: "name", value: { type: "ref", path: "trigger.input.name" } },
327
+ { key: "email", value: { type: "ref", path: "trigger.input.email" } },
328
+ ],
329
+ },
330
+ {
331
+ id: "add_row",
332
+ name: "Add row",
333
+ kind: "tool",
334
+ ui: { x: 480, y: 0 },
335
+ tool: "oxygen_table_rows_insert",
336
+ effect: "external_write",
337
+ params: {
338
+ table: { type: "literal", value: "signups" },
339
+ values: { type: "ref", path: "steps.row.output" },
340
+ },
341
+ },
342
+ ],
343
+ edges: [
344
+ { id: "trigger-row", source: "trigger", target: "row" },
345
+ { id: "row-add_row", source: "row", target: "add_row" },
346
+ ],
347
+ source_hash: "draft",
348
+ };
303
349
  export const workflowGraphManifestSchema = {
304
350
  $schema: "https://json-schema.org/draft/2020-12/schema",
305
351
  title: "OXYGEN Workflow Graph Manifest (oxygen-workflows-v2)",
306
- description: "A node + edge workflow graph. Exactly one trigger node; every node reachable from it; cycles are illegal except a single back-edge from inside a loop's body to that loop node.",
352
+ description: "A node + edge workflow graph. Exactly one trigger node; every node reachable from it; cycles are illegal except a single back-edge from inside a loop's body to that loop node."
353
+ + " `examples[0]` is a minimal valid graph: a webhook trigger (type api is the by-hand trigger) whose payload a set node maps, and a tool node adding the row with oxygen_table_rows_insert (table slug or id; effect external_write)."
354
+ + " source_hash must be a non-empty string; lint does not verify it.",
307
355
  type: "object",
308
356
  additionalProperties: true,
309
357
  properties: {
@@ -328,6 +376,7 @@ export const workflowGraphManifestSchema = {
328
376
  created_at: { type: "string" },
329
377
  },
330
378
  required: ["manifest_version", "workflow", "nodes", "edges", "source_hash", "compiler_version"],
379
+ examples: [minimalGraphManifestExample],
331
380
  };
332
381
  export const portableWorkflowDefinitionSchema = {
333
382
  $schema: "https://json-schema.org/draft/2020-12/schema",