@oxygen-agent/cli 1.1011.326 → 1.1011.440

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 (98) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +15 -4
  3. package/dist/connectors-commands.d.ts +4 -1
  4. package/dist/connectors-commands.js +84 -10
  5. package/dist/contract-flags.d.ts +2 -0
  6. package/dist/contract-flags.js +26 -11
  7. package/dist/crm-support-flags.d.ts +11 -0
  8. package/dist/crm-support-flags.js +60 -0
  9. package/dist/customer-operation-commands.d.ts +5 -0
  10. package/dist/customer-operation-commands.js +101 -0
  11. package/dist/database-query-flags.d.ts +2 -0
  12. package/dist/database-query-flags.js +6 -0
  13. package/dist/help.d.ts +3 -1
  14. package/dist/help.js +43 -0
  15. package/dist/http-client.d.ts +2 -0
  16. package/dist/http-client.js +41 -1
  17. package/dist/index.js +640 -234
  18. package/dist/knowledge-repository-commands.js +12 -14
  19. package/dist/mailbox-ramp-notice.d.ts +11 -0
  20. package/dist/mailbox-ramp-notice.js +53 -0
  21. package/dist/settings-commands.d.ts +11 -0
  22. package/dist/settings-commands.js +73 -0
  23. package/dist/skills.d.ts +5 -4
  24. package/dist/skills.js +106 -79
  25. package/dist/tool-catalog-detail-command.d.ts +7 -0
  26. package/dist/tool-catalog-detail-command.js +19 -0
  27. package/dist/tool-catalog-search-window.d.ts +6 -0
  28. package/dist/tool-catalog-search-window.js +12 -0
  29. package/dist/transcript.js +1 -0
  30. package/node_modules/@oxygen/shared/dist/bounded-read-result.d.ts +4 -0
  31. package/node_modules/@oxygen/shared/dist/bounded-read-result.js +25 -0
  32. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +47 -1
  33. package/node_modules/@oxygen/shared/dist/capability-discovery.js +625 -39
  34. package/node_modules/@oxygen/shared/dist/connection-approval.d.ts +2 -0
  35. package/node_modules/@oxygen/shared/dist/connection-approval.js +2 -0
  36. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.d.ts +2 -0
  37. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +110 -0
  38. package/node_modules/@oxygen/shared/dist/contracts/connectors.d.ts +32 -6
  39. package/node_modules/@oxygen/shared/dist/contracts/connectors.js +56 -4
  40. package/node_modules/@oxygen/shared/dist/contracts/crm-attributes.d.ts +4 -0
  41. package/node_modules/@oxygen/shared/dist/contracts/crm-attributes.js +39 -0
  42. package/node_modules/@oxygen/shared/dist/contracts/crm-enrichment-read.d.ts +21 -0
  43. package/node_modules/@oxygen/shared/dist/contracts/crm-enrichment-read.js +15 -0
  44. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +117 -0
  45. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -0
  46. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.d.ts +2 -0
  47. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +131 -0
  48. package/node_modules/@oxygen/shared/dist/contracts/dashboard-tabs.d.ts +3 -0
  49. package/node_modules/@oxygen/shared/dist/contracts/dashboard-tabs.js +2 -0
  50. package/node_modules/@oxygen/shared/dist/contracts/database.d.ts +30 -0
  51. package/node_modules/@oxygen/shared/dist/contracts/database.js +29 -0
  52. package/node_modules/@oxygen/shared/dist/contracts/integration-connect.d.ts +4 -0
  53. package/node_modules/@oxygen/shared/dist/contracts/integration-connect.js +7 -0
  54. package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.d.ts +43 -0
  55. package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +20 -0
  56. package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.d.ts +12 -0
  57. package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.js +8 -0
  58. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +48 -2
  59. package/node_modules/@oxygen/shared/dist/contracts/operations.js +68 -2
  60. package/node_modules/@oxygen/shared/dist/contracts/table-grid-query.d.ts +4 -0
  61. package/node_modules/@oxygen/shared/dist/contracts/table-grid-query.js +6 -0
  62. package/node_modules/@oxygen/shared/dist/contracts/table-query-grid.d.ts +21 -0
  63. package/node_modules/@oxygen/shared/dist/contracts/table-query-grid.js +27 -0
  64. package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-detail.d.ts +19 -0
  65. package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-detail.js +10 -0
  66. package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-search-window.d.ts +12 -0
  67. package/node_modules/@oxygen/shared/dist/contracts/tool-catalog-search-window.js +7 -0
  68. package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.d.ts +17 -0
  69. package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.js +49 -0
  70. package/node_modules/@oxygen/shared/dist/decision-ledger-parser.d.ts +32 -0
  71. package/node_modules/@oxygen/shared/dist/decision-ledger-parser.js +303 -0
  72. package/node_modules/@oxygen/shared/dist/decision-ledger.d.ts +10 -0
  73. package/node_modules/@oxygen/shared/dist/decision-ledger.generated.d.ts +2 -0
  74. package/node_modules/@oxygen/shared/dist/decision-ledger.generated.js +717 -0
  75. package/node_modules/@oxygen/shared/dist/decision-ledger.js +20 -0
  76. package/node_modules/@oxygen/shared/dist/email-campaign-ramp.d.ts +104 -0
  77. package/node_modules/@oxygen/shared/dist/email-campaign-ramp.js +250 -0
  78. package/node_modules/@oxygen/shared/dist/mcp-event-contract.d.ts +10 -7
  79. package/node_modules/@oxygen/shared/dist/mcp-event-contract.js +11 -7
  80. package/node_modules/@oxygen/shared/dist/mcp-table-widget.d.ts +50 -0
  81. package/node_modules/@oxygen/shared/dist/mcp-table-widget.js +24 -0
  82. package/node_modules/@oxygen/shared/dist/plan-band.d.ts +8 -0
  83. package/node_modules/@oxygen/shared/dist/plan-band.js +12 -0
  84. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +20 -0
  85. package/node_modules/@oxygen/shared/dist/plan-limits.js +41 -0
  86. package/node_modules/@oxygen/shared/dist/provider-input-value.d.ts +5 -0
  87. package/node_modules/@oxygen/shared/dist/provider-input-value.js +10 -0
  88. package/node_modules/@oxygen/shared/dist/sequences.d.ts +33 -4
  89. package/node_modules/@oxygen/shared/dist/sequences.js +48 -13
  90. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +17 -0
  91. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  92. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  93. package/node_modules/@oxygen/shared/package.json +8 -6
  94. package/package.json +1 -1
  95. package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +0 -15
  96. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +0 -31
  97. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +0 -41
  98. package/node_modules/@oxygen/shared/dist/copilot-skills.js +0 -6
@@ -22,16 +22,24 @@ export const OXYGEN_CAPABILITY_ROUTES = [
22
22
  id: "workspace-access",
23
23
  layer: "Control",
24
24
  primitive: null,
25
- owns: "Authentication, CLI identities, API keys, sessions, organizations, active-workspace selection, and connected apps (ChatGPT, Claude, Codex…) with the default workspace each one works in.",
26
- notFor: "Workspace GTM data, provider credentials, or execution inside a primitive.",
27
- execution: "Resolve identity and select the hosted workspace before operating its primitives. Each app connected with OAuth keeps its own default workspace: `connectors list` shows them and `connectors set` changes one app only; orgs select changes only the app or CLI that calls it.",
25
+ owns: "Authentication, CLI identities, API keys, sessions, organizations, active-workspace selection, connected apps (ChatGPT, Claude, Codex…) with the default workspace each one works in and the OXYGEN events each one is subscribed to, and which web-app sidebar pages the workspace hides for every member (Settings → Customise).",
26
+ notFor: "Workspace GTM data, provider credentials, execution inside a primitive, or access control: a hidden sidebar page stays reachable by URL.",
27
+ execution: "Resolve identity and select the hosted workspace before operating its primitives. Each app connected with OAuth keeps its own default workspace: `connectors list` shows them and `connectors set` changes one app only; orgs select changes only the app or CLI that calls it. What OXYGEN sends an app (its event subscriptions, such as ChatGPT watching for classified replies, and every delivery with its outcome, never content): `connectors events get <app>`; `connectors events revoke <app> --subscription <id|all>` stops them for good. While an app holds event subscriptions, its paid calls are capped by its rolling 24-hour credit ceiling: the plan default, or `connectors set <app> --credit-ceiling <credits>` (above the default also needs --approve-above-default); `connectors get <app>` shows the ceiling and what it spent. Sidebar pages (Settings → Customise): `settings pages list` (oxygen_settings_pages_get) shows each page and whether it is hidden; an admin or owner hides or shows pages for everyone with `settings pages set --hide <pages>`, `--show <pages>` or `--show-all` (oxygen_settings_pages_update). Nothing is deleted.",
28
28
  posture: "mixed",
29
- gatewayTools: ["oxygen_whoami", "oxygen_orgs_list", "oxygen_orgs_select", "oxygen_connector_list", "oxygen_connector_update"],
30
- gatewayCommands: ["whoami", "orgs list", "orgs select", "connectors list", "connectors set"],
29
+ gatewayTools: [
30
+ "oxygen_whoami",
31
+ "oxygen_orgs_list",
32
+ "oxygen_orgs_select",
33
+ "oxygen_connector_list",
34
+ "oxygen_connector_update",
35
+ "oxygen_connector_events_get",
36
+ "oxygen_connector_events_revoke",
37
+ ],
38
+ gatewayCommands: ["whoami", "orgs list", "orgs select", "connectors list", "connectors set", "connectors events get", "connectors events revoke"],
31
39
  skills: ["oxygen-quickstart"],
32
40
  // `mcp`: the host-driven MCP Events routes an OAuth connection's subscriptions go through (ADR 0030 §2).
33
41
  endpointSections: ["api-keys", "auth", "connectors", "login-requests", "mcp", "orgs", "sessions", "settings", "whoami"],
34
- intentTerms: ["login", "authenticate", "identity", "who am i", "workspace", "organization", "org", "api key", "session", "switch workspace", "connected app", "connected apps", "default workspace"],
42
+ intentTerms: ["login", "authenticate", "identity", "who am i", "workspace", "organization", "org", "api key", "session", "switch workspace", "connected app", "connected apps", "default workspace", "sidebar", "navigation", "hidden pages", "page visibility", "workspace settings", "organisation settings", "organization settings"],
35
43
  },
