@oxygen-agent/cli 1.1014.1600 → 1.1014.1763

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 (127) hide show
  1. package/README.md +1 -1
  2. package/dist/command-families/program.js +4 -0
  3. package/dist/command-manifest.js +18 -3
  4. package/dist/command-support.js +1 -1
  5. package/dist/commands/admin.js +82 -1
  6. package/dist/commands/billing.js +1 -1
  7. package/dist/commands/copilot.js +29 -7
  8. package/dist/commands/custom-integrations.js +13 -0
  9. package/dist/commands/domains.js +7 -0
  10. package/dist/commands/email-assets.d.ts +3 -0
  11. package/dist/commands/email-assets.js +78 -0
  12. package/dist/commands/email-templates.d.ts +3 -0
  13. package/dist/commands/email-templates.js +101 -0
  14. package/dist/commands/inbox.js +1 -1
  15. package/dist/commands/integrations.js +5 -1
  16. package/dist/commands/mailboxes.js +26 -6
  17. package/dist/commands/managed-inboxes.js +2 -2
  18. package/dist/commands/senders.js +4 -4
  19. package/dist/commands/sequences.js +47 -12
  20. package/dist/commands/subscriptions.d.ts +2 -4
  21. package/dist/commands/subscriptions.js +297 -51
  22. package/dist/credential-storage/store.d.ts +10 -0
  23. package/dist/credential-storage/store.js +282 -0
  24. package/dist/credentials.d.ts +1 -3
  25. package/dist/credentials.js +3 -275
  26. package/dist/mailbox-fleet-output.js +11 -2
  27. package/dist/sequence-start-at.d.ts +7 -0
  28. package/dist/sequence-start-at.js +21 -0
  29. package/dist/sequences-brand-kit-command.d.ts +28 -0
  30. package/dist/sequences-brand-kit-command.js +83 -0
  31. package/dist/sequences-content-commands.d.ts +28 -0
  32. package/dist/sequences-content-commands.js +176 -0
  33. package/dist/sequences-newsletter-commands.d.ts +6 -0
  34. package/dist/sequences-newsletter-commands.js +108 -0
  35. package/dist/sequences-test-send-command.d.ts +3 -0
  36. package/dist/sequences-test-send-command.js +47 -0
  37. package/dist/workspace-placement-commands.js +3 -2
  38. package/node_modules/@oxygen/formula/dist/evaluate.js +2 -4
  39. package/node_modules/@oxygen/formula/dist/expression.d.ts +7 -5
  40. package/node_modules/@oxygen/formula/dist/expression.js +26 -4
  41. package/node_modules/@oxygen/formula/dist/formula-functions.d.ts +6 -0
  42. package/node_modules/@oxygen/formula/dist/formula-functions.js +55 -0
  43. package/node_modules/@oxygen/shared/dist/billing.d.ts +2 -2
  44. package/node_modules/@oxygen/shared/dist/billing.js +5 -7
  45. package/node_modules/@oxygen/shared/dist/bulk-email-pacing.d.ts +105 -0
  46. package/node_modules/@oxygen/shared/dist/bulk-email-pacing.js +125 -0
  47. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +29 -0
  48. package/node_modules/@oxygen/shared/dist/capability-discovery.js +236 -18
  49. package/node_modules/@oxygen/shared/dist/clay-migration-auth-state.d.ts +7 -1
  50. package/node_modules/@oxygen/shared/dist/clay-migration-auth-state.js +6 -5
  51. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +1 -0
  52. package/node_modules/@oxygen/shared/dist/contracts/clay-migration.d.ts +40 -1
  53. package/node_modules/@oxygen/shared/dist/contracts/clay-migration.js +60 -6
  54. package/node_modules/@oxygen/shared/dist/contracts/copilot-attachment-read.d.ts +24 -0
  55. package/node_modules/@oxygen/shared/dist/contracts/copilot-attachment-read.js +7 -0
  56. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +2 -1
  57. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +6 -1
  58. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +1 -1
  59. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +1 -1
  60. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +2 -2
  61. package/node_modules/@oxygen/shared/dist/email-brand-kit.d.ts +108 -0
  62. package/node_modules/@oxygen/shared/dist/email-brand-kit.js +214 -0
  63. package/node_modules/@oxygen/shared/dist/email-classification-scope.d.ts +35 -0
  64. package/node_modules/@oxygen/shared/dist/email-classification-scope.js +40 -0
  65. package/node_modules/@oxygen/shared/dist/email-delivery.d.ts +10 -0
  66. package/node_modules/@oxygen/shared/dist/email-delivery.js +14 -0
  67. package/node_modules/@oxygen/shared/dist/email-documents.d.ts +61 -0
  68. package/node_modules/@oxygen/shared/dist/email-documents.js +143 -0
  69. package/node_modules/@oxygen/shared/dist/email-dsn.js +52 -10
  70. package/node_modules/@oxygen/shared/dist/email-html-sanitize.d.ts +23 -0
  71. package/node_modules/@oxygen/shared/dist/email-html-sanitize.js +96 -0
  72. package/node_modules/@oxygen/shared/dist/email-subscriptions.d.ts +63 -1
  73. package/node_modules/@oxygen/shared/dist/email-subscriptions.js +149 -1
  74. package/node_modules/@oxygen/shared/dist/email-unsubscribe-token.d.ts +41 -2
  75. package/node_modules/@oxygen/shared/dist/email-unsubscribe-token.js +50 -1
  76. package/node_modules/@oxygen/shared/dist/native-chat.d.ts +9 -0
  77. package/node_modules/@oxygen/shared/dist/native-chat.js +12 -1
  78. package/node_modules/@oxygen/shared/dist/newsletter-analytics.d.ts +90 -0
  79. package/node_modules/@oxygen/shared/dist/newsletter-analytics.js +28 -0
  80. package/node_modules/@oxygen/shared/dist/newsletter-test-send.d.ts +20 -0
  81. package/node_modules/@oxygen/shared/dist/newsletter-test-send.js +46 -0
  82. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +11 -0
  83. package/node_modules/@oxygen/shared/dist/object-storage.js +12 -0
  84. package/node_modules/@oxygen/shared/dist/onboarding-research.d.ts +9 -2
  85. package/node_modules/@oxygen/shared/dist/onboarding-research.js +9 -2
  86. package/node_modules/@oxygen/shared/dist/plan-band.d.ts +9 -0
  87. package/node_modules/@oxygen/shared/dist/plan-band.js +17 -0
  88. package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +4 -2
  89. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +12 -1
  90. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +3 -0
  92. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +5 -0
  93. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +5 -0
  94. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +29 -0
  95. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +29 -0
  96. package/node_modules/@oxygen/shared/dist/recipes.d.ts +2 -0
  97. package/node_modules/@oxygen/shared/dist/recipes.js +13 -0
  98. package/node_modules/@oxygen/shared/dist/sending-domain-faults.d.ts +21 -0
  99. package/node_modules/@oxygen/shared/dist/sending-domain-faults.js +53 -0
  100. package/node_modules/@oxygen/shared/dist/sending-domain-rollout.d.ts +30 -0
  101. package/node_modules/@oxygen/shared/dist/sending-domain-rollout.js +75 -0
  102. package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +10 -1
  103. package/node_modules/@oxygen/shared/dist/sequence-failures.js +68 -6
  104. package/node_modules/@oxygen/shared/dist/sequence-lifecycle.d.ts +34 -3
  105. package/node_modules/@oxygen/shared/dist/sequence-lifecycle.js +57 -4
  106. package/node_modules/@oxygen/shared/dist/sequence-template.d.ts +17 -0
  107. package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
  108. package/node_modules/@oxygen/shared/dist/sequences.d.ts +41 -0
  109. package/node_modules/@oxygen/shared/dist/sequences.js +86 -4
  110. package/node_modules/@oxygen/shared/dist/ses-identity-policy.d.ts +19 -0
  111. package/node_modules/@oxygen/shared/dist/ses-identity-policy.js +54 -0
  112. package/node_modules/@oxygen/shared/dist/skill-play-routing.d.ts +22 -0
  113. package/node_modules/@oxygen/shared/dist/skill-play-routing.js +75 -0
  114. package/node_modules/@oxygen/shared/dist/skill-plays.generated.d.ts +5 -0
  115. package/node_modules/@oxygen/shared/dist/skill-plays.generated.js +269 -0
  116. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +7 -0
  117. package/node_modules/@oxygen/shared/dist/spend-safety.js +7 -0
  118. package/node_modules/@oxygen/shared/dist/teammates.js +2 -1
  119. package/node_modules/@oxygen/shared/dist/tool-call-arguments.d.ts +24 -0
  120. package/node_modules/@oxygen/shared/dist/tool-call-arguments.js +68 -0
  121. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +15 -1
  122. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  123. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  124. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +2 -2
  125. package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +3 -0
  126. package/node_modules/@oxygen/shared/dist/workspace-placement.js +11 -0
  127. package/package.json +2 -2
