@oxygen-agent/cli 1.1014.1465 → 1.1014.1598

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 +51 -14
  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
@@ -3,9 +3,9 @@ import { resolve } from "node:path";
3
3
  import { readJsonFileValue } from "./command-support.js";
4
4
  import { requestOxygen } from "./http-client.js";
5
5
  export function registerWorkspacePlacementCommands(root, handle) {
6
- root.addCommand(new Command("workspace-test").description("Compare email copy placement in this workspace's connected Google/Microsoft receiving inboxes: Gmail Primary/Promotions and Microsoft Focused/Other. Native sends, 0 additional credits; preview and exact message approval required.")
6
+ root.addCommand(new Command("workspace-test").addCommand(bulkCommands(handle)).description("Compare email copy placement in this workspace's connected Google/Microsoft receiving inboxes: Gmail Primary/Promotions and Microsoft Focused/Other. Native sends, 0 additional credits; preview and exact message approval required.")
7
7
  .addCommand(new Command("run").description("Preview real outreach copy internally or across workspaces. Use a Sequence step or realistic prospect email, never delivery-check/no-reply text. Input JSON: sender_mailbox_ids, receiver_mailbox_ids, variants [{key,subject,body_text,body_html?}]. Approve the same input with returned request_id/plan_hash/rendered_at and max_live_sends. Five variants/50 messages; no provider purchase or campaign resume.")
8
- .option("--receiver-scope <scope>", "workspace or network; network privately selects participating receivers.").option("--receiver-providers <providers>", "Comma-separated google,microsoft for network tests.")
8
+ .option("--receiver-scope <scope>", "workspace, network or staff; staff selects named receivers across dev/prod workspaces.").option("--receiver-environment <environment>", "dev or prod for staff tests.").option("--receiver-workspace <id>", "Receiving workspace UUID for staff tests.").option("--receiver-providers <providers>", "Comma-separated google,microsoft for network tests.")
9
9
  .requiredOption("--input <file>", "JSON test input file.").option("--approved", "Execute the exact reviewed matrix.")
10
10
  .option("--rendered-at <timestamp>", "Rendering timestamp returned by preview; reuse when approving.").option("--tracking-opens", "Preview with open tracking on verified workspace domain.").option("--tracking-clicks", "Preview with click tracking on verified workspace domain.")
11
11
  .option("--request-id <id>", "Request UUID from preview.").option("--plan <hash>", "Exact preview plan hash.").option("--max-live-sends <n>", "Maximum approved diagnostic emails (1–50).")
@@ -19,10 +19,14 @@ export function registerWorkspacePlacementCommands(root, handle) {
19
19
  return requestOxygen("/api/cli/deliverability/workspace-tests", { method: "POST", body: { ...input, approved: options.approved === true,
20
20
  ...(options.approved ? { approved: true } : {}), ...(options.requestId ? { request_id: options.requestId } : {}),
21
21
  ...(options.plan ? { plan_hash: options.plan } : {}), ...(options.renderedAt ? { rendered_at: options.renderedAt } : {}), ...(options.trackingOpens ? { tracking_opens: true } : {}), ...(options.trackingClicks ? { tracking_clicks: true } : {}), ...(options.maxLiveSends ? { max_live_sends: Number(options.maxLiveSends) } : {}),
22
+ ...(options.receiverEnvironment ? { receiver_environment: options.receiverEnvironment } : {}), ...(options.receiverWorkspace ? { receiver_workspace_id: options.receiverWorkspace } : {}),
22
23
  ...(options.allowPausedProbe ? { allow_paused_probe: true } : {}), ...(options.receiverScope ? { receiver_scope: options.receiverScope } : {}), ...(options.receiverProviders ? { receiver_providers: String(options.receiverProviders).split(",") } : {}),
23
24
  ...(options.previewCursor ? { preview_cursor: options.previewCursor } : {}), ...(options.previewLimit !== undefined ? { preview_limit: Number(options.previewLimit) } : {}),
24
25
  ...(options.previewProbeId ? { preview_probe_id: options.previewProbeId } : {}), ...(options.previewPart ? { preview_part: options.previewPart } : {}), ...(options.previewOffset !== undefined ? { preview_offset: Number(options.previewOffset) } : {}), ...(options.previewCopyLimit !== undefined ? { preview_copy_limit: Number(options.previewCopyLimit) } : {}) } });
25
26
  })))
27
+ .addCommand(new Command("receivers").description("Discover staff-only receiving workspaces and connected Google/Microsoft mailboxes across dev and prod. No send or participation change.")
28
+ .requiredOption("--receiver-environment <environment>", "dev or prod.").option("--receiver-workspace <id>", "List mailboxes in this workspace UUID.").option("--query <search>", "Search workspaces by name, slug or ID.").option("--json", "Print JSON.")
29
+ .action(async (options) => handle("deliverability workspace-test receivers", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests?${new URLSearchParams({ action: "receivers", receiver_environment: String(options.receiverEnvironment), ...(options.receiverWorkspace ? { receiver_workspace_id: String(options.receiverWorkspace) } : {}), ...(options.query ? { query: String(options.query) } : {}) })}`))))
26
30
  .addCommand(new Command("network").description("Read or manage default-on receiving-pool participation. Administrators can opt out or accept the current disclosure. Does not send mail.")
27
31
  .option("--enabled <boolean>", "true or false; omit to read.").option("--accept-disclosure", "Accept real-copy delivery to participating mailboxes and metadata processing.").option("--json", "Print JSON.")
28
32
  .action(async (options) => handle("deliverability workspace-test network", options, () => {
@@ -40,3 +44,64 @@ export function registerWorkspacePlacementCommands(root, handle) {
40
44
  .option("--probe <id>", "Read this probe's frozen message copy.").option("--part <part>", "body_text or body_html.").option("--offset <n>", "Returned Unicode character offset.").option("--copy-limit <n>", "Copy page size (1–3000 characters).").option("--json", "Print JSON.")
41
45
  .action(async (id, options) => handle("deliverability workspace-test get", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/${encodeURIComponent(id)}?${new URLSearchParams({ ...options.cursor ? { cursor: options.cursor } : {}, ...options.limit !== undefined ? { limit: String(options.limit) } : {}, ...options.probe ? { probe_id: options.probe } : {}, ...options.part ? { body_part: options.part } : {}, ...options.offset !== undefined ? { offset: String(options.offset) } : {}, ...options.copyLimit !== undefined ? { copy_limit: String(options.copyLimit) } : {} })}`)))));
42
46
  }
