@oxygen-agent/cli 1.1011.326 → 1.1011.405

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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +7 -0
  3. package/dist/connectors-commands.d.ts +4 -1
  4. package/dist/connectors-commands.js +81 -10
  5. package/dist/contract-flags.d.ts +2 -0
  6. package/dist/contract-flags.js +4 -2
  7. package/dist/crm-support-flags.d.ts +11 -0
  8. package/dist/crm-support-flags.js +60 -0
  9. package/dist/help.js +10 -0
  10. package/dist/index.js +175 -65
  11. package/dist/settings-commands.d.ts +11 -0
  12. package/dist/settings-commands.js +72 -0
  13. package/node_modules/@oxygen/shared/dist/bounded-read-result.d.ts +4 -0
  14. package/node_modules/@oxygen/shared/dist/bounded-read-result.js +25 -0
  15. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +30 -0
  16. package/node_modules/@oxygen/shared/dist/capability-discovery.js +277 -16
  17. package/node_modules/@oxygen/shared/dist/contracts/connectors.d.ts +32 -6
  18. package/node_modules/@oxygen/shared/dist/contracts/connectors.js +56 -4
  19. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +117 -0
  20. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -0
  21. package/node_modules/@oxygen/shared/dist/contracts/dashboard-tabs.d.ts +3 -0
  22. package/node_modules/@oxygen/shared/dist/contracts/dashboard-tabs.js +2 -0
  23. package/node_modules/@oxygen/shared/dist/contracts/integration-connect.d.ts +4 -0
  24. package/node_modules/@oxygen/shared/dist/contracts/integration-connect.js +7 -0
  25. package/node_modules/@oxygen/shared/dist/contracts/operations.js +8 -2
  26. package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.d.ts +16 -0
  27. package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.js +37 -0
  28. package/node_modules/@oxygen/shared/dist/decision-ledger-parser.d.ts +32 -0
  29. package/node_modules/@oxygen/shared/dist/decision-ledger-parser.js +303 -0
  30. package/node_modules/@oxygen/shared/dist/decision-ledger.d.ts +10 -0
  31. package/node_modules/@oxygen/shared/dist/decision-ledger.generated.d.ts +2 -0
  32. package/node_modules/@oxygen/shared/dist/decision-ledger.generated.js +717 -0
  33. package/node_modules/@oxygen/shared/dist/decision-ledger.js +20 -0
  34. package/node_modules/@oxygen/shared/dist/mcp-event-contract.d.ts +10 -7
  35. package/node_modules/@oxygen/shared/dist/mcp-event-contract.js +11 -7
  36. package/node_modules/@oxygen/shared/dist/mcp-table-widget.d.ts +50 -0
  37. package/node_modules/@oxygen/shared/dist/mcp-table-widget.js +24 -0
  38. package/node_modules/@oxygen/shared/dist/plan-band.d.ts +8 -0
  39. package/node_modules/@oxygen/shared/dist/plan-band.js +12 -0
  40. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +21 -0
  41. package/node_modules/@oxygen/shared/dist/plan-limits.js +41 -0
  42. package/node_modules/@oxygen/shared/dist/sequences.d.ts +33 -4
  43. package/node_modules/@oxygen/shared/dist/sequences.js +48 -13
  44. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +17 -0
  45. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  46. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  47. package/node_modules/@oxygen/shared/package.json +5 -0
  48. package/package.json +1 -1
@@ -0,0 +1,4 @@
1
+ /** Complete native receipt sizing for optional bounded JSON reads. Binary
2
+ * windows have their own outer-envelope budget and must not use this helper.
3
+ * The surface is a formatting hint, never authorization or spend authority. */
4
+ export declare function boundedReadResultFits(command: string, data: Record<string, unknown>, maxChars: number | undefined, surface: string | null | undefined): boolean;
@@ -0,0 +1,25 @@
1
+ import { success } from "./cli-result.js";
2
+ import { OXYGEN_VERSION } from "./version.js";
3
+ /** Complete native receipt sizing for optional bounded JSON reads. Binary
4
+ * windows have their own outer-envelope budget and must not use this helper.
5
+ * The surface is a formatting hint, never authorization or spend authority. */
6
+ export function boundedReadResultFits(command, data, maxChars, surface) {
7
+ // Preserve legacy unbounded native/CLI reads when no window was requested.
8
+ if (maxChars === undefined)
9
+ return true;
10
+ if (JSON.stringify(success(command, data)).length > maxChars)
11
+ return false;
12
+ // MCP jsonResult writes compact data to text and adds the version to its
13
+ // structured result. The unattended worker returns both; Strands serializes
14
+ // that complete host object before its 6k cap. Escapes in text count twice.
15
+ if (surface === "agent") {
16
+ const host = {
17
+ ok: true,
18
+ text: JSON.stringify(data),
19
+ structured: { ...data, oxygen_version: OXYGEN_VERSION },
20
+ };
21
+ if (JSON.stringify(host).length > 6_000)
22
+ return false;
23
+ }
24
+ return true;
25
+ }
@@ -97,6 +97,36 @@ export declare const CAPABILITY_ROUTE_MCP_CALLS: {
97
97
  tool: string;
98
98
  arguments: {};
99
99
  };