@@ -2,6 +2,8 @@ import { findEnrichmentIntentByAlias } from "./enrichment-intents.js";
2
2
  import { networkMiningEnabled } from "./network-mining.js";
3
3
  import { nativeChatEnabled, withNativeChatDiscovery } from "./native-chat-discovery.js";
4
4
  import { RECIPE_PRIMITIVES } from "./recipes.js";
5
+ import { matchSkillPlay } from "./skill-play-routing.js";
6
+ export { matchSkillPlay, SKILL_PLAY_TRIGGERS } from "./skill-play-routing.js";
5
7
  // One compact semantic index covers every top-level /api/cli endpoint section.
6
8
  // It is navigation, never execution: live schemas, prices, readiness, and
7
9
  // approval enforcement remain on the exact command/tool selected from here.
@@ -26,7 +28,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
26
28
  posture: "workspace_write",
27
29
  gatewayTools: ["oxygen_visual_source_create", "oxygen_visual_render_preview", "oxygen_visual_render_get", "oxygen_visual_image_view"],
28
30
  gatewayCommands: ["visuals source-create", "visuals render-preview", "visuals render-get", "visuals download"],
29
- skills: ["oxygen-knowledge"],
31
+ skills: ["oxygen-linkedin-content", "oxygen-knowledge"],
30
32
  endpointSections: ["visuals"],
31
33
  intentTerms: ["infographic", "gtm flow image", "render html", "visual design", "carousel pages", "svg", "png", "pdf export", "graphic designer"],
32
34
  },