36
44
  {
37
45
  id: "discovery-and-skills",
@@ -41,7 +49,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
41
49
  notFor: "Executing GTM work or loading full manifests and schemas before a capability is selected.",
42
50
  execution: "Search by outcome, choose the owner, then hydrate one exact command, MCP schema, provider descriptor, or skill. For instance discovery, read the map and then the primitive that owns the row.",
43
51
  posture: "read_only",
44
- gatewayTools: ["oxygen_capabilities_search", "oxygen_tools_search", "oxygen_recipes_list", "oxygen_workspace_map"],
52
+ gatewayTools: ["oxygen_capabilities_search", "oxygen_skills_get", "oxygen_tools_search", "oxygen_recipes_list", "oxygen_workspace_map"],
45
53
  gatewayCommands: ["capabilities search", "commands search", "skills search", "tools search", "recipes list", "workspace map"],
46
54
  skills: ["oxygen-quickstart", "oxygen-gtm"],
47
55
  // `workspace` (the map) belongs here rather than beside `home`: capability
@@ -65,7 +73,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
65
73
  skills: ["oxygen-onboarding", "oxygen-quickstart", "oxygen-gtm"],
66
74
  // `activation` remains an exact legacy API family for saved clients, but it
67
75
  // has no recommended gateway and is not an intent term or first-touch path.
68
- endpointSections: ["activation", "copilot", "home"],
76
+ endpointSections: ["activation", "copilot", "home", "onboarding"],
69
77
  // "next step" alone is deliberately absent: it is a phrase every primitive's
70
78
  // users say ("next step of this workflow run"), and a 6-point multi-word hit
71
79
  // would drag those queries here. The terms kept are the ones only a founder
@@ -76,15 +84,15 @@ export const OXYGEN_CAPABILITY_ROUTES = [
76
84
  id: "billing-and-budgets",
77
85
  layer: "Control",
78
86
  primitive: null,
79
- owns: "Credits, plan state, usage receipts, subscriptions, and standing budget policies.",
87
+ owns: "Credits, customer credit prices, plan state, usage receipts, subscriptions, and standing budget policies.",
80
88
  notFor: "Authorizing a specific provider call or external write; that action still needs its own preview and approval.",
81
89
  execution: "Read billing state or set a hosted budget policy; exact action ceilings remain primitive-owned.",
82
90
  posture: "mixed",
83
- gatewayTools: ["oxygen_billing_balance", "oxygen_billing_usage", "oxygen_billing_estimate", "oxygen_budget_list"],
84
- gatewayCommands: ["billing balance", "billing usage", "billing estimate", "budget list", "billing invoices"],
91
+ gatewayTools: ["oxygen_billing_balance", "oxygen_billing_usage", "oxygen_billing_estimate", "oxygen_budget_list", "oxygen_customer_credit_prices"],
92
+ gatewayCommands: ["billing balance", "billing usage", "billing estimate", "budget list", "billing invoices", "pricing customer-credit-prices"],
85
93
  skills: ["oxygen-gtm", "oxygen-diagnostics"],
86
- endpointSections: ["billing", "budget", "limits"],
87
- intentTerms: ["billing", "credits", "credit balance", "usage", "spend", "cost", "cost estimate", "monthly cost", "budget", "subscription", "invoice", "vat", "vat number", "tax id", "cancel plan", "change plan", "upgrade", "downgrade"],
94
+ endpointSections: ["billing", "budget", "limits", "pricing"],
95
+ intentTerms: ["billing", "credits", "credit balance", "customer credit prices", "credit price catalog", "usage", "spend", "cost", "cost estimate", "monthly cost", "budget", "subscription", "invoice", "vat", "vat number", "tax id", "cancel plan", "change plan", "upgrade", "downgrade"],
88
96
  },
89
97
  {
90
98
  id: "support-and-feedback",
@@ -104,15 +112,15 @@ export const OXYGEN_CAPABILITY_ROUTES = [
104
112
  id: "platform-operations",
105
113
  layer: "Control",
106
114
  primitive: null,
107
- owns: "Staff/admin diagnostics, worker queue health, tenant repair, and database operating surfaces.",
108
- notFor: "Ordinary customer GTM work or product Observability over customer-visible runs.",
109
- execution: "Use the authenticated hosted operational endpoints; production mutations and migrations keep their human gates.",
115
+ owns: "Workspace queue health, failed jobs, queue recovery, and workspace database status, attach, migrations, read-only SQL and retired cost-policy controls.",
116
+ notFor: "Customer run timelines or approvals (Observability), Table grid queries (Tables), and external Supabase imports (Integrations).",
117
+ execution: "Inspect the active workspace's queue and database status; repair stale queue state or provision its database through the native scoped controls.",
110
118
  posture: "mixed",
111
- gatewayTools: ["oxygen_worker_queue_stats", "oxygen_worker_failures", "oxygen_observability_runs"],
112
- gatewayCommands: ["worker queue-stats", "worker failures", "db status"],
119
+ gatewayTools: ["oxygen_worker_queue_stats", "oxygen_worker_failures", "oxygen_workspace_queue_repair", "oxygen_workspace_database_status", "oxygen_workspace_database_provision"],
120
+ gatewayCommands: ["worker queue-stats", "worker failures", "worker repair", "db status", "db provision"],
113
121
  skills: ["oxygen-diagnostics"],
114
- endpointSections: ["admin", "db", "worker"],
115
- intentTerms: ["admin", "worker", "queue", "tenant repair", "database", "db status", "stuck jobs", "operations"],
122
+ endpointSections: ["db", "worker"],
123
+ intentTerms: ["worker", "queue", "queue repair", "database", "database hosting", "db status", "stuck jobs", "operations"],
116
124
  },
117
125
  {
118
126
  // Control-layer interaction state, deliberately NOT a sixteenth primitive
@@ -214,7 +222,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
214
222
  layer: "Data",
215
223
  primitive: "tables",
216
224
  owns: "Typed working datasets, rows, formulas, AI/tool/waterfall columns, reusable Functions with isolated drafts and published versions, cell state, projects, and run provenance.",
217
- notFor: "Canonical CRM truth, message cadence, or an off-platform spreadsheet runtime.",
225
+ notFor: "Canonical CRM truth: use Records via oxygen_crm_objects_list, oxygen_crm_record_get or oxygen_crm_record_search. Not message cadence or an off-platform spreadsheet runtime.",
218
226
  execution: "Create and run work in hosted OXYGEN Tables; validate a small sample before bounded paid runs. To see what a table's own columns can be enriched with and what each costs per row before adding anything, run `tools search --for-table <table>` (MCP oxygen_tools_search with for_table): free, it returns priced suggestions with the exact add command. For standard person or company enrichment, `columns add <table> --preset person_enrich|company_enrich` (MCP oxygen_columns_add with preset) adds the maintained bundle in one call before any hand-built tool column. Person questions over that bundle's profile payload (skill set, grad school, location, job fit, current company) and person appearance research (events, keynotes, podcasts, GitHub profile) are `columns catalog --category people` templates, added with `columns add <table> --prompt-key <key> --input <name>=<column>`. For a company fact OXYGEN already knows how to research (founders, parent company, funding, cloud provider, offers demos, industry, NAICS, HQ, LinkedIn/Crunchbase page lookups) or a page extraction, `columns catalog` lists the ready-made template and `columns add <table> --prompt-key <key> --input <name>=<column>` (MCP oxygen_columns_add with prompt_key) adds it with no prompt to write. For ICP tiers, read the saved profile with `knowledge page get icp`, then add native Classify Score: `columns add <table> --label 'ICP fit' --score 'low,medium,high' --prompt '<ICP criteria using {{evidence_column}} tokens>' --grounding workspace`. It costs 0.075 credits/row; preview with `columns run --dry-run`. `columns catalog --family classification --json` puts Classify in `data.native`; `data.entries` holds separate prompt templates that can cost more.",
219
227
  posture: "mixed",
220
228
  gatewayTools: ["oxygen_tables_create", "oxygen_tools_search", "oxygen_columns_add", "oxygen_columns_catalog", "oxygen_enrich_column_preview", "oxygen_tables_link_bulk", "oxygen_callables_manage"],
@@ -248,11 +256,11 @@ export const OXYGEN_CAPABILITY_ROUTES = [
248
256
  notFor: "Net-new LinkedIn initiation, cadence, enrollment, sender rotation, or reply-stop programs; those belong to Sequences.",
249
257
  execution: "Preview and approve a direct email or existing-thread reply; every sent fact stays in the hosted Messages ledger.",
250
258
  posture: "external_write",
251
- gatewayTools: ["oxygen_inbox_send", "oxygen_email_send", "oxygen_messages_query"],
252
- gatewayCommands: ["inbox send", "email send", "messages query"],
259
+ gatewayTools: ["oxygen_inbox_send", "oxygen_email_send", "oxygen_messages_query", "oxygen_unibox_metadata"],
260
+ gatewayCommands: ["inbox send", "email send", "messages query", "unibox metadata"],
253
261
  skills: ["oxygen-unibox"],
254
- endpointSections: ["email", "inbox", "message-reviews", "messages"],
255
- intentTerms: ["message", "messages", "reply", "email", "inbox", "conversation", "thread", "direct email", "draft reply", "one off"],
262
+ endpointSections: ["email", "inbox", "message-reviews", "messages", "unibox"],
263
+ intentTerms: ["message", "messages", "reply", "email", "inbox", "unibox", "inbox metadata", "conversation", "thread", "direct email", "draft reply", "one off"],
256
264
  },
257
265
  {
258
266
  id: "posts",
@@ -260,7 +268,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
260
268
  primitive: "posts",
261
269
  owns: "Broadcast post artifacts, author/channel identity, provider IDs, comments, reactions, and performance history.",
262
270
  notFor: "One-to-one conversations, delivery scheduling, or generic public LinkedIn provider search.",
263
- execution: "Read or create the hosted Post artifact; Publishing owns scheduled delivery.",
271
+ execution: "Read hosted posts and their comments or performance. For queued scheduling and review use Publishing. oxygen_posts_create is a direct LinkedIn publish: preview first, then approved=true publishes immediately outside the Publishing approval queue.",
264
272
  posture: "workspace_write",
265
273
  gatewayTools: ["oxygen_publishing_comments", "oxygen_publishing_analytics", "oxygen_posts_get", "oxygen_posts_create"],
266
274
  gatewayCommands: ["publishing comments list", "publishing analytics summary", "posts get", "posts create"],
@@ -368,7 +376,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
368
376
  primitive: "publishing",
369
377
  owns: "Approval-gated scheduling, dispatch, retries, review, and attempt provenance for broadcast Posts.",
370
378
  notFor: "One-to-one messaging, outreach campaigns, or general automation.",
371
- execution: "Create and schedule on OXYGEN; explicit approval releases the hosted publisher.",
379
+ execution: "Create a scheduled Publishing post, review it, then explicitly approve to release the hosted publisher. oxygen_posts_create instead publishes directly to LinkedIn after its own preview and approved=true; it does not enter this queue.",
372
380
  posture: "external_write",
373
381
  gatewayTools: ["oxygen_publishing_posts_create", "oxygen_publishing_posts_list", "oxygen_publishing_posts_approve", "oxygen_ugc_get"],
374
382
  gatewayCommands: ["publishing posts create", "publishing posts list", "publishing posts approve", "ugc programs list", "ugc programs duplicate", "ugc programs archive"],
@@ -429,8 +437,8 @@ export const OXYGEN_CAPABILITY_ROUTES = [
429
437
  notFor: "Enriching rows that already exist; use native table columns and waterfalls. Not for raw provider selection when a native gateway exists.",
430
438
  execution: "Plan or preview the hosted search, then run with an explicit scope and credit ceiling; hydrate one provider descriptor only when needed.",
431
439
  posture: "paid_read",
432
- gatewayTools: ["oxygen_search_plan", "oxygen_sourcing_plan", "oxygen_find_company", "oxygen_tools_search"],
433
- gatewayCommands: ["search plan", "sourcing plan", "find company", "tools search"],
440
+ gatewayTools: ["oxygen_companies_search_plan", "oxygen_people_search_plan", "oxygen_tools_search", "oxygen_find_company"],
441
+ gatewayCommands: ["companies search plan", "people search plan", "tools search", "find company"],
434
442
  skills: ["oxygen-gtm"],
435
443
  endpointSections: ["companies", "find", "lead-sourcing", "people", "search", "sourcing", "tools", "verify"],
436
444
  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"],
@@ -456,13 +464,13 @@ export const OXYGEN_CAPABILITY_ROUTES = [
456
464
  primitive: null,
457
465
  owns: "Connected SaaS accounts, OAuth/BYOK readiness, custom integration manifests, and provider actions that lack a native primitive gateway.",
458
466
  notFor: "Recreating native Tables, Messages, Sequences, Publishing, or Workflows through a generic provider action.",
459
- execution: "Inspect readiness, then connect or call the hosted integration under its exact write/spend gate.",
467
+ execution: "For provider API keys, inspect the connect schema and omit credentials for setup requirements with no provider call or storage. Submitting credentials creates or replaces the connection immediately. Cloudflare account selection uses top-level account_id; BounceBan validates before storage. Provider actions keep their exact write/spend gate.",
460
468
  posture: "mixed",
461
469
  gatewayTools: ["oxygen_integrations_list", "oxygen_integrations_connect", "oxygen_integrations_list_actions"],
462
470
  gatewayCommands: ["integrations list", "integrations connect", "custom-integrations list"],
463
471
  skills: ["oxygen-gtm"],
464
472
  endpointSections: ["custom-integrations", "integrations"],
465
- intentTerms: ["integration", "integrations", "connect provider", "oauth", "byok", "custom http", "custom integration", "connection readiness"],
473
+ intentTerms: ["integration", "integrations", "connect provider", "oauth", "byok", "bring your own key", "provider key", "provider connection", "custom http", "custom integration", "connection readiness"],
466
474
  },
467
475
  {
468
476
  id: "sending-infrastructure",
@@ -567,7 +575,9 @@ export function inferCapabilityRoute(query) {
567
575
  // The card's own recommendations are a pinned contract (tool order, no
568
576
  // exact command on plain enrichment asks); the catalogue path travels
569
577
  // beside them in `catalogIntent`, never inside them.
570
- const catalogue = catalogRow && CATALOGUE_CARD_IDS.has(card.id) ? catalogueRecommendations(catalogRow, card) : null;
578
+ // Sourcing filters belong to the planner, not per-company enrichment.
579
+ const catalogue = catalogRow && CATALOGUE_CARD_IDS.has(card.id) && !isCompanySourcingIntent(normalized)
580
+ ? catalogueRecommendations(catalogRow, card) : null;
571
581
  return {
572
582
  card: routeCardFor(card, normalized),
573
583
  recommendedTools: recommendations.tools,
@@ -636,7 +646,81 @@ export function inferPrimitiveRoute(query) {
636
646
  return null;
637
647
  return route;
638
648
  }
639
- export function serializeCapabilityRoute(route, surface = "cli") {
649
+ /** Current hosted instruction audiences, shared by discovery and Agent snapshots. */
650
+ export const OXYGEN_PRODUCT_SKILL_AUDIENCES = Object.freeze({
651
+ "oxygen-onboarding": "workspace",
652
+ "oxygen-gtm": "workspace",
653
+ "oxygen-recipes": "workspace",
654
+ "oxygen-playbooks": "workspace",
655
+ "oxygen-linkedin-marketing": "workspace",
656
+ "oxygen-agency-directory": "agency_partner",
657
+ "oxygen-collaboration": "workspace",
658
+ "oxygen-quickstart": "workspace",
659
+ "oxygen-workflow-authoring": "workspace",
660
+ "oxygen-knowledge": "workspace",
661
+ "oxygen-visual-design": "workspace",
662
+ "oxygen-unibox": "workspace",
663
+ "oxygen-email-infra": "workspace",
664
+ "oxygen-deliverability": "workspace",
665
+ "oxygen-sequencer": "workspace",
666
+ "oxygen-table-tidy": "workspace",
667
+ "oxygen-diagnostics": "workspace",
668
+ "oxygen-feedback": "workspace",
669
+ "oxygen-clay-migration": "workspace",
670
+ "oxygen-ugc": "workspace",
671
+ });
672
+ export function canReadProductSkillInstructions(name, entitlements) {
673
+ if (entitlements.authenticated !== true)
674
+ return false;
675
+ const audience = Object.hasOwn(OXYGEN_PRODUCT_SKILL_AUDIENCES, name)
676
+ ? OXYGEN_PRODUCT_SKILL_AUDIENCES[name]
677
+ : undefined;
678
+ return audience === "workspace" || (audience === "agency_partner" && entitlements.agencyPartner);
679
+ }
680
+ /** Identify hosted instruction bodies by native tool lineage, never by user prose. */
681
+ export function readProductSkillInstructionResult(capabilityName, result) {
682
+ if (capabilityName !== "oxygen_skills_get")
683
+ return null;
684
+ const pending = [result];
685
+ for (let i = 0; i < pending.length && i < 24; i += 1) {
686
+ let value = pending[i];
687
+ if (typeof value === "string") {
688
+ try {
689
+ value = JSON.parse(value);
690
+ }
691
+ catch {
692
+ continue;
693
+ }
694
+ }
695
+ if (value === null || typeof value !== "object" || Array.isArray(value))
696
+ continue;
697
+ const envelope = value;
698
+ if (envelope.ok === false || envelope.isError === true || envelope.status === "error")
699
+ continue;
700
+ if (typeof envelope.content === "string") {
701
+ const name = typeof envelope.name === "string" ? envelope.name : "";
702
+ return { slug: Object.hasOwn(OXYGEN_PRODUCT_SKILL_AUDIENCES, name) ? name : null };
703
+ }
704
+ for (const key of ["result", "data", "structuredContent", "structured"]) {
705
+ if (envelope[key] !== undefined)
706
+ pending.push(envelope[key]);
707
+ }
708
+ if (Array.isArray(envelope.content)) {
709
+ for (const block of envelope.content) {
710
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string")
711
+ pending.push(block.text);
712
+ }
713
+ }
714
+ }
715
+ return null;
716
+ }
717
+ /** Public action cards never advertise invite-only or unknown skill instructions. */
718
+ export function isPublicCapabilitySkill(name) {
719
+ return Object.hasOwn(OXYGEN_PRODUCT_SKILL_AUDIENCES, name) && OXYGEN_PRODUCT_SKILL_AUDIENCES[name] === "workspace";
720
+ }
721
+ export function serializeCapabilityRoute(route, surfaceOrOptions = "cli", options = {}) {
722
+ const surface = typeof surfaceOrOptions === "string" ? surfaceOrOptions : "cli";
723
+ const visibleSkills = typeof surfaceOrOptions === "string" ? options.visibleSkills : surfaceOrOptions.visibleSkills;
640
724
  if (!route)
641
725
  return null;
642
726
  return {
@@ -647,7 +731,7 @@ export function serializeCapabilityRoute(route, surface = "cli") {
647
731
  not_for: route.card.notFor,
648
732
  execution: route.card.execution,
649
733
  posture: route.card.posture,
650
- skills: route.card.skills,
734
+ skills: route.card.skills.filter((name) => isPublicCapabilitySkill(name) || visibleSkills?.includes(name)),
651
735
  endpoint_sections: route.card.endpointSections,
652
736
  recommended_commands: route.recommendedCommands,
653
737
  recommended_tools: route.recommendedTools,
@@ -672,6 +756,8 @@ export function serializeCapabilityRoute(route, surface = "cli") {
672
756
  hydrate: {
673
757
  cli: "oxygen commands get <exact-command> --json",
674
758
  mcp: "oxygen_capabilities_schema",
759
+ skill_cli: "oxygen skills get <selected-skill> --json",
760
+ skill_mcp: { tool: "oxygen_skills_get", arguments: { name: "<selected-skill>" } },
675
761
  },
676
762
  };
677
763
  }
@@ -764,8 +850,78 @@ function routeCardFor(card, query) {
764
850
  if (card.primitive === "publishing" && isUgcIntent(query))
765
851
  return { ...card, ...UGC_ROUTE_VIEW };
766
852
  if (card.id === "knowledge-graph" && isWorkspaceVoiceIntent(query))
767
- return { ...card, execution: WORKSPACE_VOICE_EXECUTION };
768
- return card;
853
+ card = { ...card, execution: WORKSPACE_VOICE_EXECUTION };
854
+ if (card.primitive === "workflows" && isRecurringCompanyMonitoringIntent(query))
855
+ card = {
856
+ ...card,
857
+ execution: "Workflows own recurring execution. Inspect oxygen_blueprints_list, describe and preflight before authoring; a matching Blueprint is not guaranteed. Retain canonical company identities and facts in Records, and time-stamped observations in Signals. Use oxygen_crm_objects_list and oxygen_signals_list to inspect those destinations. Do not apply or enable automation before the required approval and credit scope.",
858
+ };
859
+ if (card.primitive === "tables" && isTableCapacityReadIntent(query))
860
+ card = {
861
+ ...card,
862
+ execution: "Read oxygen_limits_show for Tables capacity, largest_table and largest_table_rows. Use that returned table reference with oxygen_tables_query and a small limit to inspect sample rows; no enrichment run is needed.",
863
+ };
864
+ if (card.primitive === "tables" && isFunctionIntent(query))
865
+ card = {
866
+ ...card,
867
+ execution: "List, describe or inspect Function runs without executing. Draft edits are isolated; publish requires the inspected draft hash and changes future invocations. Bind adds a caller column for free; columns run previews cost before a bounded approved run. MCP uses the mixed-effect oxygen_callables_manage through oxygen_capabilities_call, including its read operations.",
868
+ };
869
+ if (card.primitive === "tables" && isPlainTableIntent(query))
870
+ card = {
871
+ ...card,
872
+ execution: "Create a workspace-private Table or inspect its schema and rows. Manual rows, column labels, visibility and member favorites cost 0 credits. Change only the display label with columns update (oxygen_columns_update); columns rename (oxygen_columns_rename) requires a new stable key. Favorite one table or folder with tables favorite (oxygen_tables_favorite), then read tables favorites (oxygen_tables_favorites). For tidy guidance, read oxygen-table-tidy with skills get (oxygen_skills_get); tables tidy-suggest (oxygen_tables_tidy_suggest) analyzes without changes. Read the owning schema, apply the requested scope, then describe or query the Table to verify it.",
873
+ };
874
+ const maintenance = card.primitive === "tables" ? tableMaintenanceRecommendation(query) : null;
875
+ if (maintenance)
876
+ card = { ...card, execution: maintenance.execution };
877
+ const read = card.primitive === "tables" ? tableReadRecommendation(query) : null;
878
+ if (read)
879
+ card = { ...card, execution: read.execution };
880
+ const database = card.id === "platform-operations" ? databaseRecommendation(query) : null;
881
+ if (database)
882
+ card = { ...card,
883
+ owns: "Active-workspace PostgreSQL hosting, attachment, migrations, read-only SQL and retired cost-policy status.",
884
+ notFor: "Provider credentials, enrichment, workspace billing, or staff-wide database operations.",
885
+ execution: database.execution,
886
+ };
887
+ const specializedSkill = preferredSkillForIntent(card, query);
888
+ return specializedSkill ? { ...card, skills: [specializedSkill, ...card.skills.filter((skill) => skill !== specializedSkill)] } : card;
889
+ }
890
+ /** Search surfaces share the selected skill owner; a general router is fallback. */
891
+ function preferredSkillForIntent(card, query) {
892
+ if (card.id === "tables") {
893
+ if (/\bclay\b/.test(query) && /\b(import|export|migrat\w*|mov\w*)\b/.test(query))
894
+ return "oxygen-clay-migration";
895
+ if (isTableMaintenanceIntent(query))
896
+ return "oxygen-table-tidy";
897
+ if (/\b(stuck|failed|failure|trace|provenance|diagnos\w*)\b/.test(query))
898
+ return "oxygen-diagnostics";
899
+ if (isLinkedInProfileWatcherIntent(query))
900
+ return "oxygen-linkedin-marketing";
901
+ }
902
+ if (card.id === "billing-and-budgets" && /\b(explain|why|reduce|increas\w*|burn|spent)\b/.test(query))
903
+ return "oxygen-diagnostics";
904
+ if (card.id === "sending-infrastructure" && /\b(bounc\w*|health|placement|deliverability|spam)\b/.test(query))
905
+ return "oxygen-deliverability";
906
+ if (["signals", "linkedin-public-research"].includes(card.id) && /\blinkedin\b/.test(query))
907
+ return "oxygen-linkedin-marketing";
908
+ return null;
909
+ }
910
+ function isTableMaintenanceIntent(query) {
911
+ return /\b(tables?|columns?|folders?)\b/.test(query)
912
+ && !/\b(functions?|callables?|campaigns?|tags?|workflows?|agents?)\b/.test(query)
913
+ && (/\b(tidy|tidying|cleanup|clean up|reorder|formatting|duplicates?|favou?rites?|unfavou?rite|star|unstar)\b/.test(query)
914
+ || (/\bcolumns?\b/.test(query) && /\b(hide|hidden|rename|renaming|label|key)\b/.test(query)));
915
+ }
916
+ function isPlainTableIntent(query) {
917
+ if (isTableCapacityReadIntent(query))
918
+ return false;
919
+ return isTableMaintenanceIntent(query) || (/\btable\b/.test(query)
920
+ && /\b(create|manual|sample|private)\b/.test(query)
921
+ && !/\b(enrich\w*|classif\w*|research|prompt|sourc\w*|import|linkedin|function|callable)\b/.test(query));
922
+ }
923
+ function isFunctionIntent(query) {
924
+ return /\b(functions?|callables?)\b/.test(query) && !/\bformula functions?\b/.test(query);
769
925
  }
770
926
  function isLinkedInKeywordPostIntent(query) {
771
927
  return /\blinkedin\b/.test(query)
@@ -829,6 +985,11 @@ export const SEQUENCE_UNSUBSCRIBE_COMMAND = "oxygen sequences update <sequence>
829
985
  */
830
986
  export const CAPABILITY_ROUTE_MCP_CALLS = {
831
987
  connectedAppsList: () => ({ tool: "oxygen_connector_list", arguments: {} }),
988
+ connectedAppEvents: () => ({ tool: "oxygen_connector_events_get", arguments: { connection: "<app>" } }),
989
+ workspacePagesList: () => ({ tool: "oxygen_settings_pages_get", arguments: {} }),
990
+ functionsList: () => ({ tool: "oxygen_callables_manage", arguments: { operation: "list_functions" } }),
991
+ functionsDescribe: () => ({ tool: "oxygen_callables_manage", arguments: { operation: "describe", function: "<function>" } }),
992
+ functionsRuns: () => ({ tool: "oxygen_callables_manage", arguments: { operation: "runs", function: "<function>" } }),
832
993
  sequenceSendingSchedule: () => ({
833
994
  tool: "oxygen_sequences_update",
834
995
  arguments: { sequence: "<sequence>", sending_schedule: { timezone: "<IANA zone>", days: [1, 2, 3, 4, 5], start: "09:00", end: "17:00" } },
@@ -876,6 +1037,19 @@ function isSequenceScheduleIntent(query) {
876
1037
  return /\bsend(?:ing)? (?:schedule|window)s?\b/.test(query)
877
1038
  && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query);
878
1039
  }
1040
+ // How much campaign email one inbox may send: its daily campaign limit and the
1041
+ // ramp-up that climbs to it. The 2026-10-01 MCP baseline searched "mailbox
1042
+ // sending settings warmup ramp daily limit" and was routed to Workflows (bare
1043
+ // "daily" reads as a schedule); "ramp up a new inbox" went to Messages. LinkedIn,
1044
+ // WhatsApp and call limits keep their own owners, and provider warm-up without
1045
+ // campaign volume words keeps the card's warm-up path.
1046
+ function isMailboxVolumeIntent(query) {
1047
+ if (/\b(linkedin|whatsapp|calls?|dial\w*|voice)\b/.test(query))
1048
+ return false;
1049
+ const inbox = /\b(mailbox\w*|inbox(?:es)?|email accounts?|sending accounts?|sender accounts?)\b/.test(query);
1050
+ const volume = /\b(ramp\w*|daily (?:campaign )?(?:limit|cap)s?|sending limits?|send limits?|(?:emails?|sends?) (?:per|a) day|volume)\b/.test(query);
1051
+ return inbox && volume;
1052
+ }
879
1053
  // The unsubscribe option on a Sequence's emails, not the list of people who
880
1054
  // opted out: a list, import or do-not-contact ask stays with suppressions.
881
1055
  function isSequenceUnsubscribeIntent(query) {
@@ -939,6 +1113,12 @@ function isOpenLinkedInRequestsIntent(query) {
939
1113
  function isConnectedAppsIntent(query) {
940
1114
  if (/\bconnected apps?\b/.test(query))
941
1115
  return true;
1116
+ // The command group's own noun. Tables' "connect" term prefix-matches it, so
1117
+ // `commands search connectors` returned no `connectors` command at all
1118
+ // (CLI acceptance judge, 2026-09-30). Bare noun only: a provider's connector
1119
+ // ("hubspot connector") is never claimed here.
1120
+ if (/^(?:(?:list|show|see|my|our|all|the|mcp|app|apps|ai|oauth)\s+)*connectors?$/.test(query))
1121
+ return true;
942
1122
  const connection = /\b(oauth (?:apps?|clients?|connections?|grants?)|mcp (?:clients?|connections?|connectors?))\b/.test(query);
943
1123
  const app = connection || /\b(chatgpt|claude|codex|ai (?:apps?|assistants?|clients?))\b/.test(query);
944
1124
  if (!app)
@@ -949,9 +1129,230 @@ function isConnectedAppsIntent(query) {
949
1129
  && /\b(default|always|without asking|which|per (?:app|connection|client))\b/.test(query);
950
1130
  return workspaceChoice || (connection && /\b(list|show|see|which|my)\b/.test(query));
951
1131
  }
1132
+ /**
1133
+ * What OXYGEN sends a connected app (MCP Events, ADR 0030 §10): "what is
1134
+ * ChatGPT subscribed to", "what did OXYGEN send it", "stop it". Both blind
1135
+ * baselines of 2026-09-30 (OXP-11.5.22) searched exactly these words — connector
1136
+ * event subscriptions, deliveries, webhook, notifications, push — and were
1137
+ * handed the inbound provider-webhook tools instead. A billing subscription or a
1138
+ * provider's events ("hubspot webhook") never matches.
1139
+ */
1140
+ function isConnectedAppEventsIntent(query) {
1141
+ if (/\b(plans?|billing|invoices?|credits?|upgrade|downgrade|stripe|pricing|price)\b/.test(query))
1142
+ return false;
1143
+ if (/\b(hubspot|slack|gmail|calendly|cal\.?com|salesforce|pipedrive|zapier|integrations?|providers?|workflows?|triggers?)\b/.test(query))
1144
+ return false;
1145
+ const app = /\b(chatgpt|claude|codex|connected apps?|connectors?|connections?|apps?|ai (?:assistants?|clients?)|mcp)\b/.test(query);
1146
+ const events = /\b(subscri\w*|events?|webhooks?|notif\w*|push(?:es|ed)?|deliver(?:y|ies|ed)?|sen[dt]s?)\b/.test(query);
1147
+ if (!app || !events)
1148
+ return false;
1149
+ // "send a LinkedIn message from my app" is outreach, not what OXYGEN sends the app.
1150
+ return !/\b(linkedin|email|emails|whatsapp|message|messages|sequence|sequences|campaign|outreach|post|posts)\b/.test(query);
1151
+ }
1152
+ /**
1153
+ * What a connected app may spend (ADR 0030 §11): "how much can ChatGPT spend on
1154
+ * its own in a day", "cap it at 300 credits". Both blind baselines of
1155
+ * 2026-09-30 (OXP-11.5.23) searched for a per-connection daily credit limit and
1156
+ * found only workspace budgets. A sender's or channel's daily limit is not this.
1157
+ */
1158
+ function isConnectedAppSpendIntent(query) {
1159
+ const app = /\b(chatgpt|claude|codex|connected apps?|connectors?|connections?|per[- ]connection|mcp)\b/.test(query);
1160
+ const spend = /\b(spend\w*|credits?|budgets?|ceilings?|caps?|capped|limits?)\b/.test(query);
1161
+ if (!app || !spend)
1162
+ return false;
1163
+ return !/\b(linkedin|senders?|mailbox(?:es)?|sequences?|email|emails|whatsapp|warm\s?up|invites?|rate)\b/.test(query);
1164
+ }
1165
+ /**
1166
+ * The web app's sidebar pages a workspace hides for every member (Settings →
1167
+ * Customise, `settings pages`). Both blind baselines of 2026-09-30 searched
1168
+ * "hide navigation pages sidebar", "customise pages", "pages visibility", "nav
1169
+ * items" and "workspace navigation hidden pages unibox publishing workflows",
1170
+ * and were routed to workspace access, Publishing or nowhere. A sidebar or
1171
+ * navigation noun is enough on its own; "pages" plus a hide/show word is not
1172
+ * when the pages are wiki, LinkedIn, web, PDF or result pages.
1173
+ */
1174
+ function isWorkspacePagesIntent(query) {
1175
+ // A row, column or cell is content inside a page, and Sales Nav is LinkedIn.
1176
+ if (/\bsales ?nav/.test(query) || /\b(rows?|columns?|cells?|filters?|duplicates?)\b/.test(query))
1177
+ return false;
1178
+ const navigation = /\b(side ?bars?|side ?nav\w*|side ?menus?|nav ?bars?|nav|navigation|left (?:menu|nav\w*|rail|panel)|main menu)\b/.test(query);
1179
+ const visibility = /\b(hid(?:e|es|den|ing)|unhide|visib\w*|invisible)\b/.test(query);
1180
+ const showInNavigation = /\bshow(?:s|n|ing)?\b/.test(query)
1181
+ && /\b(?:in|on|from|to)\s+(?:the |our |my |this )?(?:side ?bar|side ?nav\w*|navigation|nav|left (?:menu|nav\w*|rail|panel))\b/.test(query);
1182
+ // Explicit sidebar visibility may name the page's primitive: hide Knowledge
1183
+ // from the sidebar or show Tables in navigation still means Customise.
1184
+ if (navigation && (visibility || showInNavigation))
1185
+ return true;
1186
+ if (/\bmenu items?\b/.test(query) && /\b(hid(?:e|es|den|ing)|unhide|show(?:s|n|ing)?|visib\w*)\b/.test(query))
1187
+ return true;
1188
+ // Content and navigation within a primitive keep that primitive's owner.
1189
+ // Check this before generic "customize pages" or a navigation noun.
1190
+ if (/\b(wiki|knowledge|linkedin|compan(?:y|ies)|tables?|landing|web ?pages?|websites?|carousels?|pdfs?|paginat\w*|next page|per page|page size|results?|dashboards?)\b/.test(query))
1191
+ return false;
1192
+ if (navigation)
1193
+ return true;
1194
+ if (/\bcustomi[sz](?:e|es|ed|ing|ation)\b/.test(query) && /\b(pages?|menus?)\b/.test(query))
1195
+ return true;
1196
+ if (/^customi[sz]e$|\bsettings?\W{1,5}customi[sz]e\b/.test(query))
1197
+ return true;
1198
+ return /\bpages?\b/.test(query) && /\b(hid(?:e|es|den|ing)|unhide|show(?:s|n|ing)?|visib\w*|invisible)\b/.test(query);
1199
+ }
1200
+ /**
1201
+ * "Organisation settings" (the web's own name for /settings), "workspace
1202
+ * settings", "org settings": the CLI and MCP half of those settings is the
1203
+ * sidebar pages pair. A settings ask that names its own owner (billing, API
1204
+ * keys, members, a connected app, a provider) keeps that owner.
1205
+ */
1206
+ function isWorkspaceSettingsIntent(query) {
1207
+ if (/\b(billing|plans?|credits?|invoices?|api keys?|members?|invit\w*|seats?|roles?|connected apps?|connectors?|chatgpt|claude|codex|integrations?|providers?|mailbox(?:es)?|senders?|sequences?|linkedin)\b/.test(query))
1208
+ return false;
1209
+ return /\b(workspace|organi[sz]ations?|orgs?|team)\s+settings?\b/.test(query)
1210
+ || /\bsettings?\s+(?:for|of)\s+(?:the |my |our |this )?(workspace|organi[sz]ation|org|team)\b/.test(query);
1211
+ }
1212
+ function isTableCapacityReadIntent(query) {
1213
+ if (/\b(monitor|watch|workflow|automate|automation|increase|resize|upgrade)\b/.test(query))
1214
+ return false;
1215
+ return /\b(largest|biggest) tables?\b/.test(query)
1216
+ || /\btables?\b.{0,30}\b(sizes?|row counts?)\b/.test(query)
1217
+ || /\b(sizes?|row counts?)\b.{0,30}\btables?\b/.test(query);
1218
+ }
1219
+ function isCompanySourcingIntent(query) {
1220
+ if (/\b(enrich\w*|existing|these|those|rows?|columns?|verify|verification|emails?|phones?|contacts?|people|persons?)\b/.test(query))
1221
+ return false;
1222
+ return /\b(companies|businesses|firms|company search|company sourcing)\b/.test(query)
1223
+ && /\b(search|plan|find|source|sourcing|discover|build|tam|prospect)\b/.test(query);
1224
+ }
1225
+ /** Reads of grid values/state are separate from executing or resolving actions. */
1226
+ function tableReadRecommendation(query) {
1227
+ if (!/\btables?\b/.test(query) || isFunctionIntent(query) || isSecondPartyDecisionIntent(query)
1228
+ || /\b(workflows?|agents?|actions?|items?|retry|resolve|effects?|imports?|webhooks?|feeds?|enrich\w*|create|add|insert|upsert|update|delete|remove|rename|hide|archive|columns? run)\b/.test(query)
1229
+ || /\b(run|execute|start|cancel)\s+(?:(?:the|my|these)\s+)?(?:columns?|table|enrichment)\b/.test(query))
1230
+ return null;
1231
+ if (/\bcells?\b/.test(query) && /\b(history|changes?|provenance)\b/.test(query))
1232
+ return {
1233
+ tools: ["oxygen_tables_cell_history", "oxygen_tables_cell_inspect"], commands: ["cells history", "cells inspect"],
1234
+ execution: "Read one cell's recorded value changes with cells history; cells inspect adds its provider attempt timeline. These reads do not run columns or resolve an uncertain external effect.",
1235
+ };
1236
+ if (/\bcells?\b/.test(query) && /\b(provider|attempts?|timeline)\b/.test(query))
1237
+ return {
1238
+ tools: ["oxygen_tables_cell_inspect", "oxygen_tables_cell_history"], commands: ["cells inspect", "cells history"],
1239
+ execution: "Inspect one cell's final value and provider attempt timeline with cells inspect. Cell history reads recorded changes; neither executes a column or records a verified external outcome.",
1240
+ };
1241
+ if (isTableCapacityReadIntent(query) && !/\bgrid\b/.test(query))
1242
+ return null;
1243
+ if (!/\b(grid|preview|raw rows|fill rates?|next page|pagination|cursor)\b/.test(query)
1244
+ && !(/\b(read|show|inspect|query)\b/.test(query) && /\brows?\b/.test(query)))
1245
+ return null;
1246
+ if (/\b(filtered?|filters?|sorts?|search|crm)\b/.test(query) && /\b(grid|counts?|registry)\b/.test(query))
1247
+ return {
1248
+ tools: ["oxygen_table_rows_page", "oxygen_tables_query", "oxygen_tables_preview"], commands: ["tables rows page", "tables query", "tables preview"],
1249
+ execution: "Read the native rows page for grid filters/sorts/search and count modes. exact counts the matching rows; estimate uses reconciled registry metadata only for an unfiltered scope, otherwise counts exactly. registry never counts and returns no total for a narrowed or unavailable registry scope; skip returns no total. Follow nextCursor with the same table, filters, search, sort and CRM scope; do not substitute an offset. Canonical tables query reads raw values; format=grid refuses those filters/projections.",
1250
+ };
1251
+ if (/\b(preview|fill rates?|without row|summary only|summary_only)\b/.test(query))
1252
+ return {
1253
+ tools: ["oxygen_tables_preview", "oxygen_tables_query", "oxygen_table_fill_progress"], commands: ["tables preview", "tables query", "tables fill-progress"],
1254
+ execution: "Use tables preview for compact rows, optional include_cell_states and whole-table fill stats; summary_only omits row JSON. rowCount is exact, rates may be sampled and summary.statsSampled labels that scope. Follow its nextCursor with the same options. tables query reads raw values or format=grid previews/state. tables fill-progress separately reports ingestion progress/failure; none of these runs columns.",
1255
+ };
1256
+ return {
1257
+ tools: ["oxygen_tables_query", "oxygen_tables_preview", "oxygen_table_rows_page", "oxygen_table_fill_progress"], commands: ["tables query", "tables preview", "tables rows page", "tables fill-progress"],
1258
+ execution: 'Read tables query raw values, or format="grid" for summarized cell previews and per-cell state. Grid defaults to 50 rows and caps at 200; pass grid.nextCursor as cursor with the same table/limit. Grid refuses row_id, fields, filters/filterTree, search, sorts, formula_values and nonzero offset; it is not a snapshot. Its first page uses the reconciled registry count when available, otherwise exact; continuation skips counting (null total), labeled rowCountMode. include_stats adds optional whole-table column stats/count; unavailable stats are omitted. include_fill only adds optional fill state when the first page is empty; later/nonempty pages omit it. tables preview supplies fill-rate summaries; tables fill-progress reads ingestion status. These reads do not execute columns.',
1259
+ };
1260
+ }
1261
+ /** Native database work outranks generic workspace access and credit billing. */
1262
+ function databaseRecommendation(query) {
1263
+ if (!/\b(databases?|postgres(?:ql)?|db|sql)\b/.test(query)
1264
+ || /\b(tables?|grid|external|import|workflows?|agents?|enrich\w*|providers?|integrations?|oauth|byok|mailbox\w*|crm|hubspot|salesforce|supabase)\b/.test(query))
1265
+ return null;
1266
+ const actions = [
1267
+ ["oxygen_workspace_database_status", "db status"], ["oxygen_database_cost_policy", "db cost-policy"],
1268
+ ["oxygen_database_query", "db query"], ["oxygen_database_attach", "db attach"],
1269
+ ["oxygen_database_migrate", "db migrate"], ["oxygen_workspace_database_provision", "db provision"],
1270
+ ["oxygen_database_cost_policy_reconcile", "db reconcile-cost-policy"],
1271
+ ];
1272
+ const first = /\b(migrat\w*|rotate credentials)\b/.test(query) ? 4
1273
+ : /\b(attach|connect|connection)\b/.test(query) ? 3
1274
+ : /\b(query|sql|select|explain)\b/.test(query) ? 2
1275
+ : /\b(provision\w*)\b/.test(query) ? 5
1276
+ : /\breconcile\b/.test(query) ? 6
1277
+ : /\b(cost policy|cost controls?|sleep|suspend|reconcile)\b/.test(query) && !/\bstatus\b/.test(query) ? 1 : 0;
1278
+ const ordered = [actions[first], ...actions.filter((_, index) => index !== first)];
1279
+ return {
1280
+ tools: ordered.map(([tool]) => tool), commands: ordered.map(([, command]) => command),
1281
+ execution: "Inspect db status and db cost-policy for the active workspace. Hosting is included; Neon cost controls are retired, and reconcile-cost-policy is a compatibility no-op, not PostgreSQL sleep control. db query accepts one read-only SELECT/WITH/EXPLAIN statement: request a small max_rows (hard cap 1000). Writes are refused; truncated labels the returned row window, and it has no continuation cursor. Attach (database_url), migrate (optional rotate_credentials) and provision are immediate native writes with no preview or approved field. They preserve workspace identity/access; restricted clients can read status/cost/query but cannot configure. Built-in approval policy is separate from these HTTP fields.",
1282
+ };
1283
+ }
1284
+ /** Direct Table operations keep their native gateway ahead of general authoring.
1285
+ * Explicit Workflow triggers, mailbox imports and CRM attachments retain their owners. */
1286
+ function tableMaintenanceRecommendation(query) {
1287
+ if (isFunctionIntent(query) || isSecondPartyDecisionIntent(query)
1288
+ || /\b(workflows?|agents?|campaigns?|tags?|mailbox\w*|crm|attachments?|chatgpt|connected apps?)\b/.test(query)
1289
+ || (isTableMaintenanceIntent(query) && /\b(skills?|instructions?|guidance)\b/.test(query)))
1290
+ return null;
1291
+ const table = /\btables?\b/.test(query);
1292
+ const result = (tools, commands, execution) => ({ tools, commands, execution });
1293
+ if (/\b(favou?rites?|starred|star|unstar)\b/.test(query) && /\b(tables?|folders?)\b/.test(query)) {
1294
+ const mutate = !/\bstar unstar\b/.test(query) && (/\b(unstar|unfavou?rite)\b/.test(query) || (/\b(favou?rite|star)\b/.test(query) && !/\b(show|list|read|see|favorites|favourites|star unstar)\b/.test(query)));
1295
+ return result(mutate ? ["oxygen_tables_favorite", "oxygen_tables_favorites"] : ["oxygen_tables_favorites", "oxygen_tables_favorite"], mutate ? ["tables favorite", "tables favorites"] : ["tables favorites", "tables favorite"], "Read your member-specific Table/folder stars, then change exactly one table or folder with favorite=true or false. This costs 0 credits. Tool-catalog favorites are separate.");
1296
+ }
1297
+ if (table && /\b(webhooks?|inbound.*endpoint|push.*endpoint|endpoint.*(?:inbound|push))\b/.test(query)) {
1298
+ if (/\bdeliver\w*\b/.test(query))
1299
+ return result(["oxygen_table_webhook_deliveries", "oxygen_table_webhook_get"], ["tables webhook deliveries", "tables webhook get"], "Inspect this Table endpoint and its delivery ledger. A direct Table webhook lands rows; a Workflow webhook starts a published graph.");
1300
+ if (/\b(change|edit|update|rotat\w*|pause|disable|clear)\b/.test(query) && !/\bcreate\b/.test(query))
1301
+ return result(["oxygen_table_webhook_update", "oxygen_table_webhook_get"], ["tables webhook update", "tables webhook get"], "Inspect the endpoint, then update it by endpoint_id. auth_mode=secret rotates the secret and returns it once. auto_run is standing permission; delivery ceilings are explicit or plan-default. Preserve required authorization.");
1302
+ if (/\b(create|add|new|inbound)\b/.test(query) && !/\b(inspect|show|list|read)\b/.test(query))
1303
+ return result(["oxygen_table_webhook_create", "oxygen_table_webhooks_list"], ["tables webhook create", "tables webhook list"], "Create a direct inbound Table endpoint to insert or upsert rows. Inspect configured columns before setting auto_run: it is standing permission, with explicit or plan-default delivery ceilings. Workflows own graph triggers.");
1304
+ return result(["oxygen_table_webhooks_list", "oxygen_table_webhook_get", "oxygen_table_webhook_deliveries"], ["tables webhook list", "tables webhook get", "tables webhook deliveries"], "Inspect direct Table endpoints and their deliveries. Use oxygen_table_webhook_create/update for changes; auth_mode=secret rotates protection. Workflows own graph triggers.");
1305
+ }
1306
+ if (table && /\bfeeds?\b/.test(query)) {
1307
+ return /\b(bind|attach|create|new)\b/.test(query)
1308
+ ? result(["oxygen_feeds_bind", "oxygen_feeds_list"], ["feeds bind", "feeds list"], "Bind the native Table feed: a declared-schema push source or scheduled provider pull. Inspect source, cadence and standing credit scope first; a pull feed is not an inbound webhook endpoint.")
1309
+ : result(["oxygen_feeds_list", "oxygen_feeds_get"], ["feeds list", "feeds get"], "Inspect the native Table feed's source, cadence, caps and state. Pull feeds refill Tables on their own schedule. The current MCP catalog has no general cadence-update tool; do not substitute a Workflow or generic HTTP call.");
1310
+ }
1311
+ const file = /\b(csv|jsonl?|xlsx|excel|spreadsheet|staged|uploaded|file)\b/.test(query)
1312
+ || /\b(?:pending|stuck|abandoned) imports?\b|\bimports?\b.{0,30}\b(pending|stuck|recover\w*)\b/.test(query);
1313
+ if (file && /\b(import\w*|ingestion|upload\w*|preview|columns|sample|chunks)\b/.test(query)) {
1314
+ if (/\b(pending|leased|failed|recover|chunks)\b/.test(query))
1315
+ return result(/\b(chunks|items)\b/.test(query) ? ["oxygen_table_ingestions_items", "oxygen_table_ingestions_get"] : ["oxygen_table_ingestions_get", "oxygen_table_ingestions_items"], ["table-ingestions get", "table-ingestions items"], "Inspect the durable file ingestion and its chunks. Fix the cause before retry-failed; pending or leased chunks are not verified external outcomes. recover_pending polls existing BetterContact requests, not file imports.");
1316
+ if (/\b(url|https?)\b/.test(query))
1317
+ return result(["oxygen_tables_import_url"], ["tables import"], "Use the native public-file URL preview/import tool through the call gateway even for preview. Importing into an existing Table can fire its standing import auto-run; preview and new unarmed targets do not authorize later paid or external actions.");
1318
+ if (/\b(preview|inspect|columns|sample|sheet)\b/.test(query))
1319
+ return result(["oxygen_tables_import_preview", "oxygen_tables_import_staged"], ["tables import-preview", "tables import-staged"], "Inspect the owned staged file's worksheet, complete columns and sample before importing. Keep the selected sheet/mapping intact. Import into an existing Table can fire its standing import auto-run; preview grants no spend authority.");
1320
+ if (/\b(prepare|upload)\b/.test(query))
1321
+ return result(["oxygen_tables_import_upload", "oxygen_tables_import_preview", "oxygen_tables_import_staged"], ["tables import-upload", "tables import-preview", "tables import-staged"], "Prepare an organization-owned upload, transfer bytes to the returned presigned URL, then inspect and import that storage key. Preparing the upload creates no Table/run. Public URL imports use oxygen_tables_import_url.");
1322
+ return result(["oxygen_tables_import_staged", "oxygen_tables_import_preview"], ["tables import-staged", "tables import-preview"], "Import the inspected owned staged file with its selected worksheet/mapping. Existing targets can fire standing import auto-run; a new target has no pre-existing armed columns. Preserve exact authorization and downstream credit/effect guards.");
1323
+ }
1324
+ if (table && /\b(actions?|cells?|items?|runs?)\b/.test(query)) {
1325
+ if (/\b(verified|confirmed)\b/.test(query) && /\b(record|resolve)\b/.test(query) && !/\b(inspect|list|show|read)\b/.test(query))
1326
+ return /\bcells?\b/.test(query)
1327
+ ? result(["oxygen_table_cells_resolve", "oxygen_table_runs_effect_unknown"], ["cells resolve", "table-runs effect-unknown"], "After verifying the external destination, record applied or not_applied by Table cell coordinates. This records an outcome without repeating the effect; retry remains a separate guarded action.")
1328
+ : result(["oxygen_table_action_item_resolve", "oxygen_table_runs_effect_unknown"], ["table-actions resolve", "table-runs effect-unknown"], "After verifying the external destination, record applied or not_applied by action item_id. This records an outcome without repeating the effect; retry remains a separate guarded action.");
1329
+ if (/\b(effect.*unknown|uncertain|verified outcome|blocked.*resolve)\b/.test(query))
1330
+ return result(["oxygen_table_runs_effect_unknown", "oxygen_table_runs_get", "oxygen_table_runs_items"], ["table-runs effect-unknown", "table-runs get", "table-runs items"], "Inspect exact effect-unknown items and verify the external destination. Only then record a verdict with oxygen_table_action_item_resolve by item_id or oxygen_table_cells_resolve by coordinates. Pending/failed items are not proof; retry is separate.");
1331
+ return result(["oxygen_table_action_runs_list", "oxygen_table_runs_get", "oxygen_table_runs_items"], ["table-runs list", "table-runs get", "table-runs items"], "Find the Table's durable action run, then inspect its status and items. Pending, leased and failed are distinct from effect-unknown; do not record a verified verdict or retry an external effect without evidence.");
1332
+ }
1333
+ return null;
1334
+ }
952
1335
  function explicitCapabilityIntent(query) {
953
- if (isConnectedAppsIntent(query))
1336
+ if (databaseRecommendation(query))
1337
+ return ROUTE_BY_ID.get("platform-operations") ?? null;
1338
+ if (isConnectedAppsIntent(query) || isConnectedAppEventsIntent(query) || isConnectedAppSpendIntent(query)) {
954
1339
  return ROUTE_BY_ID.get("workspace-access") ?? null;
1340
+ }
1341
+ // Ahead of every primitive rule: the pages a workspace hides are NAMED after
1342
+ // primitives ("hide Publishing and Workflows"), which those rules would claim.
1343
+ if (isWorkspacePagesIntent(query) || isWorkspaceSettingsIntent(query)) {
1344
+ return ROUTE_BY_ID.get("workspace-access") ?? null;
1345
+ }
1346
+ if (/\btables?\b/.test(query) && isSecondPartyDecisionIntent(query))
1347
+ return ROUTE_BY_ID.get("collaboration") ?? null;
1348
+ if (tableReadRecommendation(query) || tableMaintenanceRecommendation(query))
1349
+ return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
1350
+ if (/\btables?\b/.test(query) && /\b(navigation|nav|paging|pagination)\b/.test(query)) {
1351
+ return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
1352
+ }
1353
+ if (/\b(unibox|inbox)\b/.test(query) && /\b(metadata|taxonomy|filters)\b/.test(query)) {
1354
+ return ROUTE_BY_ID.get("messages") ?? null;
1355
+ }
955
1356
  if (/\b(infographic|graphic designer|render html|carousel pages|visual design|gtm flow image)\b/.test(query))
956
1357
  return ROUTE_BY_ID.get("visual-rendering") ?? null;
957
1358
  if (isUgcIntent(query))
@@ -962,6 +1363,8 @@ function explicitCapabilityIntent(query) {
962
1363
  return ROUTE_BY_ID.get("connected-linkedin") ?? null;
963
1364
  if (isCompanyPageFollowersIntent(query))
964
1365
  return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
1366
+ if (isTableCapacityReadIntent(query))
1367
+ return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
965
1368
  // A whole-motion ask ("set up inbound-led outbound for my posts") is a
966
1369
  // Recipe kit + playbook, not the connected-LinkedIn signals card that the
967
1370
  // "my LinkedIn … engagers" rule below would otherwise claim. Resolve it first.
@@ -998,6 +1401,10 @@ function explicitCapabilityIntent(query) {
998
1401
  // claimed "a first comment on my LinkedIn post" and "... automatically".
999
1402
  if (isFirstCommentIntent(query))
1000
1403
  return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
1404
+ // Publish/approve are writes even when the noun is a public LinkedIn post.
1405
+ // The 2026-09-30 connector baseline offered the cookieless read owner first.
1406
+ if (isPublishingPostIntent(query))
1407
+ return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
1001
1408
  if (isLinkedInKeywordPostIntent(query))
1002
1409
  return ROUTE_BY_ID.get("signals") ?? null;
1003
1410
  // Ahead of the workflow rule, whose "retry" would claim it. Why a post failed and how to get it out is Publishing's attempt ledger and
@@ -1012,8 +1419,13 @@ function explicitCapabilityIntent(query) {
1012
1419
  // hid every Sequences command.
1013
1420
  if (isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query))
1014
1421
  return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
1422
+ if (isMailboxVolumeIntent(query) && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query)) {
1423
+ return ROUTE_BY_ID.get("sending-infrastructure") ?? null;
1424
+ }
1015
1425
  if (isHostedWorkflowIntent(query))
1016
1426
  return ROUTE_BY_PRIMITIVE.get("workflows") ?? null;
1427
+ if (isCompanySourcingIntent(query))
1428
+ return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
1017
1429
  if (isOwnedPostCommentIntent(query))
1018
1430
  return ROUTE_BY_PRIMITIVE.get("posts") ?? null;
1019
1431
  // Asking a second party to decide is Collaboration even when the thing being
@@ -1091,6 +1503,8 @@ function explicitCapabilityIntent(query) {
1091
1503
  // and the "label" wording fell to the Tags card below.
1092
1504
  if (isRowClassificationIntent(query))
1093
1505
  return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
1506
+ if (isTableMaintenanceIntent(query))
1507
+ return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
1094
1508
  if (/\b(tag|tags|label|labels)\b/.test(query))
1095
1509
  return ROUTE_BY_PRIMITIVE.get("tags") ?? null;
1096
1510
  if (/\b(disposable|working)\b.{0,30}\b(list|dataset|table)\b|\b(prospect|sourcing) table\b/.test(query)) {
@@ -1202,7 +1616,22 @@ export const LOCAL_BUSINESS_SCRAPE_COMMAND = `oxygen companies search preview --
1202
1616
  function isLocalBusinessIntent(query) {
1203
1617
  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);
1204
1618
  }
1619
+ function isPublishingPostIntent(query) {
1620
+ return /\b(posts?|publishing|content calendar)\b/.test(query)
1621
+ && /\b(publish|publishing|schedule|scheduled|approve|approval)\b/.test(query)
1622
+ && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query)
1623
+ && !(/\b(find|search|scrape|research)\b/.test(query) && /\b(about|mention|containing)\b/.test(query));
1624
+ }
1205
1625
  function recommendationsFor(card, query) {
1626
+ const database = card.id === "platform-operations" ? databaseRecommendation(query) : null;
1627
+ if (database)
1628
+ return database;
1629
+ if (card.id === "billing-and-budgets" && /\b(credit prices|credit price catalog|customer credit)\b/.test(query)) {
1630
+ return { tools: ["oxygen_customer_credit_prices"], commands: ["pricing customer-credit-prices"] };
1631
+ }
1632
+ if (card.id === "messages" && /\b(unibox|inbox)\b/.test(query) && /\b(metadata|taxonomy|filters|accounts|tags)\b/.test(query)) {
1633
+ return { tools: ["oxygen_unibox_metadata"], commands: ["unibox metadata"] };
1634
+ }
1206
1635
  // A program's lifecycle and a creator's voice are UGC work the Publishing card
1207
1636
  // also claims ("archive program", "creator voice"); before these they got the
1208
1637
  // post tools (UGC E2E pass, 2026-09-29).
@@ -1261,6 +1690,22 @@ function recommendationsFor(card, query) {
1261
1690
  exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.localBusinessScrape(),
1262
1691
  };
1263
1692
  }
1693
+ if (card.id === "workspace-access" && isConnectedAppSpendIntent(query)) {
1694
+ return {
1695
+ tools: ["oxygen_connector_list", "oxygen_connector_update", "oxygen_limits_show"],
1696
+ commands: ["connectors get", "connectors set", "limits show"],
1697
+ exactCommand: "oxygen connectors get <app> --json",
1698
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.connectedAppsList(),
1699
+ };
1700
+ }
1701
+ if (card.id === "workspace-access" && isConnectedAppEventsIntent(query)) {
1702
+ return {
1703
+ tools: ["oxygen_connector_events_get", "oxygen_connector_events_revoke", "oxygen_connector_list"],
1704
+ commands: ["connectors events get", "connectors events revoke", "connectors list"],
1705
+ exactCommand: "oxygen connectors events get <app> --json",
1706
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.connectedAppEvents(),
1707
+ };
1708
+ }
1264
1709
  if (card.id === "workspace-access" && isConnectedAppsIntent(query)) {
1265
1710
  return {
1266
1711
  tools: ["oxygen_connector_list", "oxygen_connector_update"],
@@ -1269,6 +1714,43 @@ function recommendationsFor(card, query) {
1269
1714
  exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.connectedAppsList(),
1270
1715
  };
1271
1716
  }
1717
+ if (card.id === "workspace-access" && isWorkspacePagesIntent(query)) {
1718
+ return {
1719
+ tools: ["oxygen_settings_pages_get", "oxygen_settings_pages_update"],
1720
+ commands: ["settings pages list", "settings pages set"],
1721
+ exactCommand: "oxygen settings pages list --json",
1722
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.workspacePagesList(),
1723
+ };
1724
+ }
1725
+ // A general settings ask: the pages pair is the settings CLI and MCP can change.
1726
+ if (card.id === "workspace-access" && isWorkspaceSettingsIntent(query)) {
1727
+ return {
1728
+ tools: ["oxygen_settings_pages_get", "oxygen_settings_pages_update", ...card.gatewayTools],
1729
+ commands: ["settings pages list", "settings pages set", ...card.gatewayCommands],
1730
+ };
1731
+ }
1732
+ // Contact enrichment keeps its own gateway even when the input mentions
1733
+ // company names or domains; those words do not make it account sourcing.
1734
+ if (card.id === "sourcing-and-provider-tools" && /\bfind (?:work )?emails?\b/.test(query))
1735
+ return {
1736
+ tools: ["oxygen_find_email", "oxygen_tools_search"], commands: ["find email", "tools search"],
1737
+ };
1738
+ if (card.id === "sourcing-and-provider-tools" && /\bfind (?:mobile )?phones?\b/.test(query))
1739
+ return {
1740
+ tools: ["oxygen_find_phone", "oxygen_tools_search"], commands: ["find phone", "tools search"],
1741
+ };
1742
+ if (card.id === "sourcing-and-provider-tools" && (isCompanySourcingIntent(query) || /\b(companies|company search|company sourcing)\b/.test(query))) {
1743
+ return {
1744
+ tools: ["oxygen_companies_search_plan", "oxygen_companies_search_preview", "oxygen_companies_search_run", ...(/\b(people|contacts)\b/.test(query) ? ["oxygen_people_search_plan"] : [])],
1745
+ commands: ["companies search plan", "companies search preview", "companies search run", ...(/\b(people|contacts)\b/.test(query) ? ["people search plan"] : [])],
1746
+ };
1747
+ }
1748
+ if (card.id === "sourcing-and-provider-tools" && /\b(people|contacts|personas?)\b/.test(query)) {
1749
+ return {
1750
+ tools: ["oxygen_people_search_plan", "oxygen_people_search_preview", "oxygen_people_search_run"],
1751
+ commands: ["people search plan", "people search preview", "people search run"],
1752
+ };
1753
+ }
1272
1754
  if (card.id === "connected-linkedin" && isLinkedInAccountConnectIntent(query)) {
1273
1755
  return {
1274
1756
  tools: ["oxygen_senders_connect", "oxygen_senders_list", "oxygen_billing_commitments"],
@@ -1311,6 +1793,12 @@ function recommendationsFor(card, query) {
1311
1793
  ],
1312
1794
  };
1313
1795
  }
1796
+ if (card.id === "sending-infrastructure" && isMailboxVolumeIntent(query)) {
1797
+ return {
1798
+ tools: ["oxygen_mailboxes_set_ramp", "oxygen_mailboxes_set_cap", "oxygen_mailboxes_get"],
1799
+ commands: ["mailboxes ramp set", "mailboxes cap set", "mailboxes get"],
1800
+ };
1801
+ }
1314
1802
  if (card.id === "sending-infrastructure" && isMailboxOnboardingIntent(query)) {
1315
1803
  return {
1316
1804
  tools: [
@@ -1371,6 +1859,89 @@ function recommendationsFor(card, query) {
1371
1859
  };
1372
1860
  }
1373
1861
  if (card.primitive === "tables") {
1862
+ const read = tableReadRecommendation(query);
1863
+ if (read)
1864
+ return read;
1865
+ const maintenance = tableMaintenanceRecommendation(query);
1866
+ if (maintenance)
1867
+ return maintenance;
1868
+ if (isTableCapacityReadIntent(query))
1869
+ return {
1870
+ tools: ["oxygen_limits_show", "oxygen_tables_query", "oxygen_tables_describe"],
1871
+ commands: ["limits show", "tables query", "tables describe"],
1872
+ exactMcpCall: { tool: "oxygen_limits_show", arguments: {} },
1873
+ };
1874
+ if (isFunctionIntent(query)) {
1875
+ if (/\b(?:list|show|read|see|my)\b.{0,30}\bfavou?rites?\b/.test(query))
1876
+ return {
1877
+ tools: ["oxygen_callables_manage"], commands: ["functions list", "functions describe"],
1878
+ exactMcpCall: { tool: "oxygen_callables_manage", arguments: { operation: "list_functions", filter: "favorites" } },
1879
+ };
1880
+ if (/\b(runs|invocations?|history)\b/.test(query))
1881
+ return {
1882
+ tools: ["oxygen_callables_manage"], commands: ["functions runs", "functions describe"],
1883
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.functionsRuns(),
1884
+ };
1885
+ const functionMutation = [
1886
+ [/\b(publish)\b/, "functions publish"],
1887
+ [/\b(bind|attach)\b/, "functions bind"],
1888
+ [/\b(rename)\b/, "functions rename"],
1889
+ [/\b(delete|remove)\b/, "functions delete"],
1890
+ [/\b(duplicate|copy)\b/, "functions duplicate"],
1891
+ [/\b(unfavorite|unfavourite)\b/, "functions unfavorite"],
1892
+ [/\b(favorite|favourite)\b/, "functions favorite"],
1893
+ [/\b(enable|disable)\b/, /\bdisable\b/.test(query) ? "functions disable" : "functions enable"],
1894
+ [/\b(create|save|edit)\b/, "functions draft"],
1895
+ ];
1896
+ const mutation = functionMutation.find(([pattern]) => pattern.test(query));
1897
+ if (mutation)
1898
+ return {
1899
+ tools: ["oxygen_callables_manage"],
1900
+ commands: [/\bcreate\b/.test(query) ? "functions list" : "functions describe", mutation[1]],
1901
+ exactMcpCall: /\bcreate\b/.test(query) ? CAPABILITY_ROUTE_MCP_CALLS.functionsList() : CAPABILITY_ROUTE_MCP_CALLS.functionsDescribe(),
1902
+ };
1903
+ if (/\b(run|execute|invoke)\b/.test(query))
1904
+ return {
1905
+ tools: ["oxygen_callables_manage", "oxygen_columns_run"], commands: ["functions describe", "columns run"],
1906
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.functionsDescribe(),
1907
+ };
1908
+ if (/\b(describe|inspect|inputs?|outputs?|steps?|caps?|version|details?)\b/.test(query))
1909
+ return {
1910
+ tools: ["oxygen_callables_manage"], commands: ["functions describe", "functions runs"],
1911
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.functionsDescribe(),
1912
+ };
1913
+ return {
1914
+ tools: ["oxygen_callables_manage"], commands: ["functions list", "functions describe"],
1915
+ exactMcpCall: { ...CAPABILITY_ROUTE_MCP_CALLS.functionsList(), ...(/\b(favorites|favourites)\b/.test(query) ? { arguments: { operation: "list_functions", filter: "favorites" } } : {}) },
1916
+ };
1917
+ }
1918
+ if (isTableMaintenanceIntent(query)) {
1919
+ if (/\b(skills?|instructions?|guidance)\b/.test(query))
1920
+ return {
1921
+ tools: ["oxygen_skills_get", "oxygen_tables_tidy_suggest", "oxygen_tables_describe"],
1922
+ commands: ["skills get", "tables tidy-suggest", "tables describe"],
1923
+ exactCommand: "oxygen skills get oxygen-table-tidy --json",
1924
+ exactMcpCall: { tool: "oxygen_skills_get", arguments: { name: "oxygen-table-tidy" } },
1925
+ };
1926
+ if (/\b(favou?rites?|unfavou?rite|star|unstar)\b/.test(query)) {
1927
+ const read = /\b(list|read|show|see)\b/.test(query);
1928
+ return {
1929
+ tools: read ? ["oxygen_tables_favorites", "oxygen_tables_favorite"] : ["oxygen_tables_favorite", "oxygen_tables_favorites"],
1930
+ commands: read ? ["tables favorites", "tables favorite"] : ["tables favorite", "tables favorites"],
1931
+ };
1932
+ }
1933
+ if (/\bcolumns?\b/.test(query) && /\b(rename|renaming|label|key)\b/.test(query)) {
1934
+ const key = /\b(stable key|column key|snake case|rename key)\b/.test(query);
1935
+ return {
1936
+ tools: [key ? "oxygen_columns_rename" : "oxygen_columns_update", "oxygen_tables_describe"],
1937
+ commands: [key ? "columns rename" : "columns update", "tables describe"],
1938
+ };
1939
+ }
1940
+ return {
1941
+ tools: ["oxygen_tables_tidy_suggest", "oxygen_columns_update", "oxygen_tables_describe", "oxygen_skills_get"],
1942
+ commands: ["tables tidy-suggest", "columns update", "tables describe", "skills get"],
1943
+ };
1944
+ }
1374
1945
  if (isLinkedInProfileWatcherIntent(query)) {
1375
1946
  return {
1376
1947
  tools: ["oxygen_tables_watcher"],
@@ -1527,6 +2098,14 @@ function recommendationsFor(card, query) {
1527
2098
  ],
1528
2099
  };
1529
2100
  }
2101
+ // Inspect reusable bundles before authoring a new recurring monitor. This
2102
+ // does not assume a matching seed exists; list/describe/preflight establish
2103
+ // that. Existing run inspection still belongs to tail/list below.
2104
+ if (/\b(monitor|monitoring|watch|watching)\b/.test(query) && !/\b(existing|status|failed|failure|logs?|tail|cancel|pause|resume|disable|enable)\b/.test(query))
2105
+ return {
2106
+ tools: ["oxygen_blueprints_list", "oxygen_blueprints_describe", "oxygen_blueprints_preflight", "oxygen_workflows_schema", "oxygen_workflows_lint"],
2107
+ commands: ["blueprints list", "blueprints describe", "blueprints preflight", "workflows schema", "workflows lint"],
2108
+ };
1530
2109
  if (/\b(workflow )?templates?\b/.test(query)) {
1531
2110
  return {
1532
2111
  tools: ["oxygen_workflow_templates_search", "oxygen_workflow_templates_describe", "oxygen_workflow_templates_preflight", "oxygen_workflow_templates_apply"],
@@ -1749,10 +2328,11 @@ function isSecondPartyDecisionIntent(query) {
1749
2328
  const decisionLanguage = /\b(sign ?off|signoff|approval request|request approval|approval gate|go no go)\b/.test(query);
1750
2329
  const clientDecision = /\bclient\b.{0,20}\b(approv\w*|sign ?off|review|decide|decision|login|role)\b/.test(query)
1751
2330
  || /\b(approv\w*|sign ?off|decision)\b.{0,20}\bfrom the client\b/.test(query);
2331
+ const teammateDecision = /\b(teammate|colleague|workspace member)\b.{0,25}\b(approv\w*|sign ?off|review|decid\w*)\b/.test(query);
1752
2332
  const internalThread = /\b(comment|comments|thread)\b/.test(query)
1753
2333
  && /\b(internal|client|teammate|workspace member|discuss)\b/.test(query)
1754
2334
  && !/\b(linkedin|public|published|post|posts|publishing)\b/.test(query);
1755
- return decisionLanguage || clientDecision || internalThread;
2335
+ return decisionLanguage || clientDecision || teammateDecision || internalThread;
1756
2336
  }
1757
2337
  /** An X (Twitter) post format: thread, poll, alt text, who-can-reply, quote, photo tags. */
1758
2338
  function isXPostFormatIntent(query) {
@@ -1781,7 +2361,8 @@ function isOwnedPostCommentIntent(query) {
1781
2361
  return owned || operatorWork;
1782
2362
  }
1783
2363
  function isHostedWorkflowIntent(query) {
1784
- const strongWorkflowLanguage = /\b(workflow|automation|automate|automatically|webhook|cron|event trigger|branching?|retr(?:y|ies)|orchestrat(?:e|ion)|scheduled job|deterministic graph)\b/.test(query);
2364
+ const strongWorkflowLanguage = /\b(workflow|automation|automate|automatically|webhook|cron|event trigger|branching?|retr(?:y|ies)|orchestrat(?:e|ion)|scheduled job|deterministic graph)\b/.test(query)
2365
+ || isRecurringCompanyMonitoringIntent(query);
1785
2366
  const cadenceLanguage = /\b(sequence|campaign|cadence|day\s*\d+|multi[ -]step|rotate senders?|sender rotation|stop on reply|until (?:they )?reply)\b/.test(query);
1786
2367
  const workflowMechanics = /\b(webhook|cron|event trigger|triggered by|branching?|retr(?:y|ies)|orchestrat(?:e|ion)|scheduled job|deterministic graph|automatically)\b/.test(query)
1787
2368
  || /\bwhen\b.{0,50}\b(signs? up|created|updated|changes?|happens?|received)\b/.test(query);
@@ -1794,6 +2375,11 @@ function isHostedWorkflowIntent(query) {
1794
2375
  return /\b(nightly|hourly|daily|weekly|every (?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|day|night|week|month))\b/.test(query)
1795
2376
  || (/\bschedule(?:d)?\b/.test(query) && !/\bpost|content\b/.test(query));
1796
2377
  }
2378
+ function isRecurringCompanyMonitoringIntent(query) {
2379
+ return /\b(companies|company|competitors?)\b/.test(query)
2380
+ && /\b(monitor(?:ing)?|watch(?:ing|list)?|track(?:ing)?)\b/.test(query)
2381
+ && /\b(recurring|periodic|repeat(?:ed|able)?|over time|new observations?)\b/.test(query);
2382
+ }
1797
2383
  function isPublicLinkedInRead(query) {
1798
2384
  if (!query.includes("linkedin"))
1799
2385
  return false;