100
+ connectedAppEvents: () => {
101
+ tool: string;
102
+ arguments: {
103
+ connection: string;
104
+ };
105
+ };
106
+ workspacePagesList: () => {
107
+ tool: string;
108
+ arguments: {};
109
+ };
110
+ functionsList: () => {
111
+ tool: string;
112
+ arguments: {
113
+ operation: string;
114
+ };
115
+ };
116
+ functionsDescribe: () => {
117
+ tool: string;
118
+ arguments: {
119
+ operation: string;
120
+ function: string;
121
+ };
122
+ };
123
+ functionsRuns: () => {
124
+ tool: string;
125
+ arguments: {
126
+ operation: string;
127
+ function: string;
128
+ };
129
+ };
100
130
  sequenceSendingSchedule: () => {
101
131
  tool: string;
102
132
  arguments: {
@@ -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",
@@ -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"],
@@ -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,
@@ -765,8 +775,26 @@ function routeCardFor(card, query) {
765
775
  return { ...card, ...UGC_ROUTE_VIEW };
766
776
  if (card.id === "knowledge-graph" && isWorkspaceVoiceIntent(query))
767
777
  return { ...card, execution: WORKSPACE_VOICE_EXECUTION };
778
+ if (card.primitive === "workflows" && isRecurringCompanyMonitoringIntent(query))
779
+ return {
780
+ ...card,
781
+ 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.",
782
+ };
783
+ if (card.primitive === "tables" && isTableCapacityReadIntent(query))
784
+ return {
785
+ ...card,
786
+ 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.",
787
+ };
788
+ if (card.primitive === "tables" && isFunctionIntent(query))
789
+ return {
790
+ ...card,
791
+ 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.",
792
+ };
768
793
  return card;
769
794
  }
795
+ function isFunctionIntent(query) {
796
+ return /\b(functions?|callables?)\b/.test(query) && !/\bformula functions?\b/.test(query);
797
+ }
770
798
  function isLinkedInKeywordPostIntent(query) {
771
799
  return /\blinkedin\b/.test(query)
772
800
  && /\b(posts?|posting)\b/.test(query)
@@ -829,6 +857,11 @@ export const SEQUENCE_UNSUBSCRIBE_COMMAND = "oxygen sequences update <sequence>
829
857
  */
830
858
  export const CAPABILITY_ROUTE_MCP_CALLS = {
831
859
  connectedAppsList: () => ({ tool: "oxygen_connector_list", arguments: {} }),
860
+ connectedAppEvents: () => ({ tool: "oxygen_connector_events_get", arguments: { connection: "<app>" } }),
861
+ workspacePagesList: () => ({ tool: "oxygen_settings_pages_get", arguments: {} }),
862
+ functionsList: () => ({ tool: "oxygen_callables_manage", arguments: { operation: "list_functions" } }),
863
+ functionsDescribe: () => ({ tool: "oxygen_callables_manage", arguments: { operation: "describe", function: "<function>" } }),
864
+ functionsRuns: () => ({ tool: "oxygen_callables_manage", arguments: { operation: "runs", function: "<function>" } }),
832
865
  sequenceSendingSchedule: () => ({
833
866
  tool: "oxygen_sequences_update",
834
867
  arguments: { sequence: "<sequence>", sending_schedule: { timezone: "<IANA zone>", days: [1, 2, 3, 4, 5], start: "09:00", end: "17:00" } },
@@ -939,6 +972,12 @@ function isOpenLinkedInRequestsIntent(query) {
939
972
  function isConnectedAppsIntent(query) {
940
973
  if (/\bconnected apps?\b/.test(query))
941
974
  return true;
975
+ // The command group's own noun. Tables' "connect" term prefix-matches it, so
976
+ // `commands search connectors` returned no `connectors` command at all
977
+ // (CLI acceptance judge, 2026-09-30). Bare noun only: a provider's connector
978
+ // ("hubspot connector") is never claimed here.
979
+ if (/^(?:(?:list|show|see|my|our|all|the|mcp|app|apps|ai|oauth)\s+)*connectors?$/.test(query))
980
+ return true;
942
981
  const connection = /\b(oauth (?:apps?|clients?|connections?|grants?)|mcp (?:clients?|connections?|connectors?))\b/.test(query);
943
982
  const app = connection || /\b(chatgpt|claude|codex|ai (?:apps?|assistants?|clients?))\b/.test(query);
944
983
  if (!app)
@@ -949,9 +988,100 @@ function isConnectedAppsIntent(query) {
949
988
  && /\b(default|always|without asking|which|per (?:app|connection|client))\b/.test(query);
950
989
  return workspaceChoice || (connection && /\b(list|show|see|which|my)\b/.test(query));
951
990
  }
991
+ /**
992
+ * What OXYGEN sends a connected app (MCP Events, ADR 0030 §10): "what is
993
+ * ChatGPT subscribed to", "what did OXYGEN send it", "stop it". Both blind
994
+ * baselines of 2026-09-30 (OXP-11.5.22) searched exactly these words — connector
995
+ * event subscriptions, deliveries, webhook, notifications, push — and were
996
+ * handed the inbound provider-webhook tools instead. A billing subscription or a
997
+ * provider's events ("hubspot webhook") never matches.
998
+ */
999
+ function isConnectedAppEventsIntent(query) {
1000
+ if (/\b(plans?|billing|invoices?|credits?|upgrade|downgrade|stripe|pricing|price)\b/.test(query))
1001
+ return false;
1002
+ if (/\b(hubspot|slack|gmail|calendly|cal\.?com|salesforce|pipedrive|zapier|integrations?|providers?|workflows?|triggers?)\b/.test(query))
1003
+ return false;
1004
+ const app = /\b(chatgpt|claude|codex|connected apps?|connectors?|connections?|apps?|ai (?:assistants?|clients?)|mcp)\b/.test(query);
1005
+ const events = /\b(subscri\w*|events?|webhooks?|notif\w*|push(?:es|ed)?|deliver(?:y|ies|ed)?|sen[dt]s?)\b/.test(query);
1006
+ if (!app || !events)
1007
+ return false;
1008
+ // "send a LinkedIn message from my app" is outreach, not what OXYGEN sends the app.
1009
+ return !/\b(linkedin|email|emails|whatsapp|message|messages|sequence|sequences|campaign|outreach|post|posts)\b/.test(query);
1010
+ }
1011
+ /**
1012
+ * What a connected app may spend (ADR 0030 §11): "how much can ChatGPT spend on
1013
+ * its own in a day", "cap it at 300 credits". Both blind baselines of
1014
+ * 2026-09-30 (OXP-11.5.23) searched for a per-connection daily credit limit and
1015
+ * found only workspace budgets. A sender's or channel's daily limit is not this.
1016
+ */
1017
+ function isConnectedAppSpendIntent(query) {
1018
+ const app = /\b(chatgpt|claude|codex|connected apps?|connectors?|connections?|per[- ]connection|mcp)\b/.test(query);
1019
+ const spend = /\b(spend\w*|credits?|budgets?|ceilings?|caps?|capped|limits?)\b/.test(query);
1020
+ if (!app || !spend)
1021
+ return false;
1022
+ return !/\b(linkedin|senders?|mailbox(?:es)?|sequences?|email|emails|whatsapp|warm\s?up|invites?|rate)\b/.test(query);
1023
+ }
1024
+ /**
1025
+ * The web app's sidebar pages a workspace hides for every member (Settings →
1026
+ * Customise, `settings pages`). Both blind baselines of 2026-09-30 searched
1027
+ * "hide navigation pages sidebar", "customise pages", "pages visibility", "nav
1028
+ * items" and "workspace navigation hidden pages unibox publishing workflows",
1029
+ * and were routed to workspace access, Publishing or nowhere. A sidebar or
1030
+ * navigation noun is enough on its own; "pages" plus a hide/show word is not
1031
+ * when the pages are wiki, LinkedIn, web, PDF or result pages.
1032
+ */
1033
+ function isWorkspacePagesIntent(query) {
1034
+ // "Sales Nav" is LinkedIn Sales Navigator.
1035
+ if (/\bsales ?nav/.test(query))
1036
+ return false;
1037
+ if (/\b(side ?bars?|side ?nav\w*|side ?menus?|nav ?bars?|nav|navigation|left (?:menu|nav\w*|rail|panel)|main menu)\b/.test(query))
1038
+ return true;
1039
+ // "Menu items" alone is as likely a restaurant's menu as the app's.
1040
+ if (/\bmenu items?\b/.test(query) && /\b(hid(?:e|es|den|ing)|unhide|show(?:s|n|ing)?|visib\w*|customi[sz]\w*)\b/.test(query))
1041
+ return true;
1042
+ if (/\bcustomi[sz](?:e|es|ed|ing|ation)\b/.test(query) && /\b(pages?|menus?)\b/.test(query))
1043
+ return true;
1044
+ // The web page's own name: Settings → Customise.
1045
+ if (/^customi[sz]e$|\bsettings?\W{1,5}customi[sz]e\b/.test(query))
1046
+ return true;
1047
+ if (/\b(wiki|linkedin|compan(?:y|ies)|landing|web ?pages?|websites?|carousels?|pdfs?|paginat\w*|next page|per page|page size|results?|dashboards?|rows?|columns?)\b/.test(query))
1048
+ return false;
1049
+ return /\bpages?\b/.test(query) && /\b(hid(?:e|es|den|ing)|unhide|show(?:s|n|ing)?|visib\w*|invisible)\b/.test(query);
1050
+ }
1051
+ /**
1052
+ * "Organisation settings" (the web's own name for /settings), "workspace
1053
+ * settings", "org settings": the CLI and MCP half of those settings is the
1054
+ * sidebar pages pair. A settings ask that names its own owner (billing, API
1055
+ * keys, members, a connected app, a provider) keeps that owner.
1056
+ */
1057
+ function isWorkspaceSettingsIntent(query) {
1058
+ 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))
1059
+ return false;
1060
+ return /\b(workspace|organi[sz]ations?|orgs?|team)\s+settings?\b/.test(query)
1061
+ || /\bsettings?\s+(?:for|of)\s+(?:the |my |our |this )?(workspace|organi[sz]ation|org|team)\b/.test(query);
1062
+ }
1063
+ function isTableCapacityReadIntent(query) {
1064
+ if (/\b(monitor|watch|workflow|automate|automation|increase|resize|upgrade)\b/.test(query))
1065
+ return false;
1066
+ return /\b(largest|biggest) tables?\b/.test(query)
1067
+ || /\btables?\b.{0,30}\b(sizes?|row counts?)\b/.test(query)
1068
+ || /\b(sizes?|row counts?)\b.{0,30}\btables?\b/.test(query);
1069
+ }
1070
+ function isCompanySourcingIntent(query) {
1071
+ if (/\b(enrich\w*|existing|these|those|rows?|columns?|verify|verification|emails?|phones?|contacts?|people|persons?)\b/.test(query))
1072
+ return false;
1073
+ return /\b(companies|businesses|firms|company search|company sourcing)\b/.test(query)
1074
+ && /\b(search|plan|find|source|sourcing|discover|build|tam|prospect)\b/.test(query);
1075
+ }
952
1076
  function explicitCapabilityIntent(query) {
953
- if (isConnectedAppsIntent(query))
1077
+ if (isConnectedAppsIntent(query) || isConnectedAppEventsIntent(query) || isConnectedAppSpendIntent(query)) {
1078
+ return ROUTE_BY_ID.get("workspace-access") ?? null;
1079
+ }
1080
+ // Ahead of every primitive rule: the pages a workspace hides are NAMED after
1081
+ // primitives ("hide Publishing and Workflows"), which those rules would claim.
1082
+ if (isWorkspacePagesIntent(query) || isWorkspaceSettingsIntent(query)) {
954
1083
  return ROUTE_BY_ID.get("workspace-access") ?? null;
1084
+ }
955
1085
  if (/\b(infographic|graphic designer|render html|carousel pages|visual design|gtm flow image)\b/.test(query))
956
1086
  return ROUTE_BY_ID.get("visual-rendering") ?? null;
957
1087
  if (isUgcIntent(query))
@@ -962,6 +1092,8 @@ function explicitCapabilityIntent(query) {
962
1092
  return ROUTE_BY_ID.get("connected-linkedin") ?? null;
963
1093
  if (isCompanyPageFollowersIntent(query))
964
1094
  return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
1095
+ if (isTableCapacityReadIntent(query))
1096
+ return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
965
1097
  // A whole-motion ask ("set up inbound-led outbound for my posts") is a
966
1098
  // Recipe kit + playbook, not the connected-LinkedIn signals card that the
967
1099
  // "my LinkedIn … engagers" rule below would otherwise claim. Resolve it first.
@@ -998,6 +1130,10 @@ function explicitCapabilityIntent(query) {
998
1130
  // claimed "a first comment on my LinkedIn post" and "... automatically".
999
1131
  if (isFirstCommentIntent(query))
1000
1132
  return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
1133
+ // Publish/approve are writes even when the noun is a public LinkedIn post.
1134
+ // The 2026-09-30 connector baseline offered the cookieless read owner first.
1135
+ if (isPublishingPostIntent(query))
1136
+ return ROUTE_BY_PRIMITIVE.get("publishing") ?? null;
1001
1137
  if (isLinkedInKeywordPostIntent(query))
1002
1138
  return ROUTE_BY_ID.get("signals") ?? null;
1003
1139
  // 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
@@ -1014,6 +1150,8 @@ function explicitCapabilityIntent(query) {
1014
1150
  return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
1015
1151
  if (isHostedWorkflowIntent(query))
1016
1152
  return ROUTE_BY_PRIMITIVE.get("workflows") ?? null;
1153
+ if (isCompanySourcingIntent(query))
1154
+ return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
1017
1155
  if (isOwnedPostCommentIntent(query))
1018
1156
  return ROUTE_BY_PRIMITIVE.get("posts") ?? null;
1019
1157
  // Asking a second party to decide is Collaboration even when the thing being
@@ -1202,6 +1340,12 @@ export const LOCAL_BUSINESS_SCRAPE_COMMAND = `oxygen companies search preview --
1202
1340
  function isLocalBusinessIntent(query) {
1203
1341
  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
1342
  }
1343
+ function isPublishingPostIntent(query) {
1344
+ return /\b(posts?|publishing|content calendar)\b/.test(query)
1345
+ && /\b(publish|publishing|schedule|scheduled|approve|approval)\b/.test(query)
1346
+ && !/\b(workflow|automation|automate|webhook|cron)\b/.test(query)
1347
+ && !(/\b(find|search|scrape|research)\b/.test(query) && /\b(about|mention|containing)\b/.test(query));
1348
+ }
1205
1349
  function recommendationsFor(card, query) {
1206
1350
  // A program's lifecycle and a creator's voice are UGC work the Publishing card
1207
1351
  // also claims ("archive program", "creator voice"); before these they got the
@@ -1261,6 +1405,22 @@ function recommendationsFor(card, query) {
1261
1405
  exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.localBusinessScrape(),
1262
1406
  };
1263
1407
  }
1408
+ if (card.id === "workspace-access" && isConnectedAppSpendIntent(query)) {
1409
+ return {
1410
+ tools: ["oxygen_connector_list", "oxygen_connector_update", "oxygen_limits_show"],
1411
+ commands: ["connectors get", "connectors set", "limits show"],
1412
+ exactCommand: "oxygen connectors get <app> --json",
1413
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.connectedAppsList(),
1414
+ };
1415
+ }
1416
+ if (card.id === "workspace-access" && isConnectedAppEventsIntent(query)) {
1417
+ return {
1418
+ tools: ["oxygen_connector_events_get", "oxygen_connector_events_revoke", "oxygen_connector_list"],
1419
+ commands: ["connectors events get", "connectors events revoke", "connectors list"],
1420
+ exactCommand: "oxygen connectors events get <app> --json",
1421
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.connectedAppEvents(),
1422
+ };
1423
+ }
1264
1424
  if (card.id === "workspace-access" && isConnectedAppsIntent(query)) {
1265
1425
  return {
1266
1426
  tools: ["oxygen_connector_list", "oxygen_connector_update"],
@@ -1269,6 +1429,43 @@ function recommendationsFor(card, query) {
1269
1429
  exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.connectedAppsList(),
1270
1430
  };
1271
1431
  }
1432
+ if (card.id === "workspace-access" && isWorkspacePagesIntent(query)) {
1433
+ return {
1434
+ tools: ["oxygen_settings_pages_get", "oxygen_settings_pages_update"],
1435
+ commands: ["settings pages list", "settings pages set"],
1436
+ exactCommand: "oxygen settings pages list --json",
1437
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.workspacePagesList(),
1438
+ };
1439
+ }
1440
+ // A general settings ask: the pages pair is the settings CLI and MCP can change.
1441
+ if (card.id === "workspace-access" && isWorkspaceSettingsIntent(query)) {
1442
+ return {
1443
+ tools: ["oxygen_settings_pages_get", "oxygen_settings_pages_update", ...card.gatewayTools],
1444
+ commands: ["settings pages list", "settings pages set", ...card.gatewayCommands],
1445
+ };
1446
+ }
1447
+ // Contact enrichment keeps its own gateway even when the input mentions
1448
+ // company names or domains; those words do not make it account sourcing.
1449
+ if (card.id === "sourcing-and-provider-tools" && /\bfind (?:work )?emails?\b/.test(query))
1450
+ return {
1451
+ tools: ["oxygen_find_email", "oxygen_tools_search"], commands: ["find email", "tools search"],
1452
+ };
1453
+ if (card.id === "sourcing-and-provider-tools" && /\bfind (?:mobile )?phones?\b/.test(query))
1454
+ return {
1455
+ tools: ["oxygen_find_phone", "oxygen_tools_search"], commands: ["find phone", "tools search"],
1456
+ };
1457
+ if (card.id === "sourcing-and-provider-tools" && (isCompanySourcingIntent(query) || /\b(companies|company search|company sourcing)\b/.test(query))) {
1458
+ return {
1459
+ tools: ["oxygen_companies_search_plan", "oxygen_companies_search_preview", "oxygen_companies_search_run", ...(/\b(people|contacts)\b/.test(query) ? ["oxygen_people_search_plan"] : [])],
1460
+ commands: ["companies search plan", "companies search preview", "companies search run", ...(/\b(people|contacts)\b/.test(query) ? ["people search plan"] : [])],
1461
+ };
1462
+ }
1463
+ if (card.id === "sourcing-and-provider-tools" && /\b(people|contacts|personas?)\b/.test(query)) {
1464
+ return {
1465
+ tools: ["oxygen_people_search_plan", "oxygen_people_search_preview", "oxygen_people_search_run"],
1466
+ commands: ["people search plan", "people search preview", "people search run"],
1467
+ };
1468
+ }
1272
1469
  if (card.id === "connected-linkedin" && isLinkedInAccountConnectIntent(query)) {
1273
1470
  return {
1274
1471
  tools: ["oxygen_senders_connect", "oxygen_senders_list", "oxygen_billing_commitments"],
@@ -1371,6 +1568,56 @@ function recommendationsFor(card, query) {
1371
1568
  };
1372
1569
  }
1373
1570
  if (card.primitive === "tables") {
1571
+ if (isTableCapacityReadIntent(query))
1572
+ return {
1573
+ tools: ["oxygen_limits_show", "oxygen_tables_query", "oxygen_tables_describe"],
1574
+ commands: ["limits show", "tables query", "tables describe"],
1575
+ exactMcpCall: { tool: "oxygen_limits_show", arguments: {} },
1576
+ };
1577
+ if (isFunctionIntent(query)) {
1578
+ if (/\b(?:list|show|read|see|my)\b.{0,30}\bfavou?rites?\b/.test(query))
1579
+ return {
1580
+ tools: ["oxygen_callables_manage"], commands: ["functions list", "functions describe"],
1581
+ exactMcpCall: { tool: "oxygen_callables_manage", arguments: { operation: "list_functions", filter: "favorites" } },
1582
+ };
1583
+ if (/\b(runs|invocations?|history)\b/.test(query))
1584
+ return {
1585
+ tools: ["oxygen_callables_manage"], commands: ["functions runs", "functions describe"],
1586
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.functionsRuns(),
1587
+ };
1588
+ const functionMutation = [
1589
+ [/\b(publish)\b/, "functions publish"],
1590
+ [/\b(bind|attach)\b/, "functions bind"],
1591
+ [/\b(rename)\b/, "functions rename"],
1592
+ [/\b(delete|remove)\b/, "functions delete"],
1593
+ [/\b(duplicate|copy)\b/, "functions duplicate"],
1594
+ [/\b(unfavorite|unfavourite)\b/, "functions unfavorite"],
1595
+ [/\b(favorite|favourite)\b/, "functions favorite"],
1596
+ [/\b(enable|disable)\b/, /\bdisable\b/.test(query) ? "functions disable" : "functions enable"],
1597
+ [/\b(create|save|edit)\b/, "functions draft"],
1598
+ ];
1599
+ const mutation = functionMutation.find(([pattern]) => pattern.test(query));
1600
+ if (mutation)
1601
+ return {
1602
+ tools: ["oxygen_callables_manage"],
1603
+ commands: [/\bcreate\b/.test(query) ? "functions list" : "functions describe", mutation[1]],
1604
+ exactMcpCall: /\bcreate\b/.test(query) ? CAPABILITY_ROUTE_MCP_CALLS.functionsList() : CAPABILITY_ROUTE_MCP_CALLS.functionsDescribe(),
1605
+ };
1606
+ if (/\b(run|execute|invoke)\b/.test(query))
1607
+ return {
1608
+ tools: ["oxygen_callables_manage", "oxygen_columns_run"], commands: ["functions describe", "columns run"],
1609
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.functionsDescribe(),
1610
+ };
1611
+ if (/\b(describe|inspect|inputs?|outputs?|steps?|caps?|version|details?)\b/.test(query))
1612
+ return {
1613
+ tools: ["oxygen_callables_manage"], commands: ["functions describe", "functions runs"],
1614
+ exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.functionsDescribe(),
1615
+ };
1616
+ return {
1617
+ tools: ["oxygen_callables_manage"], commands: ["functions list", "functions describe"],
1618
+ exactMcpCall: { ...CAPABILITY_ROUTE_MCP_CALLS.functionsList(), ...(/\b(favorites|favourites)\b/.test(query) ? { arguments: { operation: "list_functions", filter: "favorites" } } : {}) },
1619
+ };
1620
+ }
1374
1621
  if (isLinkedInProfileWatcherIntent(query)) {
1375
1622
  return {
1376
1623
  tools: ["oxygen_tables_watcher"],
@@ -1527,6 +1774,14 @@ function recommendationsFor(card, query) {
1527
1774
  ],
1528
1775
  };
1529
1776
  }
1777
+ // Inspect reusable bundles before authoring a new recurring monitor. This
1778
+ // does not assume a matching seed exists; list/describe/preflight establish
1779
+ // that. Existing run inspection still belongs to tail/list below.
1780
+ if (/\b(monitor|monitoring|watch|watching)\b/.test(query) && !/\b(existing|status|failed|failure|logs?|tail|cancel|pause|resume|disable|enable)\b/.test(query))
1781
+ return {
1782
+ tools: ["oxygen_blueprints_list", "oxygen_blueprints_describe", "oxygen_blueprints_preflight", "oxygen_workflows_schema", "oxygen_workflows_lint"],
1783
+ commands: ["blueprints list", "blueprints describe", "blueprints preflight", "workflows schema", "workflows lint"],
1784
+ };
1530
1785
  if (/\b(workflow )?templates?\b/.test(query)) {
1531
1786
  return {
1532
1787
  tools: ["oxygen_workflow_templates_search", "oxygen_workflow_templates_describe", "oxygen_workflow_templates_preflight", "oxygen_workflow_templates_apply"],
@@ -1781,7 +2036,8 @@ function isOwnedPostCommentIntent(query) {
1781
2036
  return owned || operatorWork;
1782
2037
  }
1783
2038
  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);
2039
+ 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)
2040
+ || isRecurringCompanyMonitoringIntent(query);
1785
2041
  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
2042
  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
2043
  || /\bwhen\b.{0,50}\b(signs? up|created|updated|changes?|happens?|received)\b/.test(query);
@@ -1794,6 +2050,11 @@ function isHostedWorkflowIntent(query) {
1794
2050
  return /\b(nightly|hourly|daily|weekly|every (?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|day|night|week|month))\b/.test(query)
1795
2051
  || (/\bschedule(?:d)?\b/.test(query) && !/\bpost|content\b/.test(query));
1796
2052
  }
2053
+ function isRecurringCompanyMonitoringIntent(query) {
2054
+ return /\b(companies|company|competitors?)\b/.test(query)
2055
+ && /\b(monitor(?:ing)?|watch(?:ing|list)?|track(?:ing)?)\b/.test(query)
2056
+ && /\b(recurring|periodic|repeat(?:ed|able)?|over time|new observations?)\b/.test(query);
2057
+ }
1797
2058
  function isPublicLinkedInRead(query) {
1798
2059
  if (!query.includes("linkedin"))
1799
2060
  return false;
@@ -2,12 +2,14 @@
2
2
  * `PATCH /api/cli/connectors/{connection}`: the settings one connected app
3
3
  * (an MCP OAuth client such as ChatGPT, Claude or Codex) keeps for itself.
4
4
  *
5
- * Today that is its default workspace: the workspace the app works in without
6
- * asking. It is the same per-connection choice `oxygen_orgs_select` makes from
7
- * inside the app, so either surface changes one setting and the latest choice
8
- * wins. The contract is the extension point for later per-connection settings
9
- * (a per-connection credit ceiling is planned, OXP-11.5.23): add the field here
10
- * and the route, the CLI flags and the MCP tool follow.
5
+ * Its default workspace: the workspace the app works in without asking. It is
6
+ * the same per-connection choice `oxygen_orgs_select` makes from inside the
7
+ * app, so either surface changes one setting and the latest choice wins.
8
+ *
9
+ * Its credit ceiling (ADR 0030 §11, OXP-11.5.23): the most credits the app may
10
+ * spend in any rolling 24 hours while it holds MCP event subscriptions. null
11
+ * restores the plan default; a value above that default needs
12
+ * approve_above_default, the posture Agent run and trigger ceilings use.
11
13
  */
12
14
  import type { OperationSpec } from "./operations.js";
13
15
  import { type Infer } from "./schema.js";
@@ -16,8 +18,32 @@ export declare const CONNECTOR_UPDATE_CONTRACT: import("./schema.js").ContractOb
16
18
  oneOf: [import("./schema.js").StringProperty, import("./schema.js").NullProperty];
17
19
  description?: string;
18
20
  };
21
+ readonly credit_ceiling: {
22
+ oneOf: [{
23
+ type: "integer";
24
+ description?: string;
25
+ minimum?: number;
26
+ maximum?: number;
27
+ "x-clamp"?: true;
28
+ }, import("./schema.js").NullProperty];
29
+ description?: string;
30
+ };
31
+ readonly approve_above_default: import("./schema.js").BooleanProperty;
19
32
  }, never>;
20
33
  export type ConnectorUpdate = Infer<typeof CONNECTOR_UPDATE_CONTRACT>;
21
34
  /** The route's path value for "the app making this request". */
22
35
  export declare const CURRENT_CONNECTOR_REF = "current";
23
36
  export declare const CONNECTOR_UPDATE_OPERATION: OperationSpec<typeof CONNECTOR_UPDATE_CONTRACT>;
37
+ /**
38
+ * `POST /api/cli/connectors/{connection}/events/revoke`: stop one connected
39
+ * app's MCP event subscription, or every live one it holds (ADR 0030 §10;
40
+ * OXP-11.5.22). Destructive and lasting: the app cannot renew a subscription
41
+ * revoked here. The events/* protocol methods the app itself calls stay outside
42
+ * this contract; they are host-driven.
43
+ */
44
+ export declare const CONNECTOR_EVENTS_REVOKE_CONTRACT: import("./schema.js").ContractObject<{
45
+ readonly subscription: import("./schema.js").StringProperty;
46
+ readonly workspace: import("./schema.js").StringProperty;
47
+ }, "subscription">;
48
+ export type ConnectorEventsRevoke = Infer<typeof CONNECTOR_EVENTS_REVOKE_CONTRACT>;
49
+ export declare const CONNECTOR_EVENTS_REVOKE_OPERATION: OperationSpec<typeof CONNECTOR_EVENTS_REVOKE_CONTRACT>;