@@ -114,8 +116,8 @@ export const OXYGEN_CAPABILITY_ROUTES = [
114
116
  notFor: "Primitive run diagnosis when no support report is needed; inspect Observability first.",
115
117
  execution: "Start or continue a hosted support conversation, optionally file structured metadata, and return its deep link; never send a customer reply automatically.",
116
118
  posture: "workspace_write",
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"],
119
+ gatewayTools: ["oxygen_feedback", "oxygen_support_ticket_create", "oxygen_support_tickets_list", "oxygen_chat_threads_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"],
120
+ gatewayCommands: ["feedback", "support chat", "support file", "support list", "chat threads list", "chat channel", "chat threads get", "chat threads create", "chat threads reply", "chat files upload", "chat files get", "chat threads read"],
119
121
  skills: ["oxygen-feedback"],
120
122
  endpointSections: ["feedback", "support", "chat"],
121
123
  intentTerms: ["support", "feedback", "bug report", "feature request", "report issue", "ticket", "complaint", "support chat", "support thread", "chat attachment"],
@@ -298,7 +300,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
298
300
  primitive: "signals",
299
301
  owns: "Durable typed pulse, engagement, and product events resolved onto records.",
300
302
  notFor: "Message bodies, post artifacts, or ad-hoc provider webhooks as private automation state.",
301
- execution: "Read the hosted signal stream and type registry; tag a registered type with tags attach. Feed subscriptions, Sequences, or Workflows.",
303
+ execution: "Read the hosted signal stream and type registry; tag a registered type with tags attach. Feed subscriptions, Sequences, or Workflows. A visit or view the user reports goes on the person with oxygen_crm_activity_log (activity_type website_visit, direction inbound, live), after creating a missing person with oxygen_crm_record_assert (free, no approval); oxygen_crm_signal_record is for a provider webhook.",
302
304
  posture: "workspace_write",
303
305
  gatewayTools: ["oxygen_signals_list", "oxygen_signals_leads_today", "oxygen_signals_registry", "oxygen_tags_attach"],
304
306
  gatewayCommands: ["signals list", "signals leads-today", "signals registry", "tags attach"],
@@ -375,7 +377,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
375
377
  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
378
  gatewayCommands: ["sequences send", "sequences create", "sequences enroll", "sequences start", "sequences list", "sequences update", "sequences get", "sequences push-external", "sequences pause-company"],
377
379
  skills: ["oxygen-sequencer"],
378
- endpointSections: ["senders", "sequencer", "sequences", "spintax", "subscriptions", "suppressions", "voice"],
380
+ endpointSections: ["email-assets", "email-documents", "senders", "sequencer", "sequences", "spintax", "subscriptions", "suppressions", "voice"],
379
381
  // "sender identity", "sender profile" and "phone number" are here because a
380
382
  // blind-user eval (2026-08-09) asked for "our sender identity" and got routed
381
383
  // to workspace-access — "identity" matched the AUTH sense. The nouns a user
@@ -398,12 +400,12 @@ export const OXYGEN_CAPABILITY_ROUTES = [
398
400
  posture: "external_write",
399
401
  gatewayTools: ["oxygen_publishing_posts_create", "oxygen_publishing_posts_list", "oxygen_publishing_posts_approve", "oxygen_ugc_get"],
400
402
  gatewayCommands: ["publishing posts create", "publishing posts list", "publishing posts approve", "ugc programs list", "ugc programs duplicate", "ugc programs archive"],
401
- skills: ["oxygen-publishing", "oxygen-ugc"],
403
+ skills: ["oxygen-publishing", "oxygen-linkedin-content", "oxygen-ugc"],
402
404
  endpointSections: ["publishing", "ugc"],
403
405
  // "remove program" / "delete program" landed on route: null in a blind eval
404
406
  // (2026-09-10); a program is a UGC noun here, and its lifecycle verbs must
405
407
  // resolve to the group that owns `ugc programs archive|delete|duplicate`.
406
- intentTerms: ["publish", "publishing", "schedule post", "content calendar", "posting calendar", "approve post", "social publishing", "video cover", "video thumbnail", "cover image", "post preview", "ugc", "creator program", "user generated content", "creator voice", "sponsored creator", "ugc program", "archive program", "delete program", "remove program", "duplicate program", "copy program"],
408
+ intentTerms: ["publish", "publishing", "schedule post", "content calendar", "posting calendar", "approve post", "social publishing", "video cover", "video thumbnail", "cover image", "post preview", "ugc", "creator program", "user generated content", "creator voice", "sponsored creator", "ugc program", "archive program", "delete program", "remove program", "duplicate program", "copy program", "write posts", "write a post", "post ideas", "content pillars", "hook", "linkedin content"],
407
409
  },
408
410
  {
409
411
  id: "workflows",
@@ -411,7 +413,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
411
413
  primitive: "workflows",
412
414
  owns: "Deterministic hosted automation definitions, versions, triggers, branching, retries, approvals, modes, and durable runs.",
413
415
  notFor: "Local cron, client-side execution, adaptive Agent goals, or outreach cadence already owned by Sequences.",
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.",
416
+ 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. To change existing rows, a tool node runs oxygen_tables_query with filters, a loop goes over steps.<node>.output.rows (body edge source_handle loop_body, plus an edge back to the loop), and the body calls oxygen_rows_update with row_id loop.each.item._row_id and values. A fixed change on a schedule stays a Workflow: draft and lint it rather than offering a formula column instead; a routine is for checks that need judgment. A cron time is in the person's time zone: set trigger.timezone, and ask for it in your first question when you do not know it. 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. If apply is refused, nothing was saved: say so once, never call it saved or a draft in OXYGEN, and never offer another way around the refusal (such as writing the rows directly). Then say in plain words what it would do once saved, and include the full manifest below that: show its manifest every time, since it exists only in your reply.",
415
417
  posture: "mixed",
416
418
  gatewayTools: ["oxygen_workflows_schema", "oxygen_workflows_lint", "oxygen_workflows_apply", "oxygen_workflows_call", "oxygen_workflows_tail"],
417
419
  gatewayCommands: ["workflows schema", "workflows lint", "workflows apply", "workflows call", "workflows tail"],
@@ -459,7 +461,11 @@ export const OXYGEN_CAPABILITY_ROUTES = [
459
461
  gatewayCommands: ["companies search plan", "people search plan", "tools search", "find company"],
460
462
  skills: ["oxygen-sourcing", "oxygen-enrichment"],
461
463
  endpointSections: ["companies", "find", "lead-sourcing", "people", "search", "sourcing", "tools", "verify"],
462
- intentTerms: ["find companies", "find people", "source leads", "build a list", "tam", "prospect", "provider", "tool catalog", "find email", "find phone", "local businesses", "local business", "google maps", "storefronts"],
464
+ // Contacts at accounts are a people search: "merge two lists then find
465
+ // contacts" scored Records on "merge"/"contact", and "who we should reach at
466
+ // each company ... cost" scored Billing on "cost".
467
+ intentTerms: ["find companies", "find people", "source leads", "build a list", "tam", "prospect", "provider", "tool catalog", "find email", "find phone", "local businesses", "local business", "google maps", "storefronts", "find contacts", "contacts per account", "contacts per company", "contacts at each", "reach at each", "people at each"],
468
+ negativeTerms: ["in my crm", "in our crm", "in the crm", "existing contacts"],
463
469
  },
464
470
  {
465
471
  id: "linkedin-public-research",
@@ -486,7 +492,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
486
492
  posture: "mixed",
487
493
  gatewayTools: ["oxygen_integrations_list", "oxygen_integrations_connect", "oxygen_integrations_list_actions"],
488
494
  gatewayCommands: ["integrations list", "integrations connect", "custom-integrations list"],
489
- skills: ["oxygen-tables", "oxygen-gtm"],
495
+ skills: ["oxygen-custom-http", "oxygen-tables", "oxygen-gtm"],
490
496
  endpointSections: ["custom-integrations", "integrations"],
491
497
  intentTerms: ["integration", "integrations", "connect provider", "oauth", "byok", "bring your own key", "provider key", "provider connection", "custom http", "custom integration", "connection readiness"],
492
498
  },
@@ -591,6 +597,19 @@ export function inferCapabilityRoute(query) {
591
597
  const normalized = normalizeIntent(query);
592
598
  if (!normalized)
593
599
  return null;
600
+ if (/\b(custom http|custom integrations?|own (?:rest|http) api)\b/.test(normalized)) {
601
+ const integrations = ROUTE_BY_ID.get("connected-integrations");
602
+ if (integrations) {
603
+ const removal = /\b(remov\w*|delet\w*|disconnect\w*|cleanup|clean up|throwaway|disposable)\b/.test(normalized);
604
+ const tools = removal ? ["oxygen_custom_integrations_remove", "oxygen_custom_integrations_list"] : ["oxygen_custom_integrations_list", "oxygen_custom_integrations_apply", "oxygen_custom_integration_connect"];
605
+ const commands = removal ? ["custom-integrations remove", "custom-integrations list"] : ["custom-integrations list", "custom-integrations apply", "custom-integrations connect"];
606
+ return {
607
+ card: { ...integrations, gatewayTools: tools, gatewayCommands: commands, skills: ["oxygen-custom-http"], execution: removal ? "Preview the exact custom HTTP connection and tools, then approve the returned plan_hash. Caller definitions and history remain, but bound callers stop working; provider-side key revocation is separate." : "Read oxygen-custom-http for the manifest schema and auth types. Apply registers catalog tools without a provider call; connect stores credentials separately. Preview a tool request before approved live execution." },
608
+ recommendedTools: tools,
609
+ recommendedCommands: commands,
610
+ };
611
+ }
612
+ }
594
613
  if (/\b(mailbox(?:es)?|inbox(?:es)?)\b/.test(normalized) && /\b(standby|reserve|replacement|replace|burn(?:ed|t)?)\b/.test(normalized)) {
595
614
  const campaign = /\b(campaign|sequence|cadence)\b/.test(normalized);
596
615
  const card = ROUTE_BY_ID.get(campaign ? "sequences" : "sending-infrastructure") ?? ROUTE_BY_PRIMITIVE.get("sequences");
@@ -624,7 +643,10 @@ export function inferCapabilityRoute(query) {
624
643
  // Card scoring stays the owner of routing; the catalogue only breaks a
625
644
  // no-match tie ("get the funding round for these accounts" names no card
626
645
  // term) and decorates the data cards it belongs to.
627
- const card = explicit ?? highestScoringRoute(normalized) ?? (catalogRow ? cardForCatalogueIntent(catalogRow) : null);
646
+ // A feature-skill play the ask matches votes for the cards its skill owns and,
647
+ // when the chosen card lists that skill, travels on the route as the file to open.
648
+ const play = matchSkillPlay(normalized) ?? sourcingPlaybookFor(normalized);
649
+ const card = explicit ?? highestScoringRoute(normalized, play?.skill) ?? (catalogRow ? cardForCatalogueIntent(catalogRow) : null);
628
650
  if (!card)
629
651
  return null;
630
652
  const recommendations = recommendationsFor(card, normalized);
@@ -634,7 +656,7 @@ export function inferCapabilityRoute(query) {
634
656
  // Sourcing filters belong to the planner, not per-company enrichment.
635
657
  const catalogue = catalogRow && CATALOGUE_CARD_IDS.has(card.id) && !isCompanySourcingIntent(normalized)
636
658
  ? catalogueRecommendations(catalogRow, card) : null;
637
- return withRecordBrief({
659
+ return withSkillPlay(withRecordBrief({
638
660
  card: routeCardFor(card, normalized),
639
661
  recommendedTools: recommendations.tools,
640
662
  recommendedCommands: recommendations.commands,
@@ -643,7 +665,30 @@ export function inferCapabilityRoute(query) {
643
665
  ...(catalogue && catalogRow
644
666
  ? { catalogIntent: { id: catalogRow.id, label: catalogRow.label, status: catalogRow.status, group: catalogRow.group, tools: catalogue.tools, commands: catalogue.commands, exactCommand: catalogue.exactCommand, exactMcpCall: catalogue.exactMcpCall } }
645
667
  : {}),
646
- }, networkMiningEnabled(runtimeEnv()));
668
+ }, networkMiningEnabled(runtimeEnv())), play);
669
+ }
670
+ /**
671
+ * Contacts at accounts has no play of its own: oxygen-sourcing's SKILL.md sends
672
+ * "merging sources, setting contacts per account" to its playbook reference,
673
+ * so the route points there (2026-10-22 suite: the merge-then-contacts case
674
+ * missed two contacts per account and the second-source rule without it).
675
+ * Contacts already in the CRM stay with Records.
676
+ */
677
+ function sourcingPlaybookFor(query) {
678
+ return /\b(find contacts|contacts (per|at each) (account|company)|reach at each|people at each)\b/.test(query)
679
+ && !/\b(crm|existing contacts)\b/.test(query)
680
+ ? { skill: "oxygen-sourcing", slug: "playbook", file: "references/playbook.md" }
681
+ : null;
682
+ }
683
+ /** The play rides only on a card that lists its skill, and that skill is named first. */
684
+ function withSkillPlay(route, play) {
685
+ if (!play || !route.card.skills.includes(play.skill))
686
+ return route;
687
+ return {
688
+ ...route,
689
+ card: { ...route.card, skills: [play.skill, ...route.card.skills.filter((skill) => skill !== play.skill)] },
690
+ play,
691
+ };
647
692
  }
648
693
  /**
649
694
  * A data-type ask ("tech stack", "decision makers at each account", "funding
@@ -734,12 +779,14 @@ export const OXYGEN_PRODUCT_SKILL_AUDIENCES = Object.freeze({
734
779
  "oxygen-deliverability": "workspace",
735
780
  "oxygen-sequencer": "workspace",
736
781
  "oxygen-publishing": "workspace",
782
+ "oxygen-linkedin-content": "workspace",
737
783
  "oxygen-signals": "workspace",
738
784
  "oxygen-table-tidy": "workspace",
739
785
  "oxygen-diagnostics": "workspace",
740
786
  "oxygen-feedback": "workspace",
741
787
  "oxygen-clay-migration": "workspace",
742
788
  "oxygen-ugc": "workspace",
789
+ "oxygen-custom-http": "workspace",
743
790
  // Retired (RETIRED_PRODUCT_SKILLS): readable for existing pins, never served.
744
791
  "oxygen-playbooks": "workspace",
745
792
  "oxygen-visual-design": "workspace",
@@ -803,6 +850,8 @@ export function serializeCapabilityRoute(route, surfaceOrOptions = "cli", option
803
850
  return null;
804
851
  const nativeChat = (typeof surfaceOrOptions === "string" ? options.nativeChat : surfaceOrOptions.nativeChat) ?? nativeChatEnabled(runtimeEnv());
805
852
  route = withNativeChatDiscovery(route, nativeChat);
853
+ const play = route.play && (isPublicCapabilitySkill(route.play.skill) || visibleSkills?.includes(route.play.skill))
854
+ ? route.play : null;
806
855
  return {
807
856
  capability_id: route.card.id,
808
857
  layer: route.card.layer,
@@ -812,6 +861,15 @@ export function serializeCapabilityRoute(route, surfaceOrOptions = "cli", option
812
861
  execution: route.card.execution,
813
862
  posture: route.card.posture,
814
863
  skills: route.card.skills.filter((name) => isPublicCapabilitySkill(name) || visibleSkills?.includes(name)),
864
+ // The play to open before planning, in both read forms like `hydrate` below.
865
+ ...(play ? {
866
+ play: {
867
+ skill: play.skill,
868
+ file: play.file,
869
+ read_cli: `oxygen skills get ${play.skill} --file ${play.file} --json`,
870
+ read_mcp: { tool: "oxygen_skills_get", arguments: { name: play.skill, file: play.file } },
871
+ },
872
+ } : {}),
815
873
  endpoint_sections: route.card.endpointSections,
816
874
  recommended_commands: route.recommendedCommands,
817
875
  recommended_tools: route.recommendedTools,
@@ -1149,6 +1207,20 @@ function readInspectionRecommendation(query) {
1149
1207
  if (action)
1150
1208
  return null;
1151
1209
  const stored = positive.replace(/\bwithout\s+(?:changing|approving|rejecting|publishing|running|executing|refreshing)\b(?:\s+(?:widget\s+)?data\b)?/g, "");
1210
+ // A daily cap is context for an inbox inspection, not a recurring job or a
1211
+ // request to change its volume. Keep explicit writes and other owners intact.
1212
+ const mailboxInspection = stored.replace(/\bdaily (?:(?:campaign|sending|email) )?(?:limits?|caps?)\b/g, "sending limit");
1213
+ if (/\b(?:mailbox(?:es)?|inbox(?:es)?|email accounts?|email senders?)\b/.test(stored)
1214
+ && /\b(?:health|reputation|rejections?|bounc\w*|deliverability|dsn|ndr)\b/.test(stored)
1215
+ && !isHostedWorkflowIntent(mailboxInspection)
1216
+ && !isSecondPartyDecisionIntent(stored)
1217
+ && !/\b(?:linkedin|whatsapp|sequences?|campaigns?|tables?|columns?|crm|posts?|messages?|conversations?|threads?|dns|spf|dkim|dmarc|verify|verification|create|write|edit|update|change|delete|remove|archive|save|approve|reject|publish|run|running|execute|send|refresh|launch|enable|disable|apply|import|sync|schedule|enroll|add|set|pause|resume|test)\b/.test(mailboxInspection))
1218
+ return {
1219
+ capabilityId: "sending-infrastructure",
1220
+ tools: ["oxygen_mailboxes_health", "oxygen_mailboxes_get", "oxygen_mailboxes_deliverability_evidence"],
1221
+ commands: ["mailboxes health", "mailboxes get", "mailboxes deliverability evidence"],
1222
+ execution: "Read stored reputation, measurement freshness and seven-day rejection/send evidence with oxygen_mailboxes_health. Its mailboxes selector accepts inbox ids or addresses and defaults to full evidence; fleet totals and coverage stay unfiltered. CLI: mailboxes health --mailboxes <id-or-address> --json. Read oxygen_mailboxes_get for today's effective cap; deliverability evidence adds stored placement and domain checks. These reads cost 0 credits and do not sync providers or change settings. External MCP uses oxygen_capabilities_read; hydrate the selected tool's schema before calling.",
1223
+ };
1152
1224
  // A trigger diagnosis needs the live definition, not run tailing or a call.
1153
1225
  // Keep execution, authoring and run-history requests with their own owners.
1154
1226
  if (/\bworkflows?\b/.test(stored)
@@ -1247,8 +1319,11 @@ function isPartnerBonusReadIntent(query) {
1247
1319
  /** The card a route reports: Publishing's own, or its UGC view for a creator-program ask. */
1248
1320
  function routeCardFor(card, query) {
1249
1321
  const readOnly = readInspectionRecommendation(query);
1250
- if (readOnly?.capabilityId === card.id)
1251
- return { ...card, posture: "read_only", execution: readOnly.execution };
1322
+ if (readOnly?.capabilityId === card.id) {
1323
+ const skill = preferredSkillForIntent(card, query);
1324
+ return { ...card, posture: "read_only", execution: readOnly.execution,
1325
+ ...(skill ? { skills: [skill, ...card.skills.filter(name => name !== skill)] } : {}) };
1326
+ }
1252
1327
  const placementGuide = card.id === "sending-infrastructure" ? placementGuideRecommendation(query) : null;
1253
1328
  if (placementGuide)
1254
1329
  card = { ...card, posture: "read_only", execution: placementGuide.execution };
@@ -1280,6 +1355,10 @@ function routeCardFor(card, query) {
1280
1355
  return { ...card, execution: feed.execution };
1281
1356
  if (card.primitive === "publishing" && isUgcIntent(query))
1282
1357
  return { ...card, ...UGC_ROUTE_VIEW };
1358
+ if (card.primitive === "publishing" && isPostDraftIntent(query))
1359
+ return { ...card, execution: POST_DRAFT_EXECUTION };
1360
+ if (card.primitive === "signals" && isSignalCaptureIntent(query))
1361
+ return { ...card, execution: SIGNAL_CAPTURE_EXECUTION, skills: ["oxygen-signals", "oxygen-crm"] };
1283
1362
  const contactFind = card.id === "sourcing-and-provider-tools" ? oneShotContactFindingRecommendation(query) : null;
1284
1363
  if (contactFind)
1285
1364
  card = { ...card, execution: contactFind.execution };
@@ -1320,6 +1399,8 @@ function routeCardFor(card, query) {
1320
1399
  card = { ...card, posture: "read_only", execution: CANONICAL_COPY_READ_EXECUTION };
1321
1400
  else if (card.id === "knowledge-graph" && isWorkspaceVoiceIntent(query))
1322
1401
  card = { ...card, execution: WORKSPACE_VOICE_EXECUTION };
1402
+ if (card.primitive === "agents" && isAdaptiveWatchIntent(query))
1403
+ card = { ...card, execution: ADAPTIVE_WATCH_EXECUTION };
1323
1404
  if (card.primitive === "workflows" && isRecurringCompanyMonitoringIntent(query))
1324
1405
  card = {
1325
1406
  ...card,
@@ -1361,6 +1442,12 @@ function routeCardFor(card, query) {
1361
1442
  const removal = card.primitive === "tables" ? tableRowRemovalRecommendation(query) : null;
1362
1443
  if (removal)
1363
1444
  card = { ...card, posture: "workspace_write", execution: removal.execution };
1445
+ const insert = card.primitive === "tables" ? tableRowInsertRecommendation(query) : null;
1446
+ if (insert)
1447
+ card = { ...card, posture: "workspace_write", execution: insert.execution };
1448
+ const update = card.primitive === "tables" ? tableRowUpdateRecommendation(query) : null;
1449
+ if (update)
1450
+ card = { ...card, posture: "workspace_write", execution: update.execution };
1364
1451
  const sent = card.id === "connected-linkedin" ? sentLinkedInInvitationReadRecommendation(query) : null;
1365
1452
  if (sent)
1366
1453
  card = { ...card, execution: sent.execution };
@@ -1400,7 +1487,7 @@ function preferredSkillForIntent(card, query) {
1400
1487
  return "oxygen-enrichment";
1401
1488
  if (card.id === "billing-and-budgets" && /\b(explain|why|reduce|increas\w*|burn|spent)\b/.test(query))
1402
1489
  return "oxygen-diagnostics";
1403
- if (card.id === "sending-infrastructure" && /\b(bounc\w*|health|placement|deliverability|spam)\b/.test(query))
1490
+ if (card.id === "sending-infrastructure" && /\b(bounc\w*|health|reputation|rejections?|placement|deliverability|spam)\b/.test(query))
1404
1491
  return "oxygen-deliverability";
1405
1492
  if (["signals", "linkedin-public-research"].includes(card.id) && /\blinkedin\b/.test(query))
1406
1493
  return "oxygen-signals";
@@ -1519,6 +1606,14 @@ export const CAPABILITY_ROUTE_MCP_CALLS = {
1519
1606
  sequenceReplyToStop: () => ({ tool: "oxygen_sequences_update", arguments: { sequence: "<sequence>", reply_to_stop_text: "Reply STOP to opt out." } }),
1520
1607
  ugcProgramCreate: () => ({ tool: "oxygen_ugc_program_create", arguments: { product_url: "<your product site>" } }),
1521
1608
  // The first comment rides in `content`, as the CLI's --first-comment does.
1609
+ publishingPostDraft: () => ({
1610
+ tool: "oxygen_publishing_posts_create",
1611
+ arguments: { content_text: "<post>", publish_at: "<iso>", status: "draft" },
1612
+ }),
1613
+ signalCapture: () => ({
1614
+ tool: "oxygen_crm_activity_log",
1615
+ arguments: { object: "people", row_id: "<person rowId or email>", activity_type: "website_visit", direction: "inbound", summary: "<what they viewed>", occurred_at: "<iso>", mode: "live" },
1616
+ }),
1522
1617
  publishingFirstComment: () => ({
1523
1618
  tool: "oxygen_publishing_posts_create",
1524
1619
  arguments: { content_text: "<post>", publish_at: "<iso>", sender_account_id: "<sender_id>", content: { first_comment: "<comment, e.g. the link>" } },
@@ -1614,6 +1709,8 @@ function isSequenceUnsubscribeIntent(query) {
1614
1709
  && !/\b(unsubscribed|lists?|import\w*|suppress\w*|block ?list|blocklist|do not contact|dnc)\b/.test(query);
1615
1710
  }
1616
1711
  /** A post's first comment is set with the post; `publishing posts update` takes the same flag. */
1712
+ export const POST_DRAFT_COMMAND = 'oxygen publishing posts create --draft --publish-at <iso> --text "<post>" --json';
1713
+ export const SIGNAL_CAPTURE_COMMAND = 'oxygen crm activity log people <rowId or email> --type website_visit --direction inbound --summary "<what they viewed>" --live --json';
1617
1714
  export const PUBLISHING_FIRST_COMMENT_COMMAND = 'oxygen publishing posts create --publish-at <iso> --sender <sender_id> --text "<post>" --first-comment "<comment, e.g. the link>" --json';
1618
1715
  /**
1619
1716
  * The comment OXYGEN posts right after a post goes out ("put the link in the first
@@ -1865,6 +1962,41 @@ function tableRowRemovalRecommendation(query) {
1865
1962
  execution: "Delete Table rows with tables rows remove-selection (oxygen_table_rows_remove_selection): the rows a readable filter tree matches (--filter-tree-json / filter_tree, the same tree tables query takes), exact row_ids, or an all_matching selection with search, sort, range and exclusions. Hydrate its schema first. Unreadable filter nodes and filters arrays are refused; unqualified all_matching deletes every row. This is permanent and costs 0 credits; to keep a restorable copy first, tables duplicate (oxygen_tables_duplicate, 0 credits). Read the matching rows before deletion and the survivors afterward; each removed row's values stay readable with rows history (oxygen_rows_history). Deleting rows does not stop campaign enrollment.",
1866
1963
  };
1867
1964
  }
1965
+ /**
1966
+ * Literal row values go in with the row-insert tool. Without this card "add rows"
1967
+ * ranked create, enrichment and the external Records import first (sweep 3).
1968
+ */
1969
+ function tableRowInsertRecommendation(query) {
1970
+ if (!/\brows?\b/.test(query) || !/\b(add|adding|insert\w*|append\w*|put|enter|write)\b/.test(query)
1971
+ || /\b(delete|remove|update|edit|change|set|mark|existing|import\w*|csv|crm|records?|enrich\w*|sync\w*|workflows?|agents?|campaigns?|sequences?|webhooks?|feeds?|functions?|callables?|cells?|dedup\w*|duplicates?)\b/.test(query)
1972
+ || /\b(?:add|insert|new)\s+(?:an?\s+|the\s+)?(?:\w+\s+)?columns?\b/.test(query)
1973
+ || isSecondPartyDecisionIntent(query))
1974
+ return null;
1975
+ return {
1976
+ tools: ["oxygen_table_rows_insert", "oxygen_tables_create", "oxygen_tables_query"],
1977
+ commands: ["tables rows insert", "tables create", "tables query"],
1978
+ execution: "Add literal rows with tables rows insert (oxygen_table_rows_insert): table plus rows, an array of {column_key: value} objects. tables create (oxygen_tables_create) makes the columns but takes no rows, so insert after it. Manual rows are a free internal write. Read the rows back with tables query (oxygen_tables_query).",
1979
+ };
1980
+ }
1981
+ /**
1982
+ * Changing values on rows that already exist is oxygen_rows_update. Without this
1983
+ * card "update rows" / "set a cell" ranked create and enrichment tools, and an
1984
+ * Oxygent concluded no row update existed and offered a formula column instead of
1985
+ * the weekly Workflow it was asked for (System 1 routing case, 2026-10-22).
1986
+ */
1987
+ function tableRowUpdateRecommendation(query) {
1988
+ if (!/\b(rows?|cells?)\b/.test(query)
1989
+ || !(/\b(update\w*|edit\w*|chang\w*|sets?|setting|overwrit\w*|mark\w*)\b/.test(query) || /\bexisting (?:table )?rows?\b/.test(query))
1990
+ || /\b(delete|remove|insert\w*|append\w*|import\w*|csv|crm|records?|enrich\w*|sync\w*|campaigns?|sequences?|functions?|callables?|dedup\w*|duplicates?|history|provenance|rerun|re-run|retry|columns? run|run columns?|formulas?)\b/.test(query)
1991
+ || /\b(?:add|insert|new|create)\s+(?:an?\s+|the\s+)?(?:\w+\s+)?(?:columns?|rows?)\b/.test(query)
1992
+ || isSecondPartyDecisionIntent(query))
1993
+ return null;
1994
+ return {
1995
+ tools: ["oxygen_rows_update", "oxygen_tables_query", "oxygen_rows_upsert"],
1996
+ commands: ["tables update", "tables query", "tables upsert"],
1997
+ execution: "Change values on existing rows with tables update (oxygen_rows_update): table, row_id and values as {column_key: value}, one row per call, a free internal write. Find the rows and their _row_id with tables query (oxygen_tables_query) and filters first; tables upsert (oxygen_rows_upsert) writes by a key column instead. In a Workflow, loop over the query's rows and call oxygen_rows_update with row_id loop.each.item._row_id. Read the rows back after writing.",
1998
+ };
1999
+ }
1868
2000
  /** Reads of grid values/state are separate from executing or resolving actions. */
1869
2001
  function tableReadRecommendation(query) {
1870
2002
  if (isFunctionIntent(query) || isSecondPartyDecisionIntent(query)
@@ -2219,6 +2351,12 @@ function explicitCapabilityIntent(query) {
2219
2351
  return ROUTE_BY_ID.get(readOnly.capabilityId) ?? null;
2220
2352
  if (isOxygentChatIntent(query))
2221
2353
  return ROUTE_BY_ID.get("agents") ?? null;
2354
+ // Values on existing Table rows, ahead of the sourcing rule whose "prospect"
2355
+ // claimed "set a value on rows where stage is prospect". Scheduled or tag
2356
+ // wording keeps its Workflow and Tags owners.
2357
+ if (tableRowUpdateRecommendation(query)
2358
+ && !/\b(workflows?|agents?|routines?|automat\w*|schedul\w*|weekly|daily|nightly|hourly|every|cron|tags?|tagg\w*)\b/.test(query))
2359
+ return ROUTE_BY_ID.get("tables") ?? null;
2222
2360
  // Importing an AI answer is GEO evidence, rather than a Table file import.
2223
2361
  if (/\b(?:geo|aeo|ai (?:(?:search|answer) )?visibility|consumer ai answers?|answer engine optimization|generative engine optimization)\b/.test(query))
2224
2362
  return ROUTE_BY_ID.get("geo") ?? null;
@@ -2334,6 +2472,15 @@ function explicitCapabilityIntent(query) {
2334
2472
  // claimed "a first comment on my LinkedIn post" and "... automatically".
2335
2473
  if (isFirstCommentIntent(query))
2336
2474
  return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
2475
+ // Writing the workspace's own post is a Publishing draft, ahead of the keyword
2476
+ // post search ("a LinkedIn post about ...") and the public LinkedIn read owner,
2477
+ // which claimed "write a LinkedIn post and save it as a draft".
2478
+ if (isPostDraftIntent(query))
2479
+ return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
2480
+ // One event the user reports ("log that she visited our pricing page") is a
2481
+ // Signal on the person, not a CRM note or a table row.
2482
+ if (isSignalCaptureIntent(query))
2483
+ return ROUTE_BY_PRIMITIVE.get("signals") ?? null;
2337
2484
  // Publish/approve are writes even when the noun is a public LinkedIn post.
2338
2485
  // The 2026-09-30 connector baseline offered the cookieless read owner first.
2339
2486
  if (isPublishingPostIntent(query))
@@ -2355,6 +2502,8 @@ function explicitCapabilityIntent(query) {
2355
2502
  if (isMailboxVolumeIntent(query) && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query)) {
2356
2503
  return ROUTE_BY_ID.get("sending-infrastructure") ?? null;
2357
2504
  }
2505
+ if (isAdaptiveWatchIntent(query))
2506
+ return ROUTE_BY_PRIMITIVE.get("agents") ?? null;
2358
2507
  if (isHostedWorkflowIntent(query))
2359
2508
  return ROUTE_BY_PRIMITIVE.get("workflows") ?? null;
2360
2509
  if (placementGuideRecommendation(query))
@@ -2531,10 +2680,16 @@ function matchesIntentTerm(query, term) {
2531
2680
  }
2532
2681
  return false;
2533
2682
  }
2534
- function highestScoringRoute(query) {
2683
+ /**
2684
+ * A matched play's skill counts as one multi-word term for each card that names
2685
+ * it first: "cold email the whole market" matched the whole-market play, yet its
2686
+ * one card term ("email") picked Messages over Sequences.
2687
+ */
2688
+ const PLAY_SKILL_TERM_SCORE = 6;
2689
+ function highestScoringRoute(query, playSkill) {
2535
2690
  let best = null;
2536
2691
  for (const card of OXYGEN_CAPABILITY_ROUTES) {
2537
- let score = 0;
2692
+ let score = playSkill !== undefined && card.skills[0] === playSkill ? PLAY_SKILL_TERM_SCORE : 0;
2538
2693
  for (const term of card.intentTerms) {
2539
2694
  if (matchesIntentTerm(query, term))
2540
2695
  score += term.includes(" ") ? 6 : 2;
@@ -2561,6 +2716,25 @@ export const LOCAL_BUSINESS_SCRAPE_COMMAND = `oxygen companies search preview --
2561
2716
  function isLocalBusinessIntent(query) {
2562
2717
  return /\b(local business(?:es)?|google maps|maps listings?|storefronts?|near me|(?:dental|medical|law|veterinary) (?:practices?|offices?|clinics?)|dentists?|restaurants?|cafes?|coffee shops?|gyms?|salons?|plumbers?|electricians?|contractors?|hvac|auto repair|real estate agents?|businesses (?:in|near|around) [a-z])\b/.test(query);
2563
2718
  }
2719
+ /** Writing or saving the workspace's own post, or asking to post it: a Publishing draft. */
2720
+ function isPostDraftIntent(query) {
2721
+ if (/\b(engagers?|engag(?:ed|ing|ement)|comments?|commenters?|react\w*|harvest|scrape|competitors?|creators?|repl(?:y|ies)|tables?|rows?|workflows?|automat\w*|sequences?|campaigns?)\b/.test(query))
2722
+ return false;
2723
+ if (/\b(find|search|research|monitor|watch)\b/.test(query))
2724
+ return false;
2725
+ return (/\b(write|draft|drafts|drafted|compose|save|saved)\b/.test(query) && /\bposts?\b/.test(query))
2726
+ || /\bpost (?:it|this|that)\b|\bpost (?:on|to) (?:linkedin|x|twitter)\b/.test(query);
2727
+ }
2728
+ /** Recording one reported event (a visit, a view) on a person: a Signal, logged on the Record. */
2729
+ function isSignalCaptureIntent(query) {
2730
+ return /\b(record|log|logs|logged|track|note|save|add|capture)\b/.test(query)
2731
+ && /\b(visit(?:s|ed|or)?|viewed|profile views?|signals?)\b/.test(query)
2732
+ // Outreach wording ("set up an email follow-up ... add the people") is a
2733
+ // Sequence for signalled people, not one reported event.
2734
+ && !/\b(list|show|read|which|who|rank|leads? to contact|registry|search|find|inbox|webhook|automat\w*|workflows?|sequences?|enrich\w*|tables?|columns?|feeds?|follow ups?|campaigns?|cadences?|outreach|emails)\b/.test(query);
2735
+ }
2736
+ const POST_DRAFT_EXECUTION = "Read the voice page first (oxygen_knowledge_page_get, slug voice) and use only numbers a page states. Save the post with oxygen_publishing_posts_create, status draft and no approved flag: it needs no sender, publishes nothing and opens in Publishing for review. A post written only in the reply is not saved. Publishing it is a separate oxygen_publishing_posts_approve after the user says yes; a missing connected sender is the one blocker to name. End by saying it is a draft, not live, with its web_url; if the save is refused, say the post exists only in this chat.";
2737
+ const SIGNAL_CAPTURE_EXECUTION = "Find the person with oxygen_crm_record_search; if there is no record, create it with oxygen_crm_record_assert (identity email, values full_name and company_domain when known; free, no card). Then log the event on that record with oxygen_crm_activity_log: row_id the rowId the assert or search returned (not object.id) or her email, activity_type website_visit (or profile_view, post_reaction, new_follower), direction inbound, occurred_at, a one-line summary such as the page viewed. It then shows in the Signals feed and on the person's timeline. Not a note or a table row; contacting nobody. End by saying it is recorded as a website-visit Signal and where it shows (the record's web_url); if a write is refused, say nothing was recorded.";
2564
2738
  function isPublishingPostIntent(query) {
2565
2739
  return /\b(posts?|publishing|content calendar)\b/.test(query)
2566
2740
  && /\b(publish|publishing|schedule|scheduled|approve|approval)\b/.test(query)
@@ -2702,6 +2876,22 @@ function recommendationsFor(card, query) {
2702
2876
  exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.ugcProgramCreate(),
2703
2877
  };
2704
2878
  }
2879
+ if (card.primitive === "publishing" && isPostDraftIntent(query)) {
2880
+ return {
2881
+ tools: ["oxygen_knowledge_page_get", "oxygen_publishing_posts_create", "oxygen_publishing_posts_get"],
2882
+ commands: ["knowledge page get", "publishing posts create", "publishing posts get"],
2883
+ exactCommand: POST_DRAFT_COMMAND,
2884
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.publishingPostDraft(),
2885
+ };
2886
+ }
2887
+ if (card.primitive === "signals" && isSignalCaptureIntent(query)) {
2888
+ return {
2889
+ tools: ["oxygen_crm_record_search", "oxygen_crm_record_assert", "oxygen_crm_activity_log", "oxygen_signals_list"],
2890
+ commands: ["crm search", "crm assert", "crm activity log", "signals list"],
2891
+ exactCommand: SIGNAL_CAPTURE_COMMAND,
2892
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.signalCapture(),
2893
+ };
2894
+ }
2705
2895
  if (card.primitive === "publishing" && isFirstCommentIntent(query)) {
2706
2896
  return {
2707
2897
  tools: ["oxygen_publishing_posts_create", "oxygen_publishing_posts_update", "oxygen_publishing_posts_get"],
@@ -2917,6 +3107,12 @@ function recommendationsFor(card, query) {
2917
3107
  const removal = tableRowRemovalRecommendation(query);
2918
3108
  if (removal)
2919
3109
  return removal;
3110
+ const insert = tableRowInsertRecommendation(query);
3111
+ if (insert)
3112
+ return insert;
3113
+ const update = tableRowUpdateRecommendation(query);
3114
+ if (update)
3115
+ return update;
2920
3116
  const read = tableReadRecommendation(query);
2921
3117
  if (read)
2922
3118
  return read;
@@ -3224,6 +3420,14 @@ function recommendationsFor(card, query) {
3224
3420
  commands: ["workflows templates search", "workflows templates describe", "workflows templates preflight", "workflows templates apply"],
3225
3421
  };
3226
3422
  }
3423
+ // A scheduled change to existing rows: the query and row-update tools the
3424
+ // graph's nodes call rank beside the authoring gateway.
3425
+ if (/\b(rows?|cells?)\b/.test(query) && /\b(updat\w*|edit\w*|chang\w*|sets?|setting|tag\w*|mark\w*|writ\w*)\b/.test(query)
3426
+ && !/\b(insert\w*|add\w*|append\w*|new rows?|delete\w*|remove\w*)\b/.test(query))
3427
+ return {
3428
+ tools: ["oxygen_workflows_schema", "oxygen_workflows_lint", "oxygen_tables_query", "oxygen_rows_update", "oxygen_workflows_apply"],
3429
+ commands: ["workflows schema", "workflows lint", "tables query", "tables update", "workflows apply"],
3430
+ };
3227
3431
  if (isNetNewLinkedInInitiation(query) || /\b(outreach|sequence|enroll|message)\b/.test(query)) {
3228
3432
  return {
3229
3433
  tools: ["oxygen_workflows_schema", "oxygen_workflows_lint", "oxygen_workflows_apply", "oxygen_tools_get", "oxygen_sequences_list", "oxygen_sequences_start"],
@@ -3679,6 +3883,20 @@ function isHostedWorkflowIntent(query) {
3679
3883
  return /\b(nightly|hourly|daily|weekly|every (?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|day|night|week|month))\b/.test(query)
3680
3884
  || (/\bschedule(?:d)?\b/.test(query) && !/\bpost|content\b/.test(query));
3681
3885
  }
3886
+ /**
3887
+ * Watching items that change and working out what each one needs is an Agent's
3888
+ * judgment, not a Workflow's fixed rule: "keep an eye on open deals and suggest
3889
+ * what to do next for each" routed to Records reads, and the Oxygent proposed a
3890
+ * routine without saying why (System 1 routing case, 2026-10-22).
3891
+ */
3892
+ function isAdaptiveWatchIntent(query) {
3893
+ if (/\b(workflows?|webhooks?|cron|deterministic|fixed rule|sequences?|campaigns?|posts?|mailbox\w*|deliverability|bounce\w*)\b/.test(query))
3894
+ return false;
3895
+ return /\b(keeps? an eye on|keeps? tabs on|keeps? track of|watch(?:es|ing)?|monitor(?:s|ing)?|look(?:s|ing)? after)\b/.test(query)
3896
+ && /\b(suggest\w*|decid\w*|works? out|figures? out|recommend\w*|judg\w*|what to do|next steps?|next moves?)\b/.test(query)
3897
+ && /\b(each|every|individual\w*|differently|deals?|pipeline|opportunit(?:y|ies)|accounts?)\b/.test(query);
3898
+ }
3899
+ const ADAPTIVE_WATCH_EXECUTION = "Watching items that change and deciding what fits each one is an Agent's job: each run looks at every item and works out what it needs, while a Workflow repeats one fixed rule. Tell the person that in one plain line. Set it up as an ongoing Agent session with per-run and rolling 24-hour credit caps; in an Oxygent chat, propose a routine and put that line in its why. Read the items first so the goal names real fields (for deals: stage, amount, last activity). Size the caps to one check, about 10 credits a run for reading a few dozen records and writing suggestions, and say so when that is above a limit the person gave. Suggestions stay inside OXYGEN (a task or a note); contacting anyone needs its own approval.";
3682
3900
  function isRecurringCompanyMonitoringIntent(query) {
3683
3901
  return /\b(companies|company|competitors?)\b/.test(query)
3684
3902
  && /\b(monitor(?:ing)?|watch(?:ing|list)?|track(?:ing)?)\b/.test(query)
@@ -1,3 +1,9 @@
1
- export declare function tagClayMigrationFailureState(error: unknown, encryptedState: string): void;
1
+ type FailureState = {
2
+ encryptedState: string;
3
+ expiresAt?: string;
4
+ };
5
+ export declare function tagClayMigrationFailureState(error: unknown, encryptedState: string, expiresAt?: string): void;
2
6
  /** Consume only state attached in this process by the migration adapter. */
7
+ export declare function consumeClayMigrationFailureState(error: unknown): FailureState | null;
3
8
  export declare function readClayMigrationFailureState(error: unknown): string | null;
9
+ export {};
@@ -1,15 +1,16 @@
1
- // Rotated credentials must survive a failed source read, without becoming part
2
- // of a customer error, operator log or serialized tool result.
3
1
  const failureStates = new WeakMap();
4
- export function tagClayMigrationFailureState(error, encryptedState) {
2
+ export function tagClayMigrationFailureState(error, encryptedState, expiresAt) {
5
3
  if (error !== null && typeof error === "object")
6
- failureStates.set(error, encryptedState);
4
+ failureStates.set(error, { encryptedState, ...(expiresAt === undefined ? {} : { expiresAt }) });
7
5
  }
8
6
  /** Consume only state attached in this process by the migration adapter. */
9
- export function readClayMigrationFailureState(error) {
7
+ export function consumeClayMigrationFailureState(error) {
10
8
  if (error === null || typeof error !== "object")
11
9
  return null;
12
10
  const state = failureStates.get(error) ?? null;
13
11
  failureStates.delete(error);
14
12
  return state;
15
13
  }
14
+ export function readClayMigrationFailureState(error) {
15
+ return consumeClayMigrationFailureState(error)?.encryptedState ?? null;
16
+ }
@@ -115,6 +115,7 @@ export const BROWSER_OPERATIONS = [
115
115
  return_to: text("Same-origin path to return to after Checkout, for example /home."),
116
116
  source: text("Optional upgrade-entry attribution; omit if unknown."),
117
117
  blocked: text("Optional capability that led to the upgrade; omit if unknown."),
118
+ allow_promotion_codes: bool({ description: "Show the promotion-code field instead of the first-month offer." }),
118
119
  }, ["plan"]),
119
120
  operation("billing.confirm", "/api/cli/billing/confirm", "POST", "billing confirm", "oxygen_billing_confirm", "billing", "Confirm a plan-change preview within 15 minutes; approved accepts payment, term and effective date. Unrestricted members allowed; restricted clients denied.", { token: text(), approved: bool() }, ["token", "approved"], { spends: true }),
120
121
  operation("billing.portal", "/api/cli/billing/portal", "POST", "billing portal", "oxygen_billing_portal", "billing", "Create a Stripe billing-portal URL. Unrestricted members allowed; restricted clients denied.", { returnUrl: text() }),
@@ -3,6 +3,41 @@ import { type ContractIssue, type Infer } from "./schema.js";
3
3
  export declare const CLAY_MIGRATION_ACTIONS: readonly ["connect", "status", "discover", "read", "transfer", "checkpoint", "report", "disconnect"];
4
4
  export declare const CLAY_RESOURCE_TYPES: readonly ["workspace", "folder", "workbook", "table", "audience", "workflow", "function", "claygent", "knowledge", "schedule", "campaign"];
5
5
  export declare const CLAY_RESOURCE_DISPOSITIONS: readonly ["pending", "copied", "rebuilt", "needs_decision", "unsupported", "inaccessible", "excluded", "failed"];
6
+ type GlobalAudienceScope = {
7
+ entity_type: "people" | "companies" | "deals";
8
+ global: true;
9
+ archived?: true;
10
+ };
11
+ export declare const CLAY_GLOBAL_AUDIENCE_SCOPES: {
12
+ readonly "global-people": {
13
+ readonly entity_type: "people";
14
+ readonly global: true;
15
+ };
16
+ readonly "global-companies": {
17
+ readonly entity_type: "companies";
18
+ readonly global: true;
19
+ };
20
+ readonly "global-deals": {
21
+ readonly entity_type: "deals";
22
+ readonly global: true;
23
+ };
24
+ readonly "global-archived-people": {
25
+ readonly entity_type: "people";
26
+ readonly global: true;
27
+ readonly archived: true;
28
+ };
29
+ readonly "global-archived-companies": {
30
+ readonly entity_type: "companies";
31
+ readonly global: true;
32
+ readonly archived: true;
33
+ };
34
+ readonly "global-archived-deals": {
35
+ readonly entity_type: "deals";
36
+ readonly global: true;
37
+ readonly archived: true;
38
+ };
39
+ };
40
+ export declare function clayGlobalAudienceScope(resourceId: string): GlobalAudienceScope | null;
6
41
  export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObject<{
7
42
  readonly action: {
8
43
  type: "string";
@@ -17,11 +52,13 @@ export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObje
17
52
  description?: string;
18
53
  };
19
54
  readonly resource_id: import("./schema.js").StringProperty;
55
+ readonly row_id: import("./schema.js").StringProperty;
20
56
  readonly section: {
21
57
  type: "string";
22
- enum: readonly ("columns" | "fields" | "graph" | "metadata" | "record_ids" | "records" | "rows" | "sequence")[];
58
+ enum: readonly ("columns" | "fields" | "graph" | "metadata" | "record_ids" | "records" | "row" | "rows" | "sequence")[];
23
59
  description?: string;
24
60
  };
61
+ readonly full: import("./schema.js").BooleanProperty;
25
62
  readonly path: import("./schema.js").StringProperty;
26
63
  readonly text_offset: {
27
64
  type: "integer";
@@ -35,6 +72,7 @@ export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObje
35
72
  enum: readonly ("companies" | "deals" | "people")[];
36
73
  description?: string;
37
74
  };
75
+ readonly archived: import("./schema.js").BooleanProperty;
38
76
  readonly record_ids: import("./schema.js").ArrayProperty<{
39
77
  type: "integer";
40
78
  description?: string;
@@ -69,3 +107,4 @@ export declare const CLAY_MIGRATION_CONTRACT: import("./schema.js").ContractObje
69
107
  export type ClayMigrationInput = Infer<typeof CLAY_MIGRATION_CONTRACT>;
70
108
  export declare function clayMigrationInputIssues(args: Readonly<Record<string, unknown>>): ContractIssue[];
71
109
  export declare const CLAY_MIGRATION_OPERATION: OperationSpec<typeof CLAY_MIGRATION_CONTRACT>;
110
+ export {};