47
+ function bulkCommands(handle) {
48
+ return new Command("bulk").description("Deliverability testing for all eligible mailboxes using this workspace's Google/Microsoft receivers. Preview first; 0 additional credits; approval sends real email.")
49
+ .addCommand(new Command("run").description("Preview deliverability testing of all eligible mailboxes, independent of page filters. Input JSON: variants [{key,subject,body_text,body_html?}], optional receiver_providers (google,microsoft). Review every recipient and rendered copy, then approve the same input with request_id/plan_hash/rendered_at and the exact aggregate max_live_sends. Child runs hold at most 50 messages; a batch can exceed 50. No selected sender/receiver IDs.")
50
+ .requiredOption("--input <file>", "JSON copy and test input file.")
51
+ .option("--receiver-providers <providers>", "Comma-separated google,microsoft; default both.")
52
+ .option("--approved", "Send the exact reviewed batch.")
53
+ .option("--request-id <id>", "Request UUID returned by preview.")
54
+ .option("--plan <hash>", "Exact aggregate preview plan hash.")
55
+ .option("--rendered-at <timestamp>", "Rendering timestamp returned by preview; reuse when approving.")
56
+ .option("--max-live-sends <n>", "Exact approved aggregate message count; can exceed 50.")
57
+ .option("--allow-paused-probe", "Explicitly allow diagnostic sends from paused senders, preserving pauses.")
58
+ .option("--tracking-opens", "Preview with open tracking on a verified workspace domain.")
59
+ .option("--tracking-clicks", "Preview with click tracking on a verified workspace domain.")
60
+ .option("--preview-skipped-cursor <id>", "Read the next skipped-sender page with the same frozen preview.")
61
+ .option("--preview-cursor <id>", "Read the next manifest page with the same input, request ID and rendering time.")
62
+ .option("--preview-limit <n>", "Manifest page size (1–20); omit paging when approving.")
63
+ .option("--preview-probe-id <id>", "Read exact rendered copy for this preview probe.")
64
+ .option("--preview-part <part>", "body_text or body_html.")
65
+ .option("--preview-offset <n>", "Returned Unicode character offset.")
66
+ .option("--preview-copy-limit <n>", "Copy page size (1–3000 characters).")
67
+ .option("--json", "Print JSON.")
68
+ .action(async (options) => handle("deliverability workspace-test bulk run", options, () => {
69
+ const input = readJsonFileValue(resolve(options.input), "--input");
70
+ if (!input || typeof input !== "object" || Array.isArray(input))
71
+ throw new Error("--input must contain a JSON object.");
72
+ return requestOxygen("/api/cli/deliverability/workspace-tests/bulk", { method: "POST", body: {
73
+ ...input, approved: options.approved === true,
74
+ ...(options.receiverProviders ? { receiver_providers: String(options.receiverProviders).split(",") } : {}),
75
+ ...(options.requestId ? { request_id: options.requestId } : {}),
76
+ ...(options.plan ? { plan_hash: options.plan } : {}),
77
+ ...(options.renderedAt ? { rendered_at: options.renderedAt } : {}),
78
+ ...(options.maxLiveSends !== undefined ? { max_live_sends: Number(options.maxLiveSends) } : {}),
79
+ ...(options.allowPausedProbe ? { allow_paused_probe: true } : {}),
80
+ ...(options.trackingOpens ? { tracking_opens: true } : {}),
81
+ ...(options.trackingClicks ? { tracking_clicks: true } : {}),
82
+ ...(options.previewSkippedCursor ? { preview_skipped_cursor: options.previewSkippedCursor } : {}),
83
+ ...(options.previewCursor ? { preview_cursor: options.previewCursor } : {}),
84
+ ...(options.previewLimit !== undefined ? { preview_limit: Number(options.previewLimit) } : {}),
85
+ ...(options.previewProbeId ? { preview_probe_id: options.previewProbeId } : {}),
86
+ ...(options.previewPart ? { preview_part: options.previewPart } : {}),
87
+ ...(options.previewOffset !== undefined ? { preview_offset: Number(options.previewOffset) } : {}),
88
+ ...(options.previewCopyLimit !== undefined ? { preview_copy_limit: Number(options.previewCopyLimit) } : {}),
89
+ } });
90
+ })))
91
+ .addCommand(new Command("list").description("Read saved deliverability testing batches for all mailboxes. No sends, refresh or charge.")
92
+ .option("--cursor <id>", "Next batch cursor.").option("--limit <n>", "Batches per page (1–20).").option("--json", "Print JSON.")
93
+ .action(async (options) => handle("deliverability workspace-test bulk list", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/bulk?${new URLSearchParams({
94
+ ...(options.cursor ? { cursor: options.cursor } : {}), ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
95
+ })}`))))
96
+ .addCommand(new Command("get").description("Read one deliverability testing batch, provider coverage and bounded results. No sends or provider refresh.")
97
+ .argument("<id>").option("--cursor <id>", "Next receiver-result cursor.").option("--skipped-cursor <id>", "Next skipped-sender cursor.").option("--limit <n>", "Receiver results per page (1–20).")
98
+ .option("--probe <id>", "Read this probe's frozen message copy.").option("--part <part>", "body_text or body_html.")
99
+ .option("--offset <n>", "Returned Unicode character offset.").option("--copy-limit <n>", "Copy page size (1–3000 characters).")
100
+ .option("--json", "Print JSON.")
101
+ .action(async (id, options) => handle("deliverability workspace-test bulk get", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/bulk/${encodeURIComponent(id)}?${new URLSearchParams({
102
+ ...(options.cursor ? { cursor: options.cursor } : {}), ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
103
+ ...(options.skippedCursor ? { skipped_cursor: options.skippedCursor } : {}),
104
+ ...(options.probe ? { probe_id: options.probe } : {}), ...(options.part ? { body_part: options.part } : {}),
105
+ ...(options.offset !== undefined ? { offset: String(options.offset) } : {}), ...(options.copyLimit !== undefined ? { copy_limit: String(options.copyLimit) } : {}),
106
+ })}`))));
107
+ }
@@ -104,8 +104,10 @@ export declare function readProductSkillInstructionResult(capabilityName: string
104
104
  export declare function isPublicCapabilitySkill(name: string): boolean;
105
105
  export declare function serializeCapabilityRoute(route: CapabilityRouteMatch | null, surfaceOrOptions?: CapabilityRouteSurface | {
106
106
  visibleSkills?: readonly string[];
107
+ nativeChat?: boolean;
107
108
  }, options?: {
108
109
  visibleSkills?: readonly string[];
110
+ nativeChat?: boolean;
109
111
  }): Record<string, unknown> | null;
110
112
  /**
111
113
  * Put the Network Mining record brief first on a record-dossier route where the
@@ -187,6 +189,13 @@ export declare const CAPABILITY_ROUTE_MCP_CALLS: {
187
189
  include_unsubscribe: boolean;
188
190
  };
189
191
  };
192
+ sequenceReplyToStop: () => {
193
+ tool: string;
194
+ arguments: {
195
+ sequence: string;
196
+ reply_to_stop_text: string;
197
+ };
198
+ };
190
199
  ugcProgramCreate: () => {
191
200
  tool: string;
192
201
  arguments: {
@@ -1,5 +1,6 @@
1
1
  import { findEnrichmentIntentByAlias } from "./enrichment-intents.js";
2
2
  import { networkMiningEnabled } from "./network-mining.js";
3
+ import { nativeChatEnabled, withNativeChatDiscovery } from "./native-chat-discovery.js";
3
4
  import { RECIPE_PRIMITIVES } from "./recipes.js";
4
5
  // One compact semantic index covers every top-level /api/cli endpoint section.
5
6
  // It is navigation, never execution: live schemas, prices, readiness, and
@@ -113,11 +114,11 @@ export const OXYGEN_CAPABILITY_ROUTES = [
113
114
  notFor: "Primitive run diagnosis when no support report is needed; inspect Observability first.",
114
115
  execution: "Start or continue a hosted support conversation, optionally file structured metadata, and return its deep link; never send a customer reply automatically.",
115
116
  posture: "workspace_write",
116
- gatewayTools: ["oxygen_feedback", "oxygen_support_ticket_create", "oxygen_support_tickets_list"],
117
- gatewayCommands: ["feedback", "support chat", "support file", "support list"],
117
+ gatewayTools: ["oxygen_feedback", "oxygen_support_ticket_create", "oxygen_support_tickets_list", "oxygen_chat_channel", "oxygen_chat_thread_get", "oxygen_chat_thread_create", "oxygen_chat_thread_reply", "oxygen_chat_file_upload", "oxygen_chat_file_get", "oxygen_chat_thread_read"],
118
+ gatewayCommands: ["feedback", "support chat", "support file", "support list", "chat channel", "chat threads get", "chat threads create", "chat threads reply", "chat files upload", "chat files get", "chat threads read"],
118
119
  skills: ["oxygen-feedback"],
119
- endpointSections: ["feedback", "support"],
120
- intentTerms: ["support", "feedback", "bug report", "feature request", "report issue", "ticket", "complaint"],
120
+ endpointSections: ["feedback", "support", "chat"],
121
+ intentTerms: ["support", "feedback", "bug report", "feature request", "report issue", "ticket", "complaint", "support chat", "support thread", "chat attachment"],
121
122
  },
122
123
  {
123
124
  id: "platform-operations",
@@ -371,10 +372,10 @@ export const OXYGEN_CAPABILITY_ROUTES = [
371
372
  // elsewhere is `sequences push-external`; it is not a Sequence.
372
373
  execution: "Use the one-step gateway for new LinkedIn initiation or create/enroll/start a hosted campaign with bounded send authority. Native email sends through connected Google/Microsoft mailboxes, including Microsoft SMTP transport. To push table rows into an existing Smartlead, Instantly, lemlist or HeyReach campaign you already run, use `sequences push-external` (dry_run preview, then --approved). To stop a company's other leads when one of them replies, set `--company-reply-pause` on the campaign (choose the reply categories); to pause or resume a company on demand, e.g. from a workflow when a meeting is booked, use `sequences pause-company` / `resume-company`. A pause is resumable; a company do-not-contact entry is a permanent block on every campaign. Step copy takes lead columns as {{column}} and the sending person's name as {{sender_first_name}}, {{sender_last_name}} or {{sender_full_name}} (email adds {{sender_email}}), with no table column.",
373
374
  posture: "external_write",
374
- gatewayTools: ["oxygen_sequences_send", "oxygen_sequences_create", "oxygen_sequences_enroll", "oxygen_sequences_start", "oxygen_sequences_list", "oxygen_sequences_push_external", "oxygen_sequences_company_pause"],
375
- gatewayCommands: ["sequences send", "sequences create", "sequences enroll", "sequences start", "sequences list", "sequences push-external", "sequences pause-company"],
375
+ gatewayTools: ["oxygen_sequences_send", "oxygen_sequences_create", "oxygen_sequences_enroll", "oxygen_sequences_start", "oxygen_sequences_list", "oxygen_sequences_update", "oxygen_sequences_get", "oxygen_sequences_push_external", "oxygen_sequences_company_pause"],
376
+ gatewayCommands: ["sequences send", "sequences create", "sequences enroll", "sequences start", "sequences list", "sequences update", "sequences get", "sequences push-external", "sequences pause-company"],
376
377
  skills: ["oxygen-sequencer"],
377
- endpointSections: ["senders", "sequencer", "sequences", "spintax", "suppressions", "voice"],
378
+ endpointSections: ["senders", "sequencer", "sequences", "spintax", "subscriptions", "suppressions", "voice"],
378
379
  // "sender identity", "sender profile" and "phone number" are here because a
379
380
  // blind-user eval (2026-08-09) asked for "our sender identity" and got routed
380
381
  // to workspace-access — "identity" matched the AUTH sense. The nouns a user
@@ -385,7 +386,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
385
386
  // collaboration; the {{sender_*}} copy variables live on sequence steps.
386
387
  // A/B tests are Sequence step variants: "pause a losing A/B variant" ranked
387
388
  // Table-run pauses first (2026-10-10 parity audit).
388
- intentTerms: ["a/b test", "a/b variant", "ab test", "split test", "auto-winner", "winning variant", "losing variant", "sequence", "campaign", "cadence", "enroll", "outreach", "linkedin message", "linkedin dm", "nurture", "follow up", "sender rotation", "stop on reply", "company replied", "pause company", "pause outreach to a company", "stop outreach to a company", "stop emailing a company", "colleagues", "do not contact a company", "sender identity", "sending identity", "sender profile", "phone number", "dialing", "dialer", "call", "cold call", "smartlead", "instantly", "lemlist", "heyreach", "external sequencer", "push to campaign", "push rows to campaign", "add leads to campaign", "sign-off", "sign off", "signature", "sender name", "sender first name", "sender variable", "sender_first_name"],
389
+ intentTerms: ["withdraw invitation on timeout", "withdraw unanswered invite", "withdraw_on_timeout", "a/b test", "a/b variant", "ab test", "split test", "auto-winner", "winning variant", "losing variant", "sequence", "campaign", "cadence", "enroll", "outreach", "linkedin message", "linkedin dm", "nurture", "follow up", "sender rotation", "stop on reply", "company replied", "pause company", "pause outreach to a company", "stop outreach to a company", "stop emailing a company", "colleagues", "do not contact a company", "sender identity", "sending identity", "sender profile", "phone number", "dialing", "dialer", "call", "cold call", "smartlead", "instantly", "lemlist", "heyreach", "external sequencer", "push to campaign", "push rows to campaign", "add leads to campaign", "sign-off", "sign off", "signature", "sender name", "sender first name", "sender variable", "sender_first_name"],
389
390
  },
390
391
  {
391
392
  id: "publishing",
@@ -410,7 +411,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
410
411
  primitive: "workflows",
411
412
  owns: "Deterministic hosted automation definitions, versions, triggers, branching, retries, approvals, modes, and durable runs.",
412
413
  notFor: "Local cron, client-side execution, adaptive Agent goals, or outreach cadence already owned by Sequences.",
413
- execution: "Author locally if useful, then schema/lint/apply and call the Workflow on OXYGEN workers; outbound uses internal oxygen.sequence_enroll against an already active bounded Sequence.",
414
+ execution: "Author locally if useful, then schema/lint/apply and call the Workflow on OXYGEN workers; outbound uses internal oxygen.sequence_enroll against an already active bounded Sequence. Start from the graph schema's examples[0] (oxygen_workflows_schema subject graph): a minimal valid manifest whose tool node adds a Table row with oxygen_table_rows_insert. A draft that is not saved exists only in your reply, so show its manifest there. To find why a definition fails, lint it as written first. Saving without running is apply with publish false and workflow and trigger status disabled.",
414
415
  posture: "mixed",
415
416
  gatewayTools: ["oxygen_workflows_schema", "oxygen_workflows_lint", "oxygen_workflows_apply", "oxygen_workflows_call", "oxygen_workflows_tail"],
416
417
  gatewayCommands: ["workflows schema", "workflows lint", "workflows apply", "workflows call", "workflows tail"],
@@ -590,6 +591,16 @@ export function inferCapabilityRoute(query) {
590
591
  const normalized = normalizeIntent(query);
591
592
  if (!normalized)
592
593
  return null;
594
+ if (/\b(mailbox(?:es)?|inbox(?:es)?)\b/.test(normalized) && /\b(standby|reserve|replacement|replace|burn(?:ed|t)?)\b/.test(normalized)) {
595
+ const campaign = /\b(campaign|sequence|cadence)\b/.test(normalized);
596
+ const card = ROUTE_BY_ID.get(campaign ? "sequences" : "sending-infrastructure") ?? ROUTE_BY_PRIMITIVE.get("sequences");
597
+ if (card)
598
+ return {
599
+ card,
600
+ recommendedTools: campaign ? ["oxygen_sequences_standby_get", "oxygen_sequences_standby_set", "oxygen_mailboxes_standby_pool"] : ["oxygen_mailboxes_standby_pool", "oxygen_mailboxes_standby_get", "oxygen_mailboxes_standby_set", "oxygen_mailboxes_standby_policy"],
601
+ recommendedCommands: campaign ? ["sequences standby get", "sequences standby set", "mailboxes standby pool"] : ["mailboxes standby pool", "mailboxes standby get", "mailboxes standby set", "mailboxes standby policy"],
602
+ };
603
+ }
593
604
  // A complete audience play starts from its Recipe and the shared audience
594
605
  // guide; onboarding consults on open-ended goals and never routes this ask.
595
606
  // Its source nouns otherwise select a one-shot post search or follower order.
@@ -790,6 +801,8 @@ export function serializeCapabilityRoute(route, surfaceOrOptions = "cli", option
790
801
  const visibleSkills = typeof surfaceOrOptions === "string" ? options.visibleSkills : surfaceOrOptions.visibleSkills;
791
802
  if (!route)
792
803
  return null;
804
+ const nativeChat = (typeof surfaceOrOptions === "string" ? options.nativeChat : surfaceOrOptions.nativeChat) ?? nativeChatEnabled(runtimeEnv());
805
+ route = withNativeChatDiscovery(route, nativeChat);
793
806
  return {
794
807
  capability_id: route.card.id,
795
808
  layer: route.card.layer,
@@ -865,7 +878,7 @@ function isLinkedInAudienceCampaignIntent(rawQuery) {
865
878
  || isPostCoverOrPreviewIntent(query) || isFailedPostIntent(query)
866
879
  || isOwnedPostCommentIntent(query) || isSecondPartyDecisionIntent(query)
867
880
  || tableReadRecommendation(query) || tableMaintenanceRecommendation(query)
868
- || isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query)
881
+ || isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query) || isSequenceInvitationTimeoutIntent(query)
869
882
  || isSenderProfileIntent(query) || isSenderCopyVariableIntent(query)
870
883
  || isLinkedInAccountLimitsIntent(query)
871
884
  || /\b(workflows?|sequences?|cadences?|webhook|cron|event trigger|branching|retries)\b/.test(query)
@@ -1481,6 +1494,7 @@ const WORKSPACE_VOICE_EXECUTION = "A writing voice is learned from the workspace
1481
1494
  export const SEQUENCE_SENDING_SCHEDULE_COMMAND = "oxygen sequences update <sequence> --schedule-timezone <IANA zone> --schedule-days mon-fri --schedule-hours-start 09:00 --schedule-hours-end 17:00 --json";
1482
1495
  /** Launch's unsubscribe switch: the visible link and the one-click headers together. */
1483
1496
  export const SEQUENCE_UNSUBSCRIBE_COMMAND = "oxygen sequences update <sequence> --include-unsubscribe --json";
1497
+ const SEQUENCE_REPLY_TO_STOP_COMMAND = 'oxygen sequences update <sequence> --reply-to-stop-text "Reply STOP to opt out." --json';
1484
1498
  /**
1485
1499
  * Every exact CLI command a route hands out, as the MCP tool call that sends the
1486
1500
  * same request. The MCP surface serves these in place of the command: an MCP
@@ -1502,6 +1516,7 @@ export const CAPABILITY_ROUTE_MCP_CALLS = {
1502
1516
  arguments: { sequence: "<sequence>", sending_schedule: { timezone: "<IANA zone>", days: [1, 2, 3, 4, 5], start: "09:00", end: "17:00" } },
1503
1517
  }),
1504
1518
  sequenceUnsubscribe: () => ({ tool: "oxygen_sequences_update", arguments: { sequence: "<sequence>", include_unsubscribe: true } }),
1519
+ sequenceReplyToStop: () => ({ tool: "oxygen_sequences_update", arguments: { sequence: "<sequence>", reply_to_stop_text: "Reply STOP to opt out." } }),
1505
1520
  ugcProgramCreate: () => ({ tool: "oxygen_ugc_program_create", arguments: { product_url: "<your product site>" } }),
1506
1521
  // The first comment rides in `content`, as the CLI's --first-comment does.
1507
1522
  publishingFirstComment: () => ({
@@ -1557,6 +1572,15 @@ export function catalogueIntentMcpCall(intentId) {
1557
1572
  function isSequenceLeadProgressIntent(query) {
1558
1573
  return /\b(?:next (?:message|step|send)|stuck|blocked|held|per[- ]lead (?:status|progress)|why (?:\w+ )?(?:stopped|failed))\b/.test(query);
1559
1574
  }
1575
+ function isSequenceInvitationTimeoutIntent(query) {
1576
+ if (/\b(?:workflow|automation|automate|webhook|cron)\b/.test(query))
1577
+ return false;
1578
+ if (/\bwithdraw on timeout\b/.test(query))
1579
+ return true;
1580
+ return /\b(?:withdraw\w*|cancel\w*)\b/.test(query)
1581
+ && /\b(?:invitations?|invites?|connection requests?)\b/.test(query)
1582
+ && /\b(?:timeouts?|time out|expires?|expired|expiry|unanswered|unaccepted)\b/.test(query);
1583
+ }
1560
1584
  function isSequenceScheduleIntent(query) {
1561
1585
  return /\bsend(?:ing)? (?:schedule|window)s?\b/.test(query)
1562
1586
  && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query);
@@ -1574,6 +1598,14 @@ function isMailboxVolumeIntent(query) {
1574
1598
  const volume = /\b(ramp\w*|daily (?:campaign )?(?:limit|cap)s?|sending limits?|send limits?|(?:emails?|sends?) (?:per|a) day|volume)\b/.test(query);
1575
1599
  return inbox && volume;
1576
1600
  }
1601
+ function isSequenceReplyToStopIntent(query) {
1602
+ if (/\b(workflow|automation|automate|webhook|cron|lists?|import\w*|suppress\w*|block ?list|blocklist|do not contact|dnc|conversations?|threads?)\b/.test(query))
1603
+ return false;
1604
+ const footer = /\bfooter\b/.test(query) || /\breply to stop text\b/.test(query);
1605
+ const sequenceSetting = /\b(sequences?|campaigns?)\b/.test(query) && /\b(text|wording|line|setting)\b/.test(query);
1606
+ return (footer || sequenceSetting) && (/\breply (?:to )?stop\b/.test(query)
1607
+ || /\bopt out\b/.test(query) && /\b(text|wording|line|footer)\b/.test(query));
1608
+ }
1577
1609
  // The unsubscribe option on a Sequence's emails, not the list of people who
1578
1610
  // opted out: a list, import or do-not-contact ask stays with suppressions.
1579
1611
  function isSequenceUnsubscribeIntent(query) {
@@ -2258,6 +2290,8 @@ function explicitCapabilityIntent(query) {
2258
2290
  return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
2259
2291
  if (isWorkspaceVoiceIntent(query))
2260
2292
  return ROUTE_BY_ID.get("knowledge-graph") ?? null;
2293
+ if (isSequenceInvitationTimeoutIntent(query))
2294
+ return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
2261
2295
  if (isOwnLinkedInNetworkIntent(query) || isOpenLinkedInRequestsIntent(query))
2262
2296
  return ROUTE_BY_ID.get("connected-linkedin") ?? null;
2263
2297
  if (isCompanyPageFollowersIntent(query))
@@ -2316,7 +2350,7 @@ function explicitCapabilityIntent(query) {
2316
2350
  // option. Ahead of the workflow rule, whose bare "schedule" sent the 2026-09-29
2317
2351
  // unified-launch acceptance agent's "sending schedule" search to Workflows and
2318
2352
  // hid every Sequences command.
2319
- if (isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query))
2353
+ if (isSequenceReplyToStopIntent(query) || isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query))
2320
2354
  return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
2321
2355
  if (isMailboxVolumeIntent(query) && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query)) {
2322
2356
  return ROUTE_BY_ID.get("sending-infrastructure") ?? null;
@@ -3075,6 +3109,20 @@ function recommendationsFor(card, query) {
3075
3109
  if (isSpintaxIntent(query)) {
3076
3110
  return { tools: ["oxygen_spintax_generate"], commands: ["spintax generate"] };
3077
3111
  }
3112
+ if (isSequenceInvitationTimeoutIntent(query)) {
3113
+ return {
3114
+ tools: ["oxygen_sequences_update", "oxygen_sequences_get", "oxygen_sequences_create"],
3115
+ commands: ["sequences update", "sequences get", "sequences create"],
3116
+ };
3117
+ }
3118
+ if (isSequenceReplyToStopIntent(query)) {
3119
+ return {
3120
+ tools: ["oxygen_sequences_update", "oxygen_sequences_get"],
3121
+ commands: ["sequences update", "sequences get"],
3122
+ exactCommand: SEQUENCE_REPLY_TO_STOP_COMMAND,
3123
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.sequenceReplyToStop(),
3124
+ };
3125
+ }
3078
3126
  if (isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query)) {
3079
3127
  return {
3080
3128
  tools: ["oxygen_sequences_update", "oxygen_sequences_get"],
@@ -50,7 +50,7 @@ export const BROWSER_OPERATIONS = [
50
50
  operation("billing.commitments.view", "/api/cli/billing/commitments/view", "GET", "billing commitments view", "oxygen_billing_commitments_view", "billing", "Read the complete fixed-credit commitment view and linked billing-owner display, without enumerating a child's sibling resources."),
51
51
  operation("billing.invoices.view", "/api/cli/billing/invoices/view", "GET", "billing invoices view", "oxygen_billing_invoices_view", "billing", "Read the browser invoice-history projection, including exact native timestamps, amounts and signed Stripe invoice links."),
52
52
  operation("billing.summary", "/api/cli/billing/summary", "GET", "billing summary", "oxygen_billing_summary", "billing", "Read saved wallet/plan, partner bonus and sending_seats status, period-end meaning, channel capacity, prices and snapshot eligibility. No grants or expiry. Connection actions recheck eligibility; first debit may remain unknown. trialCreditGrant is a budget, not a recorded grant."),
53
- operation("billing.usage.view", "/api/cli/billing/usage/view", "GET", "billing usage view", "oxygen_billing_usage_view", "billing", "Credit spend over 7, 30 or 90 days by category and provider, each with its share, plus permitted linked-workspace rollups. totalSpent is reserved minus released; billing usage managed_credits_used counts captured credits only. Other windows or groupings: billing audit.", { days: enumOf(["7", "30", "90"]), workspace: text() }),
53
+ operation("billing.usage.view", "/api/cli/billing/usage/view", "GET", "billing usage view", "oxygen_billing_usage_view", "billing", "Credit spend over 7, 30 or 90 days by category and provider, each with its share, plus permitted linked-workspace rollups; due plan grants or credit expiry may apply. totalSpent is reserved minus released; billing usage managed_credits_used counts captured credits only. Other windows or groupings: billing audit.", { days: enumOf(["7", "30", "90"]), workspace: text() }, [], { mutates: true }),
54
54
  operation("integrations.managed_endpoints", "/api/cli/integrations/composio/endpoints", "GET", "integrations managed-endpoints", "oxygen_integration_managed_endpoints", "integrations", "Read a managed integration's full action catalog and parameter schemas.", { integration_id: text() }, ["integration_id"]),
55
55
  operation("integrations.openrouter_models", "/api/cli/integrations/openrouter/models", "GET", "integrations openrouter-models", "oxygen_openrouter_models", "integrations", "Read model and pricing metadata using this workspace's connected OpenRouter account; credentials are never returned."),
56
56
  operation("integrations.status", "/api/cli/integrations/status", "GET", "integrations status", "oxygen_integration_status", "integrations", "Read active connection counts, sender connection state and the workspace's usable BYOK plan entitlement."),
@@ -134,6 +134,6 @@ export const BROWSER_OPERATIONS = [
134
134
  operation("notetaker.note.edit", "/api/cli/notetaker/sessions/{session}/note", "PATCH", "notetaker notes edit", "oxygen_notetaker_note_edit", "crm", "Save the markdown note for one meeting session and return the committed note.", { markdown: text() }, ["markdown"]),
135
135
  operation("orgs.create", "/api/cli/orgs/create", "POST", "orgs create", "oxygen_workspace_create", "workspace", "Create a workspace for the authenticated person's account and begin native company setup. Workspace API keys cannot create personal workspaces.", { name: text(), company_domain: text("Company website or domain, or its LinkedIn company page URL when it has no website."), linkedin_profile_url: text("Creator's LinkedIn profile URL, separate from the company's page.") }, ["name", "company_domain"]),
136
136
  operation("onboarding.complete", "/api/cli/onboarding/complete", "POST", "onboarding complete", "oxygen_onboarding_complete", "workspace", "Complete the active workspace's onboarding step and record its discovery-source choices. The other choice requires source_other.", { sources: arr(enumOf(["linkedin_content", "linkedin_dm", "linkedin_ads", "x", "youtube", "google", "ai_assistant", "email", "word_of_mouth", "referral", "event", "other"])), source_other: text(), redirect_url: text() }, ["sources"]),
137
- operation("onboarding.finish", "/api/cli/onboarding/finish", "POST", "onboarding finish", "oxygen_onboarding_finish", "workspace", "Finish agent-led onboarding once a play is chosen: the onboarding Oxygent chat opens as an ordinary chat, without the Knowledge pane. Changes nothing else and spends nothing."),
137
+ operation("onboarding.finish", "/api/cli/onboarding/finish", "POST", "onboarding finish", "oxygen_onboarding_finish", "workspace", "Finish agent-led onboarding once a play is chosen: the onboarding Oxygent chat opens as an ordinary chat, without the Knowledge pane. If the chat holds your sent play, records your activation once. Spends nothing."),
138
138
  operation("onboarding.start", "/api/cli/onboarding/start", "POST", "onboarding start", "oxygen_onboarding_start", "workspace", "Answer onboarding's choice card. own_agent continues to connecting your own agent. copilot opens the Workspace Copilot with the onboarding conversation already started, billed in credits like any Copilot turn; choosing again reopens that conversation. restart (copilot) shows a finished onboarding again and asks the Oxygent to start over, as an ordinary send.", { path: enumOf(["own_agent", "copilot"]), play: text("A play slug carried to the install step."), restart: bool() }, ["path"], { spends: true }),
139
139
  ];
@@ -9,6 +9,7 @@ import { CRM_CONNECTIONS_QUERY_CONTRACT, CRM_FILE_ATTACH_INPUT_CONTRACT, CRM_FIL
9
9
  import { spintaxContract } from "./spintax.js";
10
10
  import { betaFeaturesUpdateContract, emailDomainSuppressionListContract, emailDomainSuppressionRemoveContract, emailSuppressionRemoveContract, providerLimitListContract, providerLimitSelectorContract, providerLimitUpdateContract } from "./workspace-utilities.js";
11
11
  import { knowledgeSyncReadContract } from "./knowledge-sync.js";
12
+ import { CHAT_ADMIN_READ_CONTRACT, CHAT_CHANNEL_READ_CONTRACT, CHAT_FILE_UPLOAD_CONTRACT, CHAT_MESSAGE_CONTRACT, CHAT_THREAD_CREATE_CONTRACT, CHAT_THREAD_PATCH_CONTRACT, CHAT_THREAD_READ_CONTRACT } from "./native-chat.js";
12
13
  const text = (description) => str(description === undefined ? {} : { description });
13
14
  const list = () => arr(str());
14
15
  const json = { type: "object", additionalProperties: true };
@@ -28,6 +29,23 @@ function operation(id, route, method, cli, mcp, pack, summary, fields = {}, requ
28
29
  const mcpOnly = { generated: { cli: false, mcp: true } };
29
30
  const cliOnly = { generated: { cli: true, mcp: false } };
30
31
  export const CUSTOMER_OPERATIONS = [
32
+ operation("mailboxes.standby.get", "/api/cli/mailboxes/{mailbox}/standby", "GET", "mailboxes standby get", "oxygen_mailboxes_standby_get", "deliverability", "Read a mailbox's standby role, readiness blockers and replacements."),
33
+ operation("mailboxes.standby.set", "/api/cli/mailboxes/{mailbox}/standby", "PATCH", "mailboxes standby set", "oxygen_mailboxes_standby_set", "deliverability", "Set outreach or standby. Preview; apply with approved and returned plan. Standby holds pending outreach; existing warm-up continues. 0 credits.", { role: enumOf(["outreach", "standby"]), approved: bool(), plan: text() }, ["role"]),
34
+ operation("mailboxes.standby.pool", "/api/cli/mailboxes/standby", "GET", "mailboxes standby pool", "oxygen_mailboxes_standby_pool", "deliverability", "Read shared warm standby reserve, readiness policy and replacements."),
35
+ operation("mailboxes.standby.policy", "/api/cli/mailboxes/standby", "PATCH", "mailboxes standby policy", "oxygen_mailboxes_standby_policy", "deliverability", "Set required confirmed warm-up sending days (default 14); no subscription changes. 0 credits.", { minimum_days: int({ minimum: 1, maximum: 28 }) }, ["minimum_days"]),
36
+ operation("sequences.standby.get", "/api/cli/sequences/{sequence}/standby", "GET", "sequences standby get", "oxygen_sequences_standby_get", "sequences", "Read automatic mailbox replacement policy, ready reserve and replacements for a campaign."),
37
+ operation("sequences.standby.set", "/api/cli/sequences/{sequence}/standby", "PATCH", "sequences standby set", "oxygen_sequences_standby_set", "sequences", "Enable or disable automatic replacement after bounce protection pauses a mailbox. Preview first; apply with approved and returned plan. Shared reserves may change sender identity for uncontacted leads. Contacted leads keep their original inbox and hold follow-ups. 0 credits.", { enabled: bool(), approved: bool(), plan: text() }, ["enabled"]),
38
+ operation("chat.channel", "/api/cli/chat", "GET", "chat channel", "oxygen_chat_channel", "workspace", "Dev only: read or initialize this workspace's native support channel, persistent threads, unread counts and members; optional search matches messages.", {}, [], { contract: CHAT_CHANNEL_READ_CONTRACT, mutates: true }),
39
+ operation("chat.threads.create", "/api/cli/chat/threads", "POST", "chat threads create", "oxygen_chat_thread_create", "workspace", "Dev only: create a persistent native support thread; subject and body are customer-visible. No Plain or Slack transport.", {}, [], { contract: CHAT_THREAD_CREATE_CONTRACT }),
40
+ operation("chat.threads.get", "/api/cli/chat/threads/{thread_id}", "GET", "chat threads get", "oxygen_chat_thread_get", "workspace", "Dev only: read a native support thread, messages, members and release progress. Continue message history with the returned cursor.", {}, [], { contract: CHAT_THREAD_READ_CONTRACT }),
41
+ operation("chat.threads.reply", "/api/cli/chat/threads/{thread_id}/messages", "POST", "chat threads reply", "oxygen_chat_thread_reply", "workspace", "Dev only: reply in a native support thread with uploaded attachment IDs and member mentions. Public by default; internal notes require staff.", {}, [], { contract: CHAT_MESSAGE_CONTRACT }),
42
+ operation("chat.threads.update", "/api/cli/chat/threads/{thread_id}", "PATCH", "chat threads update", "oxygen_chat_thread_update", "workspace", "Staff-only. Dev only: update native support status, assignment, urgency, fixing-agent progress or fix evidence; release status is verified separately.", {}, [], { contract: CHAT_THREAD_PATCH_CONTRACT }),
43
+ operation("chat.threads.read", "/api/cli/chat/threads/{thread_id}/read", "POST", "chat threads read", "oxygen_chat_thread_read", "workspace", "Dev only: mark a native support thread read through the viewed cursor; omit cursor to mark all currently visible messages.", { cursor: text() }, [], { bodyRequired: true }),
44
+ operation("chat.admin.list", "/api/cli/admin/chat", "GET", "chat admin list", "oxygen_chat_admin_list", "workspace", "Staff-only. Dev only: list native customer support channels by urgency and customer significance, filtered by thread or agent state.", {}, [], { contract: CHAT_ADMIN_READ_CONTRACT }),
45
+ operation("chat.admin.channel", "/api/cli/admin/chat/{channel_id}", "GET", "chat admin channel", "oxygen_chat_admin_channel", "workspace", "Staff-only. Dev only: read a customer's native support channel, thread status and billing significance; optional search matches messages.", {}, [], { contract: CHAT_CHANNEL_READ_CONTRACT }),
46
+ operation("chat.release_check", "/api/cli/chat/threads/{thread_id}/release-check", "POST", "chat release-check", "oxygen_chat_release_check", "workspace", "Staff-only. Dev only: verify whether the thread's fix is deployed to dev or production, preserving stale evidence when verification is unavailable.", {}, [], { bodyRequired: true }),
47
+ operation("chat.files.upload", "/api/cli/chat/threads/{thread_id}/files", "POST", "chat files upload", "oxygen_chat_file_upload", "workspace", "Dev only: upload a file into a native support thread; attach the returned ID in a reply. CLI accepts --file; MCP accepts data_base64.", {}, [], { contract: CHAT_FILE_UPLOAD_CONTRACT, multipart: { contentField: "data_base64", nameField: "name", typeField: "content_type", fileField: "file" } }),
48
+ operation("chat.files.get", "/api/cli/chat/threads/{thread_id}/files/{file_id}", "GET", "chat files get", "oxygen_chat_file_get", "workspace", "Dev only: download exact bytes of a file in an authorized native support thread. CLI requires --output; MCP returns an authenticated file resource.", {}, [], { binary: { maxBytes: 10 * 1024 * 1024 } }),
31
49
  operation("activation.state", "/api/cli/activation/state", "GET", "activation state", "oxygen_activation_state", "workspace", "Read this workspace's onboarding activation state and next available actions.", {}, [], mcpOnly),
32
50
  operation("activation.checklist", "/api/cli/activation/checklist", "GET", "activation checklist", "oxygen_activation_checklist", "workspace", "Read this workspace's getting-started checklist: leads in a table, a first sequence, a connected sending account and a launched sequence, each with whether it is done and a link to do it; eligible says whether the sidebar shows it. Read-only, 0 credits."),
33
51
  operation("api_keys.list", "/api/cli/api-keys", "GET", "api-keys list", "oxygen_api_keys_list", "workspace", "List workspace API keys without revealing their tokens.", {}, [], mcpOnly),
@@ -36,14 +36,14 @@ export const GUIDED_ACTIVATION_ROUTINE_ACTIVATE_CONTRACT = obj({
36
36
  }, { required: ["purpose", "revision", "preview_token"] });
37
37
  export const GUIDED_ACTIVATION_OPERATIONS = [
38
38
  { id: "onboarding.activation.status", route: "/api/cli/onboarding/activation", method: "GET", contract: obj({}),
39
- cli: { path: ["onboarding", "activation", "status"], summary: "Resume your guided onboarding and native resource links. Enabled genuine dev only; current non-client workspace members." },
40
- mcp: { name: "oxygen_onboarding_activation_status", title: "Oxygen: Onboarding status", description: "Read your resumable guided onboarding and native links. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: false, spends: false },
39
+ cli: { path: ["onboarding", "activation", "status"], summary: "Resume your guided onboarding and native resource links. Requires enabled onboarding and current non-client membership." },
40
+ mcp: { name: "oxygen_onboarding_activation_status", title: "Oxygen: Onboarding status", description: "Read your resumable guided onboarding and native links. Requires enabled onboarding and current non-client membership.", pack: "agents" }, mutates: false, spends: false },
41
41
  { id: "onboarding.activation.start", route: "/api/cli/onboarding/activation", method: "POST", contract: GUIDED_ACTIVATION_START_CONTRACT,
42
42
  cli: { path: ["onboarding", "activation", "start"], summary: "Prepare your creator-only activation and Oxygent chat: Workspace shared, Personal creator-only. Free; no send or outreach." },
43
- mcp: { name: "oxygen_onboarding_activation_start", title: "Oxygen: Start onboarding record", description: "Prepare your creator-only activation with operating_path own_agent or oxygent. Oxygent binds its native Agent/chat: Workspace shared, Personal creator-only. Free; no turn, inference, provider or outreach dispatch. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: true, spends: false },
43
+ mcp: { name: "oxygen_onboarding_activation_start", title: "Oxygen: Start onboarding record", description: "Prepare your creator-only activation with operating_path own_agent or oxygent. Oxygent binds its native Agent/chat: Workspace shared, Personal creator-only. Free; no turn, inference, provider or outreach dispatch. Requires enabled onboarding and current non-client membership.", pack: "agents" }, mutates: true, spends: false },
44
44
  { id: "onboarding.activation.get", route: "/api/cli/onboarding/activation/{activationId}", method: "GET", parameters: ["activationId"], contract: obj({}),
45
45
  cli: { path: ["onboarding", "activation", "get"], summary: "Read your activation by ID and its native resource links." },
46
- mcp: { name: "oxygen_onboarding_activation_get", title: "Oxygen: Read onboarding record", description: "Read your activation by activationId. Only its creator can read it. Enabled genuine dev only; current non-client workspace members.", pack: "agents" }, mutates: false, spends: false },
46
+ mcp: { name: "oxygen_onboarding_activation_get", title: "Oxygen: Read onboarding record", description: "Read your activation by activationId. Only its creator can read it. Requires enabled onboarding and current non-client membership.", pack: "agents" }, mutates: false, spends: false },
47
47
  { id: "onboarding.activation.link", route: "/api/cli/onboarding/activation/{activationId}/resources", method: "POST", parameters: ["activationId"], contract: GUIDED_ACTIVATION_LINK_CONTRACT,
48
48
  cli: { path: ["onboarding", "activation", "link"], summary: "Link an existing native onboarding resource at the expected revision. Does not create, enroll or send." },
49
49
  mcp: { name: "oxygen_onboarding_activation_link", title: "Oxygen: Link onboarding resource", description: "Link an existing Knowledge page, First leads Table, pilot Sequence or LinkedIn sender to your activationId at revision. Native ownership and Sequence binding are verified; no create, enrollment or send.", pack: "agents" }, mutates: true, spends: false },
@@ -0,0 +1,120 @@
1
+ export declare const CHAT_STATUS: readonly ["todo", "snoozed", "done"];
2
+ export declare const CHAT_PRIORITY: readonly ["urgent", "high", "normal", "low"];
3
+ export declare const CHAT_AGENT_STAGE: readonly ["investigating", "fixing", "testing", "awaiting_release", "needs_human", "completed"];
4
+ export declare const CHAT_RELEASE_STATUS: readonly ["none", "dev", "prod"];
5
+ export declare const CHAT_CHANNEL_READ_CONTRACT: import("./schema.js").ContractObject<{
6
+ readonly search: import("./schema.js").StringProperty;
7
+ }, never>;
8
+ export declare const CHAT_THREAD_CREATE_CONTRACT: import("./schema.js").ContractObject<{
9
+ readonly subject: import("./schema.js").StringProperty;
10
+ readonly body: import("./schema.js").StringProperty;
11
+ readonly idempotency_key: import("./schema.js").StringProperty;
12
+ readonly context: {
13
+ readonly type: "object";
14
+ readonly additionalProperties: true;
15
+ };
16
+ }, "body" | "subject">;
17
+ export declare const CHAT_THREAD_READ_CONTRACT: import("./schema.js").ContractObject<{
18
+ readonly after: import("./schema.js").StringProperty;
19
+ readonly before: import("./schema.js").StringProperty;
20
+ readonly search: import("./schema.js").StringProperty;
21
+ readonly limit: {
22
+ type: "integer";
23
+ description?: string;
24
+ minimum?: number;
25
+ maximum?: number;
26
+ "x-clamp"?: true;
27
+ };
28
+ }, never>;
29
+ export declare const CHAT_MESSAGE_CONTRACT: import("./schema.js").ContractObject<{
30
+ readonly body: import("./schema.js").StringProperty;
31
+ readonly attachments: import("./schema.js").ArrayProperty<import("./schema.js").StringProperty>;
32
+ readonly mentions: import("./schema.js").ArrayProperty<import("./schema.js").StringProperty>;
33
+ readonly visibility: {
34
+ type: "string";
35
+ enum: readonly ("internal" | "public")[];
36
+ description?: string;
37
+ };
38
+ readonly idempotency_key: import("./schema.js").StringProperty;
39
+ }, "body">;
40
+ export declare const CHAT_THREAD_PATCH_CONTRACT: import("./schema.js").ContractObject<{
41
+ readonly status: {
42
+ type: "string";
43
+ enum: readonly ("done" | "snoozed" | "todo")[];
44
+ description?: string;
45
+ };
46
+ readonly waiting_for_customer: import("./schema.js").BooleanProperty;
47
+ readonly snoozed_until: {
48
+ oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
49
+ description?: string;
50
+ };
51
+ readonly assignee_email: {
52
+ oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
53
+ description?: string;
54
+ };
55
+ readonly priority: {
56
+ type: "string";
57
+ enum: readonly ("high" | "low" | "normal" | "urgent")[];
58
+ description?: string;
59
+ };
60
+ readonly agent: {
61
+ oneOf: [import("./schema.js").ContractObject<{
62
+ readonly id: import("./schema.js").StringProperty;
63
+ readonly name: import("./schema.js").StringProperty;
64
+ readonly stage: {
65
+ type: "string";
66
+ enum: readonly ("awaiting_release" | "completed" | "fixing" | "investigating" | "needs_human" | "testing")[];
67
+ description?: string;
68
+ };
69
+ readonly summary: {
70
+ oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
71
+ description?: string;
72
+ };
73
+ readonly session_url: {
74
+ oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
75
+ description?: string;
76
+ };
77
+ }, "id" | "name" | "stage">, import("./schema.js").NullProperty];
78
+ description?: string;
79
+ };
80
+ readonly release: import("./schema.js").ContractObject<{
81
+ readonly fix_sha: {
82
+ oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
83
+ description?: string;
84
+ };
85
+ readonly pr_url: {
86
+ oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
87
+ description?: string;
88
+ };
89
+ readonly required_roles: import("./schema.js").ArrayProperty<import("./schema.js").StringProperty>;
90
+ }, "fix_sha" | "pr_url" | "required_roles">;
91
+ }, never>;
92
+ export declare const CHAT_ADMIN_READ_CONTRACT: import("./schema.js").ContractObject<{
93
+ readonly search: import("./schema.js").StringProperty;
94
+ readonly status: {
95
+ type: "string";
96
+ enum: readonly ("done" | "snoozed" | "todo")[];
97
+ description?: string;
98
+ };
99
+ readonly priority: {
100
+ type: "string";
101
+ enum: readonly ("high" | "low" | "normal" | "urgent")[];
102
+ description?: string;
103
+ };
104
+ readonly assignee: import("./schema.js").StringProperty;
105
+ readonly agent_stage: {
106
+ type: "string";
107
+ enum: readonly ("awaiting_release" | "completed" | "fixing" | "investigating" | "needs_human" | "testing" | "unassigned")[];
108
+ description?: string;
109
+ };
110
+ readonly release: {
111
+ type: "string";
112
+ enum: readonly ("dev" | "none" | "prod")[];
113
+ description?: string;
114
+ };
115
+ }, never>;
116
+ export declare const CHAT_FILE_UPLOAD_CONTRACT: import("./schema.js").ContractObject<{
117
+ readonly name: import("./schema.js").StringProperty;
118
+ readonly content_type: import("./schema.js").StringProperty;
119
+ readonly data_base64: import("./schema.js").StringProperty;
120
+ }, "content_type" | "data_base64" | "name">;
@@ -0,0 +1,35 @@
1
+ import { arr, bool, enumOf, int, nullable, obj, str } from "./schema.js";
2
+ const text = () => str();
3
+ export const CHAT_STATUS = ["todo", "snoozed", "done"];
4
+ export const CHAT_PRIORITY = ["urgent", "high", "normal", "low"];
5
+ export const CHAT_AGENT_STAGE = ["investigating", "fixing", "testing", "awaiting_release", "needs_human", "completed"];
6
+ export const CHAT_RELEASE_STATUS = ["none", "dev", "prod"];
7
+ export const CHAT_CHANNEL_READ_CONTRACT = obj({ search: text() });
8
+ export const CHAT_THREAD_CREATE_CONTRACT = obj({
9
+ subject: str({ minLength: 1 }), body: str({ minLength: 1, maxLength: 20_000 }),
10
+ idempotency_key: text(), context: { type: "object", additionalProperties: true },
11
+ }, { required: ["subject", "body"] });
12
+ export const CHAT_THREAD_READ_CONTRACT = obj({
13
+ after: text(), before: text(), search: text(), limit: int({ minimum: 1, maximum: 100 }),
14
+ });
15
+ export const CHAT_MESSAGE_CONTRACT = obj({
16
+ body: str({ maxLength: 20_000 }), attachments: arr(text()), mentions: arr(text()),
17
+ visibility: enumOf(["public", "internal"], { description: "Public replies are customer-visible; internal notes require staff." }),
18
+ idempotency_key: text(),
19
+ }, { required: ["body"] });
20
+ export const CHAT_THREAD_PATCH_CONTRACT = obj({
21
+ status: enumOf(CHAT_STATUS), waiting_for_customer: bool(), snoozed_until: nullable(text()),
22
+ assignee_email: nullable(text()), priority: enumOf(CHAT_PRIORITY),
23
+ agent: nullable(obj({
24
+ id: str({ minLength: 1 }), name: str({ minLength: 1 }), stage: enumOf(CHAT_AGENT_STAGE),
25
+ summary: nullable(text()), session_url: nullable(text()),
26
+ }, { required: ["id", "name", "stage"] })),
27
+ release: obj({ fix_sha: nullable(text()), pr_url: nullable(text()), required_roles: arr(text()) }),
28
+ });
29
+ export const CHAT_ADMIN_READ_CONTRACT = obj({
30
+ search: text(), status: enumOf(CHAT_STATUS), priority: enumOf(CHAT_PRIORITY),
31
+ assignee: text(), agent_stage: enumOf(["unassigned", ...CHAT_AGENT_STAGE]), release: enumOf(CHAT_RELEASE_STATUS),
32
+ });
33
+ export const CHAT_FILE_UPLOAD_CONTRACT = obj({
34
+ name: str({ minLength: 1 }), content_type: str({ minLength: 1 }), data_base64: str({ minLength: 1 }),
35
+ }, { required: ["name", "content_type", "data_base64"] });
@@ -28,6 +28,15 @@ export declare const SENDER_WARMUP_CONTRACT: import("./schema.js").ContractObjec
28
28
  }, never>;
29
29
  export declare const LINKEDIN_WARMUP_PRESETS: readonly ["conservative", "standard", "fast"];
30
30
  export declare const SENDER_LIMITS_PATCH_CONTRACT: import("./schema.js").ContractObject<{
31
+ readonly publishing_approval: import("./schema.js").ContractObject<{
32
+ readonly mode: {
33
+ type: "string";
34
+ enum: readonly ("owner" | "workspace")[];
35
+ description?: string;
36
+ };
37
+ readonly owner_user_id: import("./schema.js").StringProperty;
38
+ readonly delegate_user_ids: import("./schema.js").ArrayProperty<import("./schema.js").StringProperty>;
39
+ }, "mode">;
31
40
  readonly limits: import("./schema.js").ContractObject<Record<"action_spacing_jitter_seconds" | "api_ingest_reads_per_day" | "api_reads_per_day" | "comments_per_day" | "endorsements_per_day" | "follows_per_day" | "inmails_per_day" | "inmails_per_month" | "interactive_min_spacing_seconds" | "interactive_sends_per_hour" | "interactive_spacing_jitter_seconds" | "invites_per_day" | "invites_per_week" | "likes_per_day" | "messages_ingest_reads_per_day" | "messages_per_day" | "messages_reads_per_day" | "min_action_spacing_seconds" | "network_backfill_reads_per_day" | "network_delta_reads_per_day" | "posts_per_day" | "profile_lookups_per_day" | "profile_views_per_day" | "relations_ingest_reads_per_day" | "relations_reads_per_day" | "sales_nav_search_results_per_day" | "searches_per_day" | "total_actions_per_day" | "total_reads_per_day" | "withdrawals_per_day", {
32
41
  type: "integer";
33
42
  description?: string;
@@ -57,6 +57,11 @@ export const SENDER_WARMUP_CONTRACT = obj({
57
57
  });
58
58
  export const LINKEDIN_WARMUP_PRESETS = ["conservative", "standard", "fast"];
59
59
  export const SENDER_LIMITS_PATCH_CONTRACT = obj({
60
+ publishing_approval: obj({
61
+ mode: enumOf(["workspace", "owner"]),
62
+ owner_user_id: str({ description: "Workspace user UUID; required for owner mode." }),
63
+ delegate_user_ids: arr(str(), { maxItems: 50, description: "Workspace user UUIDs." }),
64
+ }, { required: ["mode"], description: "Admin sets initial owner; only owner manages delegates/policy. Free." }),
60
65
  limits: SENDER_LIMITS_CONTRACT,
61
66
  working_hours: SENDER_WORKING_HOURS_CONTRACT,
62
67
  warmup: SENDER_WARMUP_CONTRACT,
@@ -82,7 +87,7 @@ export const SENDER_LIMITS_SET_OPERATION = {
82
87
  mcp: {
83
88
  name: "oxygen_senders_limits_set",
84
89
  title: "Oxygen: Set LinkedIn Account Limits",
85
- description: "Updates per-account daily limits for sends (invites/messages/InMails) AND reads (relations lists, message history, searches, profile views), working hours, and the warm-up ramp (enable/disable/anchor). Values are clamped to hard maximums; above the caution levels restriction risk rises. LinkedIn actions run only inside working hours.",
90
+ description: "Update account send/read caps, working hours, warm-up or profile approvals. Limits clamp to hard maxima; above caution levels restriction risk rises. Actions obey working hours.",
86
91
  pack: "deliverability",
87
92
  },
88
93
  mutates: true,