@oxygen-agent/cli 1.1011.493 → 1.1011.607
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.
- package/README.md +1 -1
- package/dist/command-manifest.js +7 -0
- package/dist/crm-file-read-commands.d.ts +6 -0
- package/dist/crm-file-read-commands.js +16 -0
- package/dist/feed-read-flags.d.ts +3 -0
- package/dist/feed-read-flags.js +9 -0
- package/dist/functions-commands.js +2 -1
- package/dist/help.js +18 -5
- package/dist/index.js +168 -107
- package/dist/sender-limits-flags.js +6 -4
- package/dist/settings-commands.js +11 -11
- package/dist/whatsapp-account-flags.d.ts +4 -0
- package/dist/whatsapp-account-flags.js +18 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
- package/node_modules/@oxygen/shared/dist/billing.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/billing.js +56 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +254 -21
- package/node_modules/@oxygen/shared/dist/contracts/billing-owner-read.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/billing-owner-read.js +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
- package/node_modules/@oxygen/shared/dist/contracts/copilot-sessions.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/contracts/copilot-sessions.js +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-file-bytes.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-file-bytes.js +12 -0
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +6 -3
- package/node_modules/@oxygen/shared/dist/contracts/function-library-read.d.ts +14 -0
- package/node_modules/@oxygen/shared/dist/contracts/function-library-read.js +16 -0
- package/node_modules/@oxygen/shared/dist/contracts/inbox-drafts-read.d.ts +45 -0
- package/node_modules/@oxygen/shared/dist/contracts/inbox-drafts-read.js +21 -0
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +121 -0
- package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/pipedrive-catalog.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/contracts/pipedrive-catalog.js +29 -0
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.d.ts +181 -0
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +62 -0
- package/node_modules/@oxygen/shared/dist/contracts/sender-limits.js +7 -5
- package/node_modules/@oxygen/shared/dist/contracts/sequence-list.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/sequence-list.js +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/stored-read-window.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/contracts/stored-read-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.d.ts +67 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.js +19 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-read.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-read.js +9 -0
- package/node_modules/@oxygen/shared/dist/contracts/whatsapp-reads.d.ts +111 -0
- package/node_modules/@oxygen/shared/dist/contracts/whatsapp-reads.js +33 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.d.ts +6 -5
- package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.js +5 -5
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +4 -1
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +2 -32
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +6 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer-window.d.ts +34 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer-window.js +41 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer.d.ts +42 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer.js +56 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +32 -1
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +68 -4
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +6 -1
- package/node_modules/@oxygen/shared/dist/sql-error.js +13 -2
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +17 -1
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +11 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +29 -3
- package/package.json +1 -1
|
@@ -22,9 +22,9 @@ 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, 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
|
|
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 each person hides from their own sidebar in a workspace (Settings → Customise).",
|
|
26
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
|
|
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) are per person and per workspace: `settings pages list` (oxygen_settings_pages_get) shows each page and whether you hid it; `settings pages set --hide <pages>`, `--show <pages>` or `--show-all` (oxygen_settings_pages_update) changes only your own sidebar, never a teammate's. Nothing is deleted.",
|
|
28
28
|
posture: "mixed",
|
|
29
29
|
gatewayTools: [
|
|
30
30
|
"oxygen_whoami",
|
|
@@ -92,7 +92,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
92
92
|
gatewayCommands: ["billing balance", "billing usage", "billing estimate", "budget list", "billing invoices", "pricing customer-credit-prices"],
|
|
93
93
|
skills: ["oxygen-gtm", "oxygen-diagnostics"],
|
|
94
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"],
|
|
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", "subscription plans", "annual plan", "yearly commitment", "yearly saving", "annual discount"],
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
id: "support-and-feedback",
|
|
@@ -246,21 +246,21 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
246
246
|
// `capabilities search "founders of a company"` and got `route: null` with a
|
|
247
247
|
// generic sourcing pointer, then hand-wrote four research prompts for
|
|
248
248
|
// questions the template catalog now answers by key.
|
|
249
|
-
intentTerms: ["table", "spreadsheet", "rows", "column", "columns", "dataset", "csv", "import", "hubspot list", "hubspot segment", "pipedrive filter", "pipedrive", "saved list", "saved segment", "saved filter", "crm segment", "list import", "segment import", "filter import", "import a segment", "import a list", "import a filter", "enrich", "enrichment", "waterfall", "score", "formula", "ai column", "lookup", "link", "link tables", "join", "connect", "relate", "relationship", "function", "functions", "reusable function", "function draft", "function version", "callable", "column template", "skill set", "grad school", "podcast appearance", "keynote", "github profile", "job fit", "founders", "parent company", "subsidiaries", "cloud provider", "company research", "research question", "research column", "web research", "template catalog", "ready-made", "offers demos", "naics", "cost per row", "price per row", "what can i enrich", "enrichment cost", "credits per row"],
|
|
249
|
+
intentTerms: ["table", "spreadsheet", "rows", "column", "columns", "dataset", "csv", "import", "hubspot list", "hubspot segment", "pipedrive filter", "pipedrive", "saved list", "saved segment", "saved filter", "crm segment", "list import", "segment import", "filter import", "import a segment", "import a list", "import a filter", "enrich", "enrichment", "waterfall", "score", "formula", "ai column", "lookup", "link", "link tables", "join", "connect", "relate", "relationship", "function", "functions", "reusable function", "function draft", "function version", "callable", "column template", "skill set", "grad school", "podcast appearance", "keynote", "github profile", "job fit", "founders", "parent company", "subsidiaries", "cloud provider", "company research", "research question", "research column", "web research", "template catalog", "ready-made", "offers demos", "naics", "cost per row", "price per row", "what can i enrich", "enrichment cost", "credits per row", "flatten", "explode", "json array", "array cell"],
|
|
250
250
|
},
|
|
251
251
|
{
|
|
252
252
|
id: "messages",
|
|
253
253
|
layer: "Data",
|
|
254
254
|
primitive: "messages",
|
|
255
|
-
owns: "One-to-one message facts, conversations, direct email, inbox drafts, and replies in existing channel threads.",
|
|
255
|
+
owns: "One-to-one message facts, imported mailbox history, conversations, direct email, inbox drafts, and replies in existing channel threads.",
|
|
256
256
|
notFor: "Net-new LinkedIn initiation, cadence, enrollment, sender rotation, or reply-stop programs; those belong to Sequences.",
|
|
257
|
-
execution: "Preview and approve
|
|
257
|
+
execution: "Read conversations and imported email summaries in Unibox. Inspect mailboxes sync for Gmail/Microsoft 365 history and included CRM analysis progress. Preview and approve direct email or existing-thread replies; every sent fact stays in Messages.",
|
|
258
258
|
posture: "external_write",
|
|
259
259
|
gatewayTools: ["oxygen_inbox_send", "oxygen_email_send", "oxygen_messages_query", "oxygen_unibox_metadata"],
|
|
260
260
|
gatewayCommands: ["inbox send", "email send", "messages query", "unibox metadata"],
|
|
261
261
|
skills: ["oxygen-unibox"],
|
|
262
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"],
|
|
263
|
+
intentTerms: ["message", "messages", "reply", "email", "inbox", "unibox", "inbox metadata", "conversation", "thread", "direct email", "draft reply", "one off", "email history", "mail history", "analyze emails", "email crm"],
|
|
264
264
|
},
|
|
265
265
|
{
|
|
266
266
|
id: "posts",
|
|
@@ -476,17 +476,23 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
476
476
|
id: "sending-infrastructure",
|
|
477
477
|
layer: "External",
|
|
478
478
|
primitive: null,
|
|
479
|
-
owns: "Sending domains, managed/BYOK mailboxes, warmup, deliverability, egress IPs, and infrastructure health
|
|
479
|
+
owns: "Sending domains, managed/BYOK mailboxes, customer Gmail/Microsoft 365 connections, mail history controls, warmup, deliverability, egress IPs, and infrastructure health.",
|
|
480
480
|
notFor: "Writing campaign copy or sending outside a Sequence/Message owner.",
|
|
481
|
-
execution: "Inspect or provision hosted sending infrastructure with explicit recurring-cost and external-write approval.",
|
|
481
|
+
execution: "Inspect or provision hosted sending infrastructure with explicit recurring-cost and external-write approval. Connect existing Gmail or Microsoft 365/Exchange Online mailboxes using mailboxes connect-oauth --vendor composio where enabled; 100 credits/month per mailbox includes mail history and CRM analysis. Read progress with oxygen_mailboxes_sync_status (CLI: mailboxes sync without --action); control it with oxygen_mailboxes_sync. Read oxygen-email-infra for history coverage and CRM rules.",
|
|
482
482
|
posture: "mixed",
|
|
483
|
-
gatewayTools: ["oxygen_mailboxes_list", "oxygen_domains_list", "oxygen_mailboxes_health"],
|
|
484
|
-
gatewayCommands: ["mailboxes list", "domains list", "mailboxes health"],
|
|
483
|
+
gatewayTools: ["oxygen_mailboxes_list", "oxygen_mailboxes_connect_oauth", "oxygen_mailboxes_sync_status", "oxygen_mailboxes_sync", "oxygen_domains_list", "oxygen_mailboxes_health"],
|
|
484
|
+
gatewayCommands: ["mailboxes list", "mailboxes connect-oauth", "mailboxes sync", "domains list", "mailboxes health"],
|
|
485
485
|
skills: ["oxygen-email-infra", "oxygen-deliverability", "oxygen-sequencer"],
|
|
486
486
|
endpointSections: ["deliverability", "domains", "egress", "mailboxes", "managed-inboxes"],
|
|
487
487
|
intentTerms: [
|
|
488
488
|
"mailbox",
|
|
489
489
|
"mailboxes",
|
|
490
|
+
"gmail",
|
|
491
|
+
"microsoft 365",
|
|
492
|
+
"exchange online",
|
|
493
|
+
"outlook",
|
|
494
|
+
"connect inbox",
|
|
495
|
+
"mailbox history",
|
|
490
496
|
"sending domain",
|
|
491
497
|
"dns",
|
|
492
498
|
"warmup",
|
|
@@ -563,6 +569,33 @@ export function inferCapabilityRoute(query) {
|
|
|
563
569
|
const normalized = normalizeIntent(query);
|
|
564
570
|
if (!normalized)
|
|
565
571
|
return null;
|
|
572
|
+
// A complete audience play starts from its Recipe and the shared audience
|
|
573
|
+
// guide; onboarding consults on open-ended goals and never routes this ask.
|
|
574
|
+
// Its source nouns otherwise select a one-shot post search or follower order.
|
|
575
|
+
if (isLinkedInAudienceCampaignIntent(query)) {
|
|
576
|
+
const recipes = ROUTE_BY_ID.get("recipes");
|
|
577
|
+
const slug = query.trim().toLowerCase();
|
|
578
|
+
const namedPlay = AUDIENCE_PLAY_SLUGS.includes(slug) ? slug : null;
|
|
579
|
+
if (recipes)
|
|
580
|
+
return {
|
|
581
|
+
card: {
|
|
582
|
+
...recipes,
|
|
583
|
+
execution: "Pick the matching audience Recipe (own, competitor, creator or keyword post engagers, company-page followers or own followers), resolve current company/ICP context, then read that Recipe and oxygen-recipes file guides/audience-campaign.md. Preserve the exact source, attribution, qualification rule/version and object IDs. Prepare a bounded native audience campaign for email, LinkedIn DM/connection requests or WhatsApp. Keep capture, standing qualification, router and Sequence disabled until their own approval and credit scope; the shared guide owns execution.",
|
|
584
|
+
skills: ["oxygen-recipes", "oxygen-linkedin-marketing"],
|
|
585
|
+
},
|
|
586
|
+
recommendedTools: ["oxygen_recipes_list", "oxygen_recipes_get", "oxygen_context_resolve"],
|
|
587
|
+
recommendedCommands: ["recipes list", "recipes show", "context resolve"],
|
|
588
|
+
...(namedPlay
|
|
589
|
+
? {
|
|
590
|
+
exactCommand: `oxygen recipes show ${namedPlay} --json`,
|
|
591
|
+
exactMcpCall: { tool: "oxygen_recipes_get", arguments: { slug: namedPlay } },
|
|
592
|
+
}
|
|
593
|
+
: {
|
|
594
|
+
exactCommand: 'oxygen recipes list "linkedin audience" --view summary --json',
|
|
595
|
+
exactMcpCall: { tool: "oxygen_recipes_list", arguments: { query: "linkedin audience", view: "summary" } },
|
|
596
|
+
}),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
566
599
|
const explicit = explicitCapabilityIntent(normalized);
|
|
567
600
|
const catalogRow = findEnrichmentIntentByAlias(normalized);
|
|
568
601
|
// Card scoring stays the owner of routing; the catalogue only breaks a
|
|
@@ -764,6 +797,60 @@ export function serializeCapabilityRoute(route, surfaceOrOptions = "cli", option
|
|
|
764
797
|
function normalizeIntent(query) {
|
|
765
798
|
return query.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
766
799
|
}
|
|
800
|
+
/** The six LinkedIn audience-campaign Recipes, by catalog slug. */
|
|
801
|
+
const AUDIENCE_PLAY_SLUGS = [
|
|
802
|
+
"own-post-engagers",
|
|
803
|
+
"competitor-post-engagers",
|
|
804
|
+
"creator-post-engagers",
|
|
805
|
+
"keyword-post-engagers",
|
|
806
|
+
"company-page-followers",
|
|
807
|
+
"own-profile-followers",
|
|
808
|
+
];
|
|
809
|
+
/** Whole audience acquisition/outreach; individual reads and runtime controls keep their owners. */
|
|
810
|
+
function isLinkedInAudienceCampaignIntent(rawQuery) {
|
|
811
|
+
const query = normalizeIntent(rawQuery);
|
|
812
|
+
// Match exact Recipe slugs before punctuation normalization makes a slug
|
|
813
|
+
// indistinguishable from a plain company-page follower research request.
|
|
814
|
+
const canonicalPlay = new RegExp(`\\b(?:${AUDIENCE_PLAY_SLUGS.join("|")})\\b`, "i").exec(rawQuery);
|
|
815
|
+
if (canonicalPlay?.[0].toLowerCase() === rawQuery.trim().toLowerCase())
|
|
816
|
+
return true;
|
|
817
|
+
if (!/\blinkedin\b/.test(query) && !canonicalPlay)
|
|
818
|
+
return false;
|
|
819
|
+
// Audience nouns can be the subject of a local read/control operation. Only
|
|
820
|
+
// explicit acquisition/setup language may turn such a request into a play.
|
|
821
|
+
const acquisition = /\b(build|prepare|create|start|launch|scrape|capture|collect)\b|\bset up\b/.test(query);
|
|
822
|
+
if (!acquisition && /\b(show|list|read|inspect|verify|validate|label|classify|tag|rank|grade|score|rename|update|change|edit|delete|remove|clear|export|import)\b/.test(query))
|
|
823
|
+
return false;
|
|
824
|
+
if (/\boxygen_\w+\b/i.test(rawQuery)
|
|
825
|
+
|| isUgcIntent(query)
|
|
826
|
+
|| isWorkspacePagesIntent(query) || isWorkspaceSettingsIntent(query)
|
|
827
|
+
|| isPublishingPostIntent(query) || isFirstCommentIntent(query)
|
|
828
|
+
|| isPostCoverOrPreviewIntent(query) || isFailedPostIntent(query)
|
|
829
|
+
|| isOwnedPostCommentIntent(query) || isSecondPartyDecisionIntent(query)
|
|
830
|
+
|| tableReadRecommendation(query) || tableMaintenanceRecommendation(query)
|
|
831
|
+
|| isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query)
|
|
832
|
+
|| isSenderProfileIntent(query) || isSenderCopyVariableIntent(query)
|
|
833
|
+
|| isLinkedInAccountLimitsIntent(query)
|
|
834
|
+
|| /\b(workflows?|sequences?|cadences?|webhook|cron|event trigger|branching|retries)\b/.test(query)
|
|
835
|
+
|| /\b(status|pause|resume|disable|enable|logs?|tail|price|pricing|order)\b/.test(query)
|
|
836
|
+
|| /\b(known|this|single|one) posts?\b|linkedin\.com\/(?:posts\/|feed\/update\/)/.test(query)
|
|
837
|
+
|| /\b(one|single) (?:of them|person|recipient)\b/.test(query))
|
|
838
|
+
return false;
|
|
839
|
+
if (canonicalPlay)
|
|
840
|
+
return true;
|
|
841
|
+
const audienceFromSources = /\baudience\b/.test(query) && /\b(from|using|sources?|based on)\b/.test(query);
|
|
842
|
+
if (audienceFromSources && /\b(posts?|creators?|keywords?|profiles?|followers?)\b/.test(query))
|
|
843
|
+
return true;
|
|
844
|
+
const engagement = /\bposts?\b/.test(query) && /\b(engagers?|engaged|engagement|reactors?|reacting|reactions?|commenters?|commenting)\b/.test(query);
|
|
845
|
+
const followers = /\bfollowers?\b/.test(query);
|
|
846
|
+
if (!engagement && !followers)
|
|
847
|
+
return false;
|
|
848
|
+
const outreach = /\b(campaigns?|outreach|warm outbound|inbound led outbound)\b/.test(query)
|
|
849
|
+
|| /\b(?:cold )?(email|dm|whatsapp|invite) (?:them|the (?:engagers|followers|audience))\b/.test(query)
|
|
850
|
+
|| /\b(send|start)\b.{0,35}\b(emails?|dms?|connection requests?|invitations?|whatsapp messages?)\b/.test(query);
|
|
851
|
+
const namedPlay = /\b(recipes?|playbooks?|plays?|whole motion|complete motion|end to end motion)\b/.test(query);
|
|
852
|
+
return outreach || namedPlay;
|
|
853
|
+
}
|
|
767
854
|
/**
|
|
768
855
|
* "LinkedIn posts about X" / "posts mentioning Y" / "monitor LinkedIn posts by
|
|
769
856
|
* keyword" is keyword post search, a Signals source with all-of / any-of /
|
|
@@ -849,6 +936,15 @@ const UGC_ROUTE_VIEW = {
|
|
|
849
936
|
function routeCardFor(card, query) {
|
|
850
937
|
if (card.primitive === "publishing" && isUgcIntent(query))
|
|
851
938
|
return { ...card, ...UGC_ROUTE_VIEW };
|
|
939
|
+
const contactFind = card.id === "sourcing-and-provider-tools" ? oneShotContactFindingRecommendation(query) : null;
|
|
940
|
+
if (contactFind)
|
|
941
|
+
card = { ...card, execution: contactFind.execution };
|
|
942
|
+
const crmContactRead = card.id === "records" ? storedCrmContactReadRecommendation(query) : null;
|
|
943
|
+
if (crmContactRead)
|
|
944
|
+
card = { ...card, posture: "read_only", execution: crmContactRead.execution };
|
|
945
|
+
const replyDraftRead = card.primitive === "messages" ? storedReplyDraftReadRecommendation(query) : null;
|
|
946
|
+
if (replyDraftRead)
|
|
947
|
+
card = { ...card, posture: "read_only", execution: replyDraftRead.execution };
|
|
852
948
|
if (card.id === "knowledge-graph" && isWorkspaceVoiceIntent(query))
|
|
853
949
|
card = { ...card, execution: WORKSPACE_VOICE_EXECUTION };
|
|
854
950
|
if (card.primitive === "workflows" && isRecurringCompanyMonitoringIntent(query))
|
|
@@ -859,7 +955,7 @@ function routeCardFor(card, query) {
|
|
|
859
955
|
if (card.primitive === "tables" && isTableCapacityReadIntent(query))
|
|
860
956
|
card = {
|
|
861
957
|
...card,
|
|
862
|
-
execution: "Read oxygen_limits_show
|
|
958
|
+
execution: "Read oxygen_limits_show with section=capacity for largest_table and largest_table_rows. Counts cover all registered workspace Tables, including CRM and archived Tables, and are available only when row_usage_reconciled is true; pending counts are unknown. Use the returned table reference with oxygen_tables_query and a small limit for sample rows. Planned capacity is not current headroom.",
|
|
863
959
|
};
|
|
864
960
|
if (card.primitive === "tables" && isFunctionIntent(query))
|
|
865
961
|
card = {
|
|
@@ -877,6 +973,9 @@ function routeCardFor(card, query) {
|
|
|
877
973
|
const read = card.primitive === "tables" ? tableReadRecommendation(query) : null;
|
|
878
974
|
if (read)
|
|
879
975
|
card = { ...card, execution: read.execution };
|
|
976
|
+
const sent = card.id === "connected-linkedin" ? sentLinkedInInvitationReadRecommendation(query) : null;
|
|
977
|
+
if (sent)
|
|
978
|
+
card = { ...card, execution: sent.execution };
|
|
880
979
|
const database = card.id === "platform-operations" ? databaseRecommendation(query) : null;
|
|
881
980
|
if (database)
|
|
882
981
|
card = { ...card,
|
|
@@ -1017,6 +1116,16 @@ export const CAPABILITY_ROUTE_MCP_CALLS = {
|
|
|
1017
1116
|
// Like the CLI command, it leaves the LinkedIn risk acknowledgement to the user.
|
|
1018
1117
|
senderConnect: () => ({ tool: "oxygen_senders_connect", arguments: { country: "<cc>" } }),
|
|
1019
1118
|
rowClassification: () => ({ tool: "oxygen_columns_catalog", arguments: { family: "classification" } }),
|
|
1119
|
+
rowFanOut: () => ({
|
|
1120
|
+
tool: "oxygen_tables_send",
|
|
1121
|
+
arguments: {
|
|
1122
|
+
source: "<source table>",
|
|
1123
|
+
target: "<target table>",
|
|
1124
|
+
flatten: { column: "<column holding the array>", path: "<path to the array>" },
|
|
1125
|
+
mapping: [{ target: "<target column>", source: { type: "column", key: "$element" }, path: "<field in each element>" }],
|
|
1126
|
+
dry_run: true,
|
|
1127
|
+
},
|
|
1128
|
+
}),
|
|
1020
1129
|
// The tool has no `family`: it always searches linkedin_posts, which the API reads as the same source.
|
|
1021
1130
|
linkedinKeywordPosts: () => ({
|
|
1022
1131
|
tool: "oxygen_signals_search_preview",
|
|
@@ -1102,6 +1211,21 @@ function isOpenLinkedInRequestsIntent(query) {
|
|
|
1102
1211
|
return /\b(connection|invitation|invite|connect) requests?\b/.test(query)
|
|
1103
1212
|
|| /\b(pending|open|unanswered|outstanding|received|sent) (linkedin )?(invitations?|invites?)\b/.test(query);
|
|
1104
1213
|
}
|
|
1214
|
+
/** One live sent-invitation page, separate from recurring capture and writes. */
|
|
1215
|
+
function sentLinkedInInvitationReadRecommendation(query) {
|
|
1216
|
+
if (!/\blinkedin\b/.test(query) || !/\b(invitations?|invites?|connection requests?)\b/.test(query)
|
|
1217
|
+
|| !/\b(sent|outgoing)\b/.test(query)
|
|
1218
|
+
|| /\b(incoming|received|mixed|tables?|workflows?|agents?|functions?|crm|mailbox\w*|setup|capture|monitor\w*|sync|recurring|schedule\w*|cron|send|sending|enroll|withdraw\w*|cancel|accept|decline|sequences?|outreach|cadence|research|third party|public)\b/.test(query))
|
|
1219
|
+
return null;
|
|
1220
|
+
if (!/\b(read|list|show|inspect|review|resume|next page|cursor|pagination)\b/.test(query)
|
|
1221
|
+
&& !/\b(cli|command line|native command)\b/.test(query))
|
|
1222
|
+
return null;
|
|
1223
|
+
return {
|
|
1224
|
+
tools: ["oxygen_linkedin_invites_sent", "oxygen_senders_list"],
|
|
1225
|
+
commands: ["linkedin invitations list", "senders list"],
|
|
1226
|
+
execution: "Read one live, free pending-sent invitation page with oxygen_linkedin_invites_sent via oxygen_capabilities_read; CLI twin: oxygen linkedin invitations list --account <ref> --limit <n> --cursor <token> --json. Resolve the sender with oxygen_senders_list. Resume invitations.cursor unchanged with the same account; a full v2 page can need one empty final page. No provider writes or credits. Recurring received/sent capture is linkedin invitations setup; withdrawal is a separate external action.",
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1105
1229
|
/**
|
|
1106
1230
|
* The apps a person connected with OAuth and where each one works: "make
|
|
1107
1231
|
* ChatGPT always use workspace X", "default workspace for my Claude
|
|
@@ -1163,7 +1287,7 @@ function isConnectedAppSpendIntent(query) {
|
|
|
1163
1287
|
return !/\b(linkedin|senders?|mailbox(?:es)?|sequences?|email|emails|whatsapp|warm\s?up|invites?|rate)\b/.test(query);
|
|
1164
1288
|
}
|
|
1165
1289
|
/**
|
|
1166
|
-
* The web app's sidebar pages a
|
|
1290
|
+
* The web app's sidebar pages a person hides from their own sidebar (Settings →
|
|
1167
1291
|
* Customise, `settings pages`). Both blind baselines of 2026-09-30 searched
|
|
1168
1292
|
* "hide navigation pages sidebar", "customise pages", "pages visibility", "nav
|
|
1169
1293
|
* items" and "workspace navigation hidden pages unibox publishing workflows",
|
|
@@ -1185,6 +1309,14 @@ function isWorkspacePagesIntent(query) {
|
|
|
1185
1309
|
return true;
|
|
1186
1310
|
if (/\bmenu items?\b/.test(query) && /\b(hid(?:e|es|den|ing)|unhide|show(?:s|n|ing)?|visib\w*)\b/.test(query))
|
|
1187
1311
|
return true;
|
|
1312
|
+
// Sidebar page names with a hide verb and a personal scope ("hide Unibox and
|
|
1313
|
+
// Workflows only for me") name the sidebar, not those pages' data; both
|
|
1314
|
+
// personal-sidebar acceptance runs (2026-10-02) found this routed to Messages.
|
|
1315
|
+
if (/\b(unibox|workflows|publishing|sequencer|tables|crm|analytics|knowledge)\b/.test(query)
|
|
1316
|
+
&& /\b(hid(?:e|es|den|ing)|unhide)\b/.test(query)
|
|
1317
|
+
&& /\b(for me|only me|just me|myself|my own|personally)\b/.test(query)
|
|
1318
|
+
&& !/\b(messages?|emails?|threads?|conversations?|repl(?:y|ies)|contacts?|leads?|runs?)\b/.test(query))
|
|
1319
|
+
return true;
|
|
1188
1320
|
// Content and navigation within a primitive keep that primitive's owner.
|
|
1189
1321
|
// Check this before generic "customize pages" or a navigation noun.
|
|
1190
1322
|
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))
|
|
@@ -1246,16 +1378,17 @@ function tableReadRecommendation(query) {
|
|
|
1246
1378
|
if (/\b(filtered?|filters?|sorts?|search|crm)\b/.test(query) && /\b(grid|counts?|registry)\b/.test(query))
|
|
1247
1379
|
return {
|
|
1248
1380
|
tools: ["oxygen_table_rows_page", "oxygen_tables_query", "oxygen_tables_preview"], commands: ["tables rows page", "tables query", "tables preview"],
|
|
1249
|
-
execution: "
|
|
1381
|
+
execution: "Use tables rows page for filtered/sorted/searchable grid cells and summarized run state, with CRM scope and count modes. exact counts matches; estimate uses reconciled registry only when unnarrowed, else exact. registry never counts and returns null for narrowed/unreconciled scope; skip returns no total (null). Cursor walks retain the same read scope; offset supports random access. Raw tables query supports its declared selectors; query format=grid refuses them.",
|
|
1250
1382
|
};
|
|
1251
|
-
if (/\b(
|
|
1383
|
+
if (/\b(fill rates?|without row|summary only|summary_only)\b/.test(query)
|
|
1384
|
+
|| (/\bpreview\b/.test(query) && !/\bgrid\b/.test(query)))
|
|
1252
1385
|
return {
|
|
1253
1386
|
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
|
|
1387
|
+
execution: "Use tables preview for projected rows and whole-table fill stats; include_cell_states adds compact state and summary_only omits row JSON. rowCount is exact; summary.statsSampled labels sampled rates. Its cursor pages the MCP widget with a minimal plain receipt, not preview row/stat JSON. For data paging use tables query format=grid or tables rows page; fill-progress reads ingestion state. These reads do not run columns.",
|
|
1255
1388
|
};
|
|
1256
1389
|
return {
|
|
1257
1390
|
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:
|
|
1391
|
+
execution: "Use tables query for raw values, format=\"grid\" for cell previews and summarized run state, or tables preview for fill rates. Grid HTTP/CLI defaults 50 and caps 200; MCP defaults 50 and clamps supplied limits to 100. Follow grid.nextCursor with the same read scope; hasMore=false and nextCursor=null end the walk, without a snapshot. Grid refuses row_id, fields, filters/filterTree, search, sorts, formula_values and nonzero offset; system/widget flags do not change it. Without stats, first-page rowCountMode is registry if reconciled, else exact; cursor pages are skipped (rowCount=null). include_stats can replace rowCount with an exact whole-table count even on continuation; column rates may be sampled and unavailable stats are omitted. include_fill adds ingestion state only to an empty first page, distinct from fill rates. The native max_chars window can shorten a whole-row prefix or refuse an oversized first row/metadata. These reads do not run columns.",
|
|
1259
1392
|
};
|
|
1260
1393
|
}
|
|
1261
1394
|
/** Native database work outranks generic workspace access and credit billing. */
|
|
@@ -1332,9 +1465,65 @@ function tableMaintenanceRecommendation(query) {
|
|
|
1332
1465
|
}
|
|
1333
1466
|
return null;
|
|
1334
1467
|
}
|
|
1468
|
+
/** Stored conversation drafts are Messages data, rather than worker queue health. */
|
|
1469
|
+
function storedReplyDraftReadRecommendation(query) {
|
|
1470
|
+
if (!/\bdrafts?\b/.test(query)
|
|
1471
|
+
|| !/\b(read|inspect|list|show|stored|retained|queue|history|historical)\b/.test(query)
|
|
1472
|
+
|| !/\b(conversations?|threads?|inbox|unibox|repl(?:y|ies)|whatsapp|email|linkedin)\b/.test(query)
|
|
1473
|
+
|| /\b(workers?|jobs?|workflows?|cron|repair|publishing|posts?|creators?|ugc|sequences?|campaigns?)\b/.test(query)
|
|
1474
|
+
|| /\b(approve|approving|send|sending|edit|editing|reject|rejecting|generate|generating|create|creating)\b/.test(query))
|
|
1475
|
+
return null;
|
|
1476
|
+
const whatsapp = /\bwhatsapp\b/.test(query);
|
|
1477
|
+
const otherChannel = /\b(email|linkedin)\b/.test(query);
|
|
1478
|
+
const tools = whatsapp && !otherChannel ? ["oxygen_whatsapp_drafts_list"]
|
|
1479
|
+
: otherChannel && !whatsapp ? ["oxygen_inbox_drafts_list"]
|
|
1480
|
+
: ["oxygen_inbox_drafts_list", "oxygen_whatsapp_drafts_list"];
|
|
1481
|
+
const commands = tools.map(tool => tool === "oxygen_whatsapp_drafts_list" ? "whatsapp drafts list" : "inbox drafts list");
|
|
1482
|
+
return { tools, commands, execution: "Hydrate the selected native draft-read schema. External MCP clients use oxygen_capabilities_read for an unlisted target; built-in agents invoke the native capability. oxygen_inbox_drafts_list covers email/LinkedIn; oxygen_whatsapp_drafts_list covers WhatsApp. When the channel is unknown, inspect both schemas before selecting. Filter by the native conversation and CSV status controls; personal composer drafts stay private. Historical states are current draft statuses, not a transition audit. Generation, edits, approval and sending are separate actions." };
|
|
1483
|
+
}
|
|
1484
|
+
/** Saved contact facts belong to Records, regardless of email/phone channel words. */
|
|
1485
|
+
function storedCrmContactReadRecommendation(query) {
|
|
1486
|
+
if (!/\b(crm|records?)\b/.test(query)
|
|
1487
|
+
|| !/\b(read|show|get|find|lookup|look up|retrieve|inspect|view|describe)\b/.test(query)
|
|
1488
|
+
|| !/\b(emails?|phones?|mobile|contact details|contact information)\b/.test(query)
|
|
1489
|
+
|| /\b(send\w*|repl(?:y|ies)|messages?|inbox|threads?|drafts?|posts?|comments?|outreach|sequences?|campaigns?|dial\w*|call|create|add|edit|update|change|delete|remove|merge|import|sync|missing|new|enrich\w*|providers?|waterfall|verify|verification|validate|tables?|rows?|columns?|spreadsheet|dataset|workflows?|schedule\w*|recurring|cron)\b/.test(query))
|
|
1490
|
+
return null;
|
|
1491
|
+
return {
|
|
1492
|
+
tools: ["oxygen_crm_record_search", "oxygen_crm_record_get", "oxygen_crm_object_describe"],
|
|
1493
|
+
commands: ["crm search", "crm get", "crm describe"],
|
|
1494
|
+
execution: "Read saved Records; no lookup or send. Hydrate search/get schemas; mode=suggest finds partial names, records resolves identities. Get takes object + row_id; describe the object or one attribute for unknown field keys. External MCP uses oxygen_capabilities_read for these unlisted reads; built-in agents invoke the native tools. Missing contact enrichment and CRM writes are separate actions.",
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
/** Finding contact details is a native lookup; channel words alone do not mean sending. */
|
|
1498
|
+
function oneShotContactFindingRecommendation(query) {
|
|
1499
|
+
if (/\b(send\w*|repl(?:y|ies)|messages?|inbox|threads?|drafts?|posts?|comments?|engagement|audiences?|followers?|outreach|sequences?|campaigns?|dial\w*|call|crm|records?|stored|saved|verify|verification|validate|deliverability|mailboxes?|warmup|accounts?|limits?|tables?|rows?|columns?|spreadsheet|dataset|enrich\w*|bulk|batch|workflows?|schedule\w*|recurring|weekly|daily|every|cron|workers?|jobs?|providers?|catalog|schemas?|metadata|pricing|personal|private|home|scrape\w*|public)\b/.test(query)
|
|
1500
|
+
|| /\bfind companies\b/.test(query))
|
|
1501
|
+
return null;
|
|
1502
|
+
const verb = /\b(find|lookup|look up|get|locate|obtain)\b/.exec(query);
|
|
1503
|
+
if (!verb)
|
|
1504
|
+
return null;
|
|
1505
|
+
const target = query.slice(verb.index + verb[0].length, verb.index + verb[0].length + 120);
|
|
1506
|
+
const email = /\bemails?\b/.test(target);
|
|
1507
|
+
const phone = /\bphones?\b|\bmobile numbers?\b/.test(target);
|
|
1508
|
+
if (!email && !phone)
|
|
1509
|
+
return null;
|
|
1510
|
+
return {
|
|
1511
|
+
tools: [...(email ? ["oxygen_find_email"] : []), ...(phone ? ["oxygen_find_phone"] : []), "oxygen_tools_search"],
|
|
1512
|
+
commands: [...(email ? ["find email"] : []), ...(phone ? ["find phone"] : []), "tools search"],
|
|
1513
|
+
execution: "Hydrate the selected native one-shot lookup schema. mode=dry_run previews its waterfall; live requires the user's approved exact scope and positive max_credits. readOnlyHint=false and openWorldHint=true still apply to previews. External MCP uses oxygen_capabilities_call for an unlisted lookup; built-in agents call the native capability under their existing policy. Free catalog metadata uses the read gateway. Sending and bulk Table enrichment have separate owners.",
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1335
1516
|
function explicitCapabilityIntent(query) {
|
|
1517
|
+
if (sentLinkedInInvitationReadRecommendation(query))
|
|
1518
|
+
return ROUTE_BY_ID.get("connected-linkedin") ?? null;
|
|
1336
1519
|
if (databaseRecommendation(query))
|
|
1337
1520
|
return ROUTE_BY_ID.get("platform-operations") ?? null;
|
|
1521
|
+
if (oneShotContactFindingRecommendation(query))
|
|
1522
|
+
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
1523
|
+
if (storedCrmContactReadRecommendation(query))
|
|
1524
|
+
return ROUTE_BY_ID.get("records") ?? null;
|
|
1525
|
+
if (storedReplyDraftReadRecommendation(query))
|
|
1526
|
+
return ROUTE_BY_PRIMITIVE.get("messages") ?? null;
|
|
1338
1527
|
if (isConnectedAppsIntent(query) || isConnectedAppEventsIntent(query) || isConnectedAppSpendIntent(query)) {
|
|
1339
1528
|
return ROUTE_BY_ID.get("workspace-access") ?? null;
|
|
1340
1529
|
}
|
|
@@ -1503,6 +1692,8 @@ function explicitCapabilityIntent(query) {
|
|
|
1503
1692
|
// and the "label" wording fell to the Tags card below.
|
|
1504
1693
|
if (isRowClassificationIntent(query))
|
|
1505
1694
|
return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
|
|
1695
|
+
if (isRowFanOutIntent(query))
|
|
1696
|
+
return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
|
|
1506
1697
|
if (isTableMaintenanceIntent(query))
|
|
1507
1698
|
return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
|
|
1508
1699
|
if (/\b(tag|tags|label|labels)\b/.test(query))
|
|
@@ -1623,12 +1814,27 @@ function isPublishingPostIntent(query) {
|
|
|
1623
1814
|
&& !(/\b(find|search|scrape|research)\b/.test(query) && /\b(about|mention|containing)\b/.test(query));
|
|
1624
1815
|
}
|
|
1625
1816
|
function recommendationsFor(card, query) {
|
|
1817
|
+
const contactFind = card.id === "sourcing-and-provider-tools" ? oneShotContactFindingRecommendation(query) : null;
|
|
1818
|
+
if (contactFind)
|
|
1819
|
+
return contactFind;
|
|
1820
|
+
const crmContactRead = card.id === "records" ? storedCrmContactReadRecommendation(query) : null;
|
|
1821
|
+
if (crmContactRead)
|
|
1822
|
+
return crmContactRead;
|
|
1823
|
+
const replyDraftRead = card.primitive === "messages" ? storedReplyDraftReadRecommendation(query) : null;
|
|
1824
|
+
if (replyDraftRead)
|
|
1825
|
+
return replyDraftRead;
|
|
1826
|
+
const sent = card.id === "connected-linkedin" ? sentLinkedInInvitationReadRecommendation(query) : null;
|
|
1827
|
+
if (sent)
|
|
1828
|
+
return sent;
|
|
1626
1829
|
const database = card.id === "platform-operations" ? databaseRecommendation(query) : null;
|
|
1627
1830
|
if (database)
|
|
1628
1831
|
return database;
|
|
1629
1832
|
if (card.id === "billing-and-budgets" && /\b(credit prices|credit price catalog|customer credit)\b/.test(query)) {
|
|
1630
1833
|
return { tools: ["oxygen_customer_credit_prices"], commands: ["pricing customer-credit-prices"] };
|
|
1631
1834
|
}
|
|
1835
|
+
if (card.id === "billing-and-budgets" && /\b(plan|plans|annual|yearly|subscription)\b/.test(query)) {
|
|
1836
|
+
return { tools: ["oxygen_billing_balance", "oxygen_limits_show", "oxygen_billing_change_plan"], commands: ["billing balance", "limits show", "billing change"] };
|
|
1837
|
+
}
|
|
1632
1838
|
if (card.id === "messages" && /\b(unibox|inbox)\b/.test(query) && /\b(metadata|taxonomy|filters|accounts|tags)\b/.test(query)) {
|
|
1633
1839
|
return { tools: ["oxygen_unibox_metadata"], commands: ["unibox metadata"] };
|
|
1634
1840
|
}
|
|
@@ -1805,12 +2011,15 @@ function recommendationsFor(card, query) {
|
|
|
1805
2011
|
"oxygen_mailboxes_compatibility",
|
|
1806
2012
|
"oxygen_mailboxes_import",
|
|
1807
2013
|
"oxygen_mailboxes_connect_oauth",
|
|
2014
|
+
"oxygen_mailboxes_sync_status",
|
|
2015
|
+
"oxygen_mailboxes_sync",
|
|
1808
2016
|
"oxygen_mailboxes_oauth_health",
|
|
1809
2017
|
],
|
|
1810
2018
|
commands: [
|
|
1811
2019
|
"mailboxes compatibility",
|
|
1812
2020
|
"mailboxes import",
|
|
1813
2021
|
"mailboxes connect-oauth",
|
|
2022
|
+
"mailboxes sync",
|
|
1814
2023
|
"mailboxes oauth-health",
|
|
1815
2024
|
],
|
|
1816
2025
|
};
|
|
@@ -1868,8 +2077,8 @@ function recommendationsFor(card, query) {
|
|
|
1868
2077
|
if (isTableCapacityReadIntent(query))
|
|
1869
2078
|
return {
|
|
1870
2079
|
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: {} },
|
|
2080
|
+
commands: ["limits show --section capacity", "tables query", "tables describe"],
|
|
2081
|
+
exactMcpCall: { tool: "oxygen_limits_show", arguments: { section: "capacity" } },
|
|
1873
2082
|
};
|
|
1874
2083
|
if (isFunctionIntent(query)) {
|
|
1875
2084
|
if (/\b(?:list|show|read|see|my)\b.{0,30}\bfavou?rites?\b/.test(query))
|
|
@@ -1948,6 +2157,15 @@ function recommendationsFor(card, query) {
|
|
|
1948
2157
|
commands: ["tables watcher preview", "tables watcher create", "tables watcher get", "tables watcher update", "tables watcher pause", "tables watcher resume"],
|
|
1949
2158
|
};
|
|
1950
2159
|
}
|
|
2160
|
+
if (isRowFanOutIntent(query)) {
|
|
2161
|
+
// `tables send --flatten` writes one row per array element (free, durable).
|
|
2162
|
+
return {
|
|
2163
|
+
tools: ["oxygen_tables_send"],
|
|
2164
|
+
commands: ["tables send"],
|
|
2165
|
+
exactCommand: ROW_FAN_OUT_COMMAND,
|
|
2166
|
+
exactMcpCall: CAPABILITY_ROUTE_MCP_CALLS.rowFanOut(),
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
1951
2169
|
if (isRowClassificationIntent(query)) {
|
|
1952
2170
|
// The classification list (free) names the template production has and,
|
|
1953
2171
|
// where the deployment runs it, the native Classify column; any other
|
|
@@ -2177,6 +2395,16 @@ function isEmailVerificationIntent(query) {
|
|
|
2177
2395
|
* column through `columns add`, which the route recommends next.
|
|
2178
2396
|
*/
|
|
2179
2397
|
export const ROW_CLASSIFICATION_COMMAND = "oxygen columns catalog --family classification";
|
|
2398
|
+
export const ROW_FAN_OUT_COMMAND = "oxygen tables send <source> <target> --flatten <column> --flatten-path <path> --mapping '[{\"target\":\"<column>\",\"source\":{\"type\":\"column\",\"key\":\"$element\"},\"path\":\"<field>\"}]' --dry-run";
|
|
2399
|
+
// "flatten the search results into one row per company", "explode this JSON array
|
|
2400
|
+
// into rows". Both MCP acceptance agents for OXP-1.39.2 (S26) reported no native way
|
|
2401
|
+
// to turn a search tool column's result array into rows, and two capability searches
|
|
2402
|
+
// never surfaced `tables send`, which does exactly that with --flatten.
|
|
2403
|
+
function isRowFanOutIntent(query) {
|
|
2404
|
+
return /\b(flatten|explode|unnest|fan[ -]?out)\b.{0,60}\b(rows?|arrays?|lists?|json|results?|cells?)\b/.test(query)
|
|
2405
|
+
|| /\b(arrays?|lists?|results?)\b.{0,40}\binto\b.{0,20}\bone row (?:per|for each)\b/.test(query)
|
|
2406
|
+
|| /\b(json array|array cell|list cell)\b.{0,40}\brows?\b/.test(query);
|
|
2407
|
+
}
|
|
2180
2408
|
/** Nouns another primitive owns: a classify ask that names one keeps that owner. */
|
|
2181
2409
|
const ROW_CLASSIFICATION_OTHER_OWNER = /\b(inbox|repl(?:y|ies)|conversations?|threads?|messages?|dms?|e-?mails?|bounces?|signals?|visitors?|workflows?|deals?|crm|records?|icp|knowledge|wiki|pages?|ads|dashboards?|metrics?|agents?|runs)\b/;
|
|
2182
2410
|
/** What one table row is: the noun a per-row classification is about. */
|
|
@@ -2262,12 +2490,17 @@ function isLinkedInAccountConnectIntent(query) {
|
|
|
2262
2490
|
return false;
|
|
2263
2491
|
if (/^(linkedin|unipile)( accounts?)?$/.test(query))
|
|
2264
2492
|
return true;
|
|
2265
|
-
if (/\b(send|sending|enroll|sequence|campaign|cadence|messages?|dms?|invites?|invitations?|posts?|profiles?|network|connections|followers|viewers|limits|hours|ads?|scrape|research|company|companies|url)\b/.test(query))
|
|
2493
|
+
if (/\b(send|sending|enroll|sequence|campaign|cadence|messages?|dms?|invites?|invitations?|posts?|profiles?|network|connections|followers|viewers|limits|hours|ads?|scrape|research|company|companies|url|pac(?:e|ing)|spacing|spaced|spread|apart|throttl\w*)\b/.test(query))
|
|
2266
2494
|
return false;
|
|
2267
2495
|
return /\b(connect|reconnect|link|add|accounts?|senders?|seats?|free|plan)\b/.test(query);
|
|
2268
2496
|
}
|
|
2497
|
+
/**
|
|
2498
|
+
* The connected account's hours, caps or pacing (`senders limits`). The
|
|
2499
|
+
* 2026-10-02 pacing acceptance asked "linkedin sender pacing connection requests
|
|
2500
|
+
* spread over day" and was sent to `senders connect` by the word "sender".
|
|
2501
|
+
*/
|
|
2269
2502
|
function isLinkedInAccountLimitsIntent(query) {
|
|
2270
|
-
return /\b(working hours|sending hours|business hours|office hours|weekdays?|time ?zone|linkedin limits|daily limits
|
|
2503
|
+
return /\b(working hours|sending hours|business hours|office hours|weekdays?|time ?zone|linkedin limits|daily limits?|pac(?:e|ing)|spacing|spaced|spread|apart|throttl\w*)\b/.test(query);
|
|
2271
2504
|
}
|
|
2272
2505
|
function isMutualLinkedInConnectionsIntent(query) {
|
|
2273
2506
|
const linkedInContext = /\blinkedin\b|\bprofiles?\b/.test(query);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Infer } from "./schema.js";
|
|
2
|
+
/** The credential selects the workspace; this optional ref only checks it. */
|
|
3
|
+
export declare const BILLING_OWNER_READ_CONTRACT: import("./schema.js").ContractObject<{
|
|
4
|
+
readonly organization: import("./schema.js").StringProperty;
|
|
5
|
+
}, never>;
|
|
6
|
+
export type BillingOwnerReadInput = Infer<typeof BILLING_OWNER_READ_CONTRACT>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { obj, str } from "./schema.js";
|
|
2
|
+
/** The credential selects the workspace; this optional ref only checks it. */
|
|
3
|
+
export const BILLING_OWNER_READ_CONTRACT = obj({
|
|
4
|
+
organization: str({
|
|
5
|
+
description: "Optional assertion of the current workspace's id, Clerk org id or slug. A mismatch fails before reading billing; this never selects another workspace.",
|
|
6
|
+
}),
|
|
7
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Browser resource operations, using the same scoped handlers on every surface. */
|
|
2
2
|
import { arr, bool, enumOf, int, nullable, num, obj, str } from "./schema.js";
|
|
3
|
+
import { TABLE_ROW_READ_WINDOW_FIELDS } from "./table-row-read.js";
|
|
3
4
|
const text = (description) => str(description ? { description } : {});
|
|
4
5
|
const json = { type: "object", additionalProperties: true };
|
|
5
6
|
const strings = () => arr(str());
|
|
@@ -66,7 +67,7 @@ export const BROWSER_OPERATIONS = [
|
|
|
66
67
|
operation("tables.resource.action_columns", `${table}/action-columns`, "POST", "columns provider-action", "oxygen_table_provider_action_column", "tables", "Preview or create a provider action column; creating it does not execute provider actions.", { provider: text(), label: text(), key: text(), operations: strings(), dry_run: bool() }, ["provider"]),
|
|
67
68
|
operation("tables.resource.rows.page", `${table}/rows`, "GET", "tables rows page", "oxygen_table_rows_page", "tables", "Read a Table row page with the same filter, sort, counts and CRM relationship projection as the grid.", { limit: int({ minimum: 1 }), cursor: text(), offset: int({ minimum: 0 }), f: text("Serialized filter tree."), s: text("Serialized sort state."), q: text(), count: enumOf(["exact", "estimate", "registry", "skip"]), crm_object: text() }),
|
|
68
69
|
operation("tables.resource.rows.insert", `${table}/rows`, "POST", "tables rows insert", "oxygen_table_rows_insert", "tables", "Insert Table row values and return the committed rows plus any existing automatic run or dedupe grant.", { rows: objects(), values: json }),
|
|
69
|
-
operation("tables.resource.rows.get", `${table}/rows/{row}`, "GET", "tables rows inspect", "oxygen_table_row_inspect", "tables", "Read one
|
|
70
|
+
operation("tables.resource.rows.get", `${table}/rows/{row}`, "GET", "tables rows inspect", "oxygen_table_row_inspect", "tables", "Read one row; field selects column keys and hydrate_relations=1 includes relations. projection=value returns lossless JSON fragments; resume value_cursor with identical table/row/field/path.", { hydrate_relations: enumOf(["1"]), field: strings(), ...TABLE_ROW_READ_WINDOW_FIELDS }, [], { repeatedQueryFields: ["field"] }),
|
|
70
71
|
operation("tables.resource.rows.edit", `${table}/rows/{row}`, "PATCH", "tables rows edit", "oxygen_table_row_edit", "tables", "Patch one row's values and return the committed row.", { values: json }, ["values"]),
|
|
71
72
|
operation("tables.resource.rows.delete", `${table}/rows/{row}`, "DELETE", "tables rows remove", "oxygen_table_row_remove", "tables", "Remove one row from a workspace Table."),
|
|
72
73
|
operation("tables.resource.rows.bulk_delete", `${table}/rows/delete`, "POST", "tables rows remove-selection", "oxygen_table_rows_remove_selection", "tables", "Remove exact row ids or the same all-matching filtered selection used by the browser.", { row_ids: strings(), selection }),
|
|
@@ -89,7 +90,8 @@ export const BROWSER_OPERATIONS = [
|
|
|
89
90
|
operation("tables.resource.ingestions.retry", `${table}/ingestions/{ingestion_run}/retry-failed`, "POST", "tables ingestion retry", "oxygen_table_resource_ingestion_retry", "runs", "Retry failed items in this Table's hosted ingestion run."),
|
|
90
91
|
operation("tables.resource.import", "/api/cli/tables/import", "POST", "tables upload", "oxygen_table_resource_upload", "tables", "Preview or import small uploaded CSV, JSON or XLSX bytes through the exact browser import contract. Preview writes no rows.", upload, ["content_base64", "file_name"], { multipart }),
|
|
91
92
|
operation("billing.activate", "/api/cli/billing/activate", "POST", "billing activate", "oxygen_billing_activate", "billing", "Activate a trial subscription now and charge its saved payment method; administrator approval is required. An optional tier reprices the active trial in the same call.", { approved: bool(), tier: text() }, ["approved"], { spends: true }),
|
|
92
|
-
operation("billing.checkout", "/api/cli/billing/checkout", "POST", "billing checkout", "oxygen_billing_checkout", "billing", "Open the same hosted plan checkout as the browser; the returned URL carries the payment approval step.", { plan: text(), interval: enumOf(["month", "year"]), currency: text(), trial: bool(), return_to: text(), source: text(), blocked: text() }, ["plan"]),
|
|
93
|
+
operation("billing.checkout", "/api/cli/billing/checkout", "POST", "billing checkout", "oxygen_billing_checkout", "billing", "Open the same hosted plan checkout as the browser; the returned URL carries the payment approval step.", { plan: text(), interval: enumOf(["month", "year"]), term: enumOf(["month", "year"]), terms_version: text(), currency: text(), trial: bool(), return_to: text(), source: text(), blocked: text() }, ["plan"]),
|
|
94
|
+
operation("billing.confirm", "/api/cli/billing/confirm", "POST", "billing confirm", "oxygen_billing_confirm", "billing", "Confirm an exact unexpired plan-change preview. approved accepts its payment, term and effective date; workspace administrator required.", { token: text(), approved: bool() }, ["token", "approved"], { spends: true }),
|
|
93
95
|
operation("billing.portal", "/api/cli/billing/portal", "POST", "billing portal", "oxygen_billing_portal", "billing", "Create the workspace's authenticated Stripe billing-portal URL; requires workspace administrator authorization.", { returnUrl: text() }),
|
|
94
96
|
operation("billing.ownership.get", "/api/cli/billing/ownership", "GET", "billing ownership get", "oxygen_billing_ownership_get", "billing", "Read the active workspace's shared billing owner, candidate owners and credit cap."),
|
|
95
97
|
operation("billing.ownership.set", "/api/cli/billing/ownership", "POST", "billing ownership set", "oxygen_billing_ownership_set", "billing", "Set the billing owner and optional monthly credit cap using the browser's administrator and workspace eligibility checks.", { billing_owner_organization_id: text(), monthly_credit_cap: nullable(num({ minimum: 0 })) }, ["billing_owner_organization_id"]),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const COPILOT_SESSION_STATUSES: readonly ["active", "completed", "cancelled", "failed"];
|
|
2
|
+
export declare const COPILOT_SESSIONS_LIST_CONTRACT: import("./schema.js").ContractObject<{
|
|
3
|
+
readonly status: import("./schema.js").StringProperty;
|
|
4
|
+
readonly limit: {
|
|
5
|
+
type: "integer";
|
|
6
|
+
description?: string;
|
|
7
|
+
minimum?: number;
|
|
8
|
+
maximum?: number;
|
|
9
|
+
"x-clamp"?: true;
|
|
10
|
+
};
|
|
11
|
+
}, never>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Stored attended session-list selectors. Native HTTP trimming/clamping remains authoritative. */
|
|
2
|
+
import { int, obj, str } from "./schema.js";
|
|
3
|
+
export const COPILOT_SESSION_STATUSES = ["active", "completed", "cancelled", "failed"];
|
|
4
|
+
export const COPILOT_SESSIONS_LIST_CONTRACT = obj({
|
|
5
|
+
status: str({ description: `Session status: ${COPILOT_SESSION_STATUSES.join(", ")}. Separate from latest turn status; omit for all.` }),
|
|
6
|
+
limit: int({ minimum: 1, description: "Newest sessions first. Default 50; native maximum 200 (larger values clamp)." }),
|
|
7
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const CRM_FILE_BYTES_MAX = 2048;
|
|
2
|
+
export declare const CRM_FILE_BYTES_QUERY_CONTRACT: import("./schema.js").ContractObject<{
|
|
3
|
+
readonly offset: {
|
|
4
|
+
type: "integer";
|
|
5
|
+
description?: string;
|
|
6
|
+
minimum?: number;
|
|
7
|
+
maximum?: number;
|
|
8
|
+
"x-clamp"?: true;
|
|
9
|
+
};
|
|
10
|
+
readonly limit: {
|
|
11
|
+
type: "integer";
|
|
12
|
+
description?: string;
|
|
13
|
+
minimum?: number;
|
|
14
|
+
maximum?: number;
|
|
15
|
+
"x-clamp"?: true;
|
|
16
|
+
};
|
|
17
|
+
readonly sha256: import("./schema.js").StringProperty;
|
|
18
|
+
readonly max_chars: {
|
|
19
|
+
type: "integer";
|
|
20
|
+
description?: string;
|
|
21
|
+
minimum?: number;
|
|
22
|
+
maximum?: number;
|
|
23
|
+
"x-clamp"?: true;
|
|
24
|
+
};
|
|
25
|
+
}, never>;
|
|
26
|
+
/** A same-original resume address. Credentials and organizations never appear in it. */
|
|
27
|
+
export declare function crmFileBytesUri(input: {
|
|
28
|
+
attachmentId: string;
|
|
29
|
+
sha256: string;
|
|
30
|
+
offset: number;
|
|
31
|
+
limit: number;
|
|
32
|
+
maxChars: number;
|
|
33
|
+
}): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { int, obj, str } from "./schema.js";
|
|
2
|
+
export const CRM_FILE_BYTES_MAX = 2048;
|
|
3
|
+
export const CRM_FILE_BYTES_QUERY_CONTRACT = obj({
|
|
4
|
+
offset: int({ minimum: 0, maximum: 100 * 1024 * 1024, description: "Byte offset; default 0. Resume at next_offset." }),
|
|
5
|
+
limit: int({ minimum: 1, maximum: CRM_FILE_BYTES_MAX, description: "Original bytes requested; default 2048. Context bounds may return fewer." }),
|
|
6
|
+
sha256: str({ minLength: 64, maxLength: 64, description: "Original SHA-256 from the previous chunk; required when resuming." }),
|
|
7
|
+
max_chars: int({ minimum: 1024, maximum: 40_000, description: "Complete serialized chunk window. Smaller native windows preserve exact continuation." }),
|
|
8
|
+
});
|
|
9
|
+
/** A same-original resume address. Credentials and organizations never appear in it. */
|
|
10
|
+
export function crmFileBytesUri(input) {
|
|
11
|
+
return `oxygen://crm/files/${encodeURIComponent(input.attachmentId)}/original/${input.sha256}/${input.offset}/${input.limit}?max_chars=${input.maxChars}`;
|
|
12
|
+
}
|