@oxygen-agent/cli 1.948.1 → 1.987.20
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/admin-primary-providers-render.js +9 -1
- package/dist/cli-values.d.ts +14 -0
- package/dist/cli-values.js +26 -0
- package/dist/command-manifest.js +6 -0
- package/dist/functions-commands.js +33 -9
- package/dist/help.d.ts +21 -0
- package/dist/help.js +94 -0
- package/dist/index.js +1493 -297
- package/dist/knowledge-repository-commands.d.ts +6 -0
- package/dist/knowledge-repository-commands.js +198 -0
- package/dist/skills.js +20 -0
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1086
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +71 -1
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +69 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +374 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +27 -27
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +127 -18
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +12 -4
- package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/index.js +5 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +32 -38
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +38 -41
- package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +48 -8
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +64 -2
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +152 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +304 -4
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +3 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
- package/node_modules/@oxygen/shared/package.json +25 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/package.json +6 -2
|
@@ -56,11 +56,11 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
56
56
|
primitive: null,
|
|
57
57
|
owns: "First-run setup and interactive Workspace Copilot sessions over the shared primitive contracts.",
|
|
58
58
|
notFor: "Recurring deterministic automation or standing adaptive jobs; use Workflows or Agents.",
|
|
59
|
-
execution: "
|
|
59
|
+
execution: "Use existing onboarding context to understand the user goal and GTM bottleneck, recommend a useful next improvement, and guide its native implementation when selected.",
|
|
60
60
|
posture: "mixed",
|
|
61
61
|
gatewayTools: ["oxygen_whoami", "oxygen_recipes_list", "oxygen_blueprints_list", "oxygen_context_resolve", "oxygen_copilot_start"],
|
|
62
|
-
gatewayCommands: ["whoami", "
|
|
63
|
-
skills: ["oxygen-quickstart", "oxygen-gtm"],
|
|
62
|
+
gatewayCommands: ["whoami", "context resolve", "recipes list", "blueprints list", "copilot start"],
|
|
63
|
+
skills: ["oxygen-onboarding", "oxygen-quickstart", "oxygen-gtm"],
|
|
64
64
|
// `activation` remains an exact legacy API family for saved clients, but it
|
|
65
65
|
// has no recommended gateway and is not an intent term or first-touch path.
|
|
66
66
|
endpointSections: ["activation", "copilot", "home"],
|
|
@@ -68,7 +68,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
68
68
|
// users say ("next step of this workflow run"), and a 6-point multi-word hit
|
|
69
69
|
// would drag those queries here. The terms kept are the ones only a founder
|
|
70
70
|
// asking where they are in setup actually types.
|
|
71
|
-
intentTerms: ["onboard", "onboarding", "first run", "first day", "setup oxygen", "copilot", "assistant", "guided", "where am i", "next step in setup", "what do i do next"],
|
|
71
|
+
intentTerms: ["onboard", "onboarding", "first run", "first day", "setup oxygen", "copilot", "assistant", "guided", "where am i", "next step in setup", "what do i do next", "gtm consultant", "gtm planning", "improve our gtm operation"],
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
id: "billing-and-budgets",
|
|
@@ -82,7 +82,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
82
82
|
gatewayCommands: ["billing balance", "billing usage", "budget list"],
|
|
83
83
|
skills: ["oxygen-gtm", "oxygen-diagnostics"],
|
|
84
84
|
endpointSections: ["billing", "budget", "limits"],
|
|
85
|
-
intentTerms: ["billing", "credits", "credit balance", "usage", "spend", "cost", "budget", "subscription", "invoice", "cancel plan"],
|
|
85
|
+
intentTerms: ["billing", "credits", "credit balance", "usage", "spend", "cost", "budget", "subscription", "invoice", "cancel plan", "change plan", "upgrade", "downgrade"],
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
id: "support-and-feedback",
|
|
@@ -174,7 +174,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
174
174
|
gatewayCommands: ["knowledge search", "knowledge index", "knowledge page get"],
|
|
175
175
|
skills: ["oxygen-knowledge"],
|
|
176
176
|
endpointSections: ["context", "knowledge"],
|
|
177
|
-
intentTerms: ["knowledge", "wiki", "context", "icp", "positioning", "offer", "rubric", "brand", "voice", "company profile"],
|
|
177
|
+
intentTerms: ["knowledge", "wiki", "context", "icp", "ideal customer", "positioning", "offer", "rubric", "brand", "voice", "company profile", "github", "obsidian", "vault", "repository sync", "markdown import"],
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
id: "recipes",
|
|
@@ -182,13 +182,13 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
182
182
|
primitive: "recipes",
|
|
183
183
|
owns: "Versioned advisory playbooks that teach when and why to run proven GTM motions through native primitives.",
|
|
184
184
|
notFor: "A hidden runtime, mandatory bundle, or replacement for hosted Workflows.",
|
|
185
|
-
execution: "Browse
|
|
185
|
+
execution: "Browse the Recipe, apply its kit (ordered Blueprint stages, one approval, 0 credits, workflows disabled), then run the play through native primitives.",
|
|
186
186
|
posture: "workspace_write",
|
|
187
187
|
gatewayTools: ["oxygen_recipes_list", "oxygen_recipes_get"],
|
|
188
|
-
gatewayCommands: ["recipes list", "recipes show", "recipes install"],
|
|
189
|
-
skills: ["oxygen-recipes"],
|
|
188
|
+
gatewayCommands: ["recipes list", "recipes show", "recipes apply", "recipes install"],
|
|
189
|
+
skills: ["oxygen-recipes", "oxygen-playbooks"],
|
|
190
190
|
endpointSections: ["blueprints", "recipes", "templates"],
|
|
191
|
-
intentTerms: ["recipe", "playbook", "proven play", "activation play", "setup play", "journey", "day 1", "day 7", "day 30", "blueprint", "template", "what should i do"],
|
|
191
|
+
intentTerms: ["recipe", "playbook", "proven play", "activation play", "setup play", "journey", "day 1", "day 7", "day 30", "blueprint", "template", "what should i do", "inbound led outbound", "engagers into a sequence", "warm outbound from my posts", "whole motion", "end to end motion", "kit"],
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
id: "records",
|
|
@@ -210,18 +210,23 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
210
210
|
primitive: "tables",
|
|
211
211
|
owns: "Typed working datasets, rows, formulas, AI/tool/waterfall columns, reusable Functions with isolated drafts and published versions, cell state, projects, and run provenance.",
|
|
212
212
|
notFor: "Canonical CRM truth, message cadence, or an off-platform spreadsheet runtime.",
|
|
213
|
-
execution: "Create and run work in hosted OXYGEN Tables; validate a small sample before bounded paid runs. 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.",
|
|
213
|
+
execution: "Create and run work in hosted OXYGEN Tables; validate a small sample before bounded paid runs. 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.",
|
|
214
214
|
posture: "mixed",
|
|
215
|
-
gatewayTools: ["oxygen_tables_create", "oxygen_columns_add", "oxygen_enrich_column_preview", "oxygen_tables_link_bulk", "oxygen_callables_manage"],
|
|
216
|
-
gatewayCommands: ["tables create", "columns add", "enrich-column preview", "tables link", "functions list", "functions draft"],
|
|
215
|
+
gatewayTools: ["oxygen_tables_create", "oxygen_columns_add", "oxygen_columns_catalog", "oxygen_enrich_column_preview", "oxygen_tables_link_bulk", "oxygen_callables_manage"],
|
|
216
|
+
gatewayCommands: ["tables create", "columns add", "columns catalog", "enrich-column preview", "tables link", "functions list", "functions draft"],
|
|
217
217
|
skills: ["oxygen-gtm", "oxygen-table-tidy", "oxygen-diagnostics", "oxygen-clay-migration", "oxygen-linkedin-marketing"],
|
|
218
|
-
endpointSections: ["action-columns", "callables", "functions", "columns", "company-enrichment", "enrich-column", "enrichment", "projects", "table-action-items", "table-action-runs", "table-ingestion-runs", "tables"],
|
|
218
|
+
endpointSections: ["action-columns", "callables", "functions", "columns", "company-enrichment", "enrich-column", "enrichment", "projects", "supabase", "table-action-items", "table-action-runs", "table-ingestion-runs", "tables"],
|
|
219
219
|
// "link"/"join"/"connect"/"relate" route here for `tables link`. Added after a
|
|
220
220
|
// blind user eval asked for exactly "link two tables" and was routed to
|
|
221
221
|
// `tables create` / `columns add` / `enrich-column preview` — none of which
|
|
222
222
|
// do it. The agent only found the right command by grepping the raw 25k-line
|
|
223
223
|
// command manifest, which is not a discovery path a customer has.
|
|
224
|
-
|
|
224
|
+
// "founders" / "parent company" / "cloud provider" / "company research" route
|
|
225
|
+
// here for `columns catalog`. Added after the 2026-09-16 blind baseline asked
|
|
226
|
+
// `capabilities search "founders of a company"` and got `route: null` with a
|
|
227
|
+
// generic sourcing pointer, then hand-wrote four research prompts for
|
|
228
|
+
// questions the template catalog now answers by key.
|
|
229
|
+
intentTerms: ["table", "spreadsheet", "rows", "column", "columns", "dataset", "csv", "import", "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"],
|
|
225
230
|
},
|
|
226
231
|
{
|
|
227
232
|
id: "messages",
|
|
@@ -324,7 +329,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
324
329
|
// to workspace-access — "identity" matched the AUTH sense. The nouns a user
|
|
325
330
|
// reaches for when naming a sending identity or the dialing pool have to land
|
|
326
331
|
// on the group that actually owns `senders profiles` and `voice numbers`.
|
|
327
|
-
intentTerms: ["sequence", "campaign", "cadence", "enroll", "outreach", "linkedin message", "linkedin dm", "nurture", "follow up", "sender rotation", "stop on reply", "sender identity", "sending identity", "sender profile", "phone number", "dialing", "dialer"],
|
|
332
|
+
intentTerms: ["sequence", "campaign", "cadence", "enroll", "outreach", "linkedin message", "linkedin dm", "nurture", "follow up", "sender rotation", "stop on reply", "sender identity", "sending identity", "sender profile", "phone number", "dialing", "dialer", "call", "cold call"],
|
|
328
333
|
},
|
|
329
334
|
{
|
|
330
335
|
id: "publishing",
|
|
@@ -526,6 +531,7 @@ export function inferCapabilityRoute(query) {
|
|
|
526
531
|
card,
|
|
527
532
|
recommendedTools: recommendations.tools,
|
|
528
533
|
recommendedCommands: recommendations.commands,
|
|
534
|
+
...(recommendations.exactCommand ? { exactCommand: recommendations.exactCommand } : {}),
|
|
529
535
|
};
|
|
530
536
|
}
|
|
531
537
|
export function inferPrimitiveRoute(query) {
|
|
@@ -549,6 +555,7 @@ export function serializeCapabilityRoute(route) {
|
|
|
549
555
|
endpoint_sections: route.card.endpointSections,
|
|
550
556
|
recommended_commands: route.recommendedCommands,
|
|
551
557
|
recommended_tools: route.recommendedTools,
|
|
558
|
+
...(route.exactCommand ? { exact_command: route.exactCommand } : {}),
|
|
552
559
|
hydrate: {
|
|
553
560
|
cli: "oxygen commands get <exact-command> --json",
|
|
554
561
|
mcp: "oxygen_capabilities_schema",
|
|
@@ -568,6 +575,11 @@ function isLinkedInProfileWatcherIntent(query) {
|
|
|
568
575
|
function explicitCapabilityIntent(query) {
|
|
569
576
|
if (/\b(infographic|graphic designer|render html|carousel pages|visual design|gtm flow image)\b/.test(query))
|
|
570
577
|
return ROUTE_BY_ID.get("visual-rendering") ?? null;
|
|
578
|
+
// A whole-motion ask ("set up inbound-led outbound for my posts") is a
|
|
579
|
+
// Recipe kit + playbook, not the connected-LinkedIn signals card that the
|
|
580
|
+
// "my LinkedIn … engagers" rule below would otherwise claim. Resolve it first.
|
|
581
|
+
if (isInboundLedOutboundIntent(query))
|
|
582
|
+
return ROUTE_BY_PRIMITIVE.get("recipes") ?? null;
|
|
571
583
|
// A unified sender profile is an owned Sequence identity, even when the ask
|
|
572
584
|
// names every attached channel (LinkedIn + WhatsApp + email). Resolve this
|
|
573
585
|
// before public LinkedIn research, whose generic "profile" wording would
|
|
@@ -666,9 +678,23 @@ function explicitCapabilityIntent(query) {
|
|
|
666
678
|
|| /\b(day\s*\d+|days?\s+\d|multi[ -]step|rotate senders?|sender rotation|stop on reply|until (?:they )?reply)\b/.test(query)) {
|
|
667
679
|
return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
|
|
668
680
|
}
|
|
681
|
+
// Existing reply handling is Messages even without a channel name. Keep this
|
|
682
|
+
// after the explicit cadence/enrollment rules so reply-stop programs retain
|
|
683
|
+
// Sequences. The onboarding baseline phrased this as "follow up on replies".
|
|
684
|
+
if (/\b(follow up|triage|answer|handle)\b.{0,40}\breplies\b/.test(query)) {
|
|
685
|
+
return ROUTE_BY_PRIMITIVE.get("messages") ?? null;
|
|
686
|
+
}
|
|
669
687
|
if (/\b(reply|respond)\b.{0,30}\b(conversation|thread|email|linkedin|whatsapp|dm)\b/.test(query)) {
|
|
670
688
|
return ROUTE_BY_PRIMITIVE.get("messages") ?? null;
|
|
671
689
|
}
|
|
690
|
+
// Resolving websites / domains / LinkedIn pages for company NAMES the user
|
|
691
|
+
// already holds is the companies-search `url_recovery` intent, not a fresh
|
|
692
|
+
// sourcing search and not an enrichment column. A 2026-09-11 blind user
|
|
693
|
+
// asking "get company website URL from company name" got NO route here
|
|
694
|
+
// (null), never discovered `companies search plan`, and priced the whole
|
|
695
|
+
// company_enrich preset instead.
|
|
696
|
+
if (isCompanyUrlRecoveryIntent(query))
|
|
697
|
+
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
672
698
|
if (/\b(find emails?|find phones?|find companies|find people|source (?:leads?|a list|companies|people)|build (?:me )?a list|tam|prospect)\b/.test(query)) {
|
|
673
699
|
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
674
700
|
}
|
|
@@ -677,16 +703,43 @@ function explicitCapabilityIntent(query) {
|
|
|
677
703
|
}
|
|
678
704
|
return null;
|
|
679
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Does `term` occur in `query` starting on a word boundary?
|
|
708
|
+
*
|
|
709
|
+
* A plain `query.includes(term)` is what sent "enrich these le**ad**s" and
|
|
710
|
+
* "build me a spre**ad**sheet" to the competitor-ad-intelligence card, and
|
|
711
|
+
* "our i**deal** customer" to CRM Records: a short term matches inside any
|
|
712
|
+
* longer word that happens to contain its letters, and nothing else scores, so
|
|
713
|
+
* the wrong card wins outright rather than narrowly.
|
|
714
|
+
*
|
|
715
|
+
* Only the START of the match is anchored, deliberately. Requiring a boundary at
|
|
716
|
+
* BOTH ends looks stricter and is wrong here, because the cards do not list
|
|
717
|
+
* their own plurals consistently — `sequence`, `workflow`, `table`, `agent` and
|
|
718
|
+
* `campaign` have no plural term beside them — so whole-word matching silently
|
|
719
|
+
* stops routing "sequences", "workflows" and "tables", which are the phrasings
|
|
720
|
+
* people actually type. Anchoring the head keeps every plural and inflection
|
|
721
|
+
* working while refusing the mid-word accidents, and it is the same
|
|
722
|
+
* word-boundary treatment v1.915.0 applied to the Tables guard this scorer sits
|
|
723
|
+
* behind.
|
|
724
|
+
*/
|
|
725
|
+
function matchesIntentTerm(query, term) {
|
|
726
|
+
for (let at = query.indexOf(term); at >= 0; at = query.indexOf(term, at + 1)) {
|
|
727
|
+
const preceding = at === 0 ? "" : query[at - 1] ?? "";
|
|
728
|
+
if (!/[a-z0-9]/.test(preceding))
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
680
733
|
function highestScoringRoute(query) {
|
|
681
734
|
let best = null;
|
|
682
735
|
for (const card of OXYGEN_CAPABILITY_ROUTES) {
|
|
683
736
|
let score = 0;
|
|
684
737
|
for (const term of card.intentTerms) {
|
|
685
|
-
if (query
|
|
738
|
+
if (matchesIntentTerm(query, term))
|
|
686
739
|
score += term.includes(" ") ? 6 : 2;
|
|
687
740
|
}
|
|
688
741
|
for (const term of card.negativeTerms ?? []) {
|
|
689
|
-
if (query
|
|
742
|
+
if (matchesIntentTerm(query, term))
|
|
690
743
|
score -= term.includes(" ") ? 8 : 3;
|
|
691
744
|
}
|
|
692
745
|
if (score > (best?.score ?? 0))
|
|
@@ -694,7 +747,16 @@ function highestScoringRoute(query) {
|
|
|
694
747
|
}
|
|
695
748
|
return best?.card ?? null;
|
|
696
749
|
}
|
|
750
|
+
/** The exact `companies search plan` invocation for the url_recovery intent. */
|
|
751
|
+
export const COMPANY_URL_RECOVERY_COMMAND = 'oxygen companies search plan --prompt "Find the websites for these companies: <company names>" --source-intent url_recovery';
|
|
697
752
|
function recommendationsFor(card, query) {
|
|
753
|
+
if (card.id === "sourcing-and-provider-tools" && isCompanyUrlRecoveryIntent(query)) {
|
|
754
|
+
return {
|
|
755
|
+
tools: ["oxygen_companies_search_plan", "oxygen_companies_search_run"],
|
|
756
|
+
commands: ["companies search plan", "companies search run"],
|
|
757
|
+
exactCommand: COMPANY_URL_RECOVERY_COMMAND,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
698
760
|
if (card.id === "connected-linkedin" && isMutualLinkedInConnectionsIntent(query)) {
|
|
699
761
|
return {
|
|
700
762
|
tools: ["oxygen_tools_get", "oxygen_tools_run_live", "oxygen_senders_list"],
|
|
@@ -781,6 +843,16 @@ function recommendationsFor(card, query) {
|
|
|
781
843
|
commands: ["tables watcher preview", "tables watcher create", "tables watcher get", "tables watcher update", "tables watcher pause", "tables watcher resume"],
|
|
782
844
|
};
|
|
783
845
|
}
|
|
846
|
+
if (isCompanyResearchQuestionIntent(query)) {
|
|
847
|
+
// A fact the template catalog already answers by key: list the keys first
|
|
848
|
+
// (free), add the column by key (free), then price and run it. Ahead of
|
|
849
|
+
// the bare "run" noun below, because "which cloud provider does it run
|
|
850
|
+
// on" is a question about the company, not about a run.
|
|
851
|
+
return {
|
|
852
|
+
tools: ["oxygen_columns_catalog", "oxygen_columns_add", "oxygen_columns_run", "oxygen_tables_describe"],
|
|
853
|
+
commands: ["columns catalog", "columns add", "columns run", "tables describe"],
|
|
854
|
+
};
|
|
855
|
+
}
|
|
784
856
|
if (/\b(table )?(action )?runs?\b/.test(query)) {
|
|
785
857
|
return {
|
|
786
858
|
tools: ["oxygen_table_runs_get", "oxygen_table_runs_items", "oxygen_table_runs_wait", "oxygen_table_runs_retry_failed"],
|
|
@@ -948,6 +1020,35 @@ function isEmailVerificationIntent(query) {
|
|
|
948
1020
|
const addressThenGrade = /\b(e ?mails?|addresses)\b.{0,40}\b(verif(?:y|ied|ication)|validat(?:e|ed|ion)|valid|invalid|deliverable|(?:will|would|might) bounce|bounce risk|risky|safe to (?:send|email)|catch ?all|accept ?all)\b/;
|
|
949
1021
|
return gradeThenAddress.test(query) || addressThenGrade.test(query);
|
|
950
1022
|
}
|
|
1023
|
+
// "get company website URL from company name", "find the websites for this
|
|
1024
|
+
// list of company names", "recover domains for company names" — a URL-ish
|
|
1025
|
+
// noun, a company noun, a recovery verb, and the hint that the input is NAMES
|
|
1026
|
+
// or a LIST (or that URLs are MISSING). Every leg is required so a sourcing
|
|
1027
|
+
// ask ("find companies with a website in Germany") keeps its owner. LinkedIn
|
|
1028
|
+
// page recovery is deliberately left to the LinkedIn rules above it, which
|
|
1029
|
+
// already resolve company pages through the public-research catalog.
|
|
1030
|
+
/**
|
|
1031
|
+
* A company fact the template catalog answers by key — founders, parent company,
|
|
1032
|
+
* subsidiaries, cloud provider, NAICS, demos — and the "ready-made / template
|
|
1033
|
+
* catalog / research question" phrasing itself. Kept to nouns no other card
|
|
1034
|
+
* owns: "funding" alone belongs to Signals and "industry" alone to company
|
|
1035
|
+
* search, so both stay off this list.
|
|
1036
|
+
*/
|
|
1037
|
+
function isCompanyResearchQuestionIntent(query) {
|
|
1038
|
+
return /\b(founders?|founded by|parent compan(?:y|ies)|subsidiar(?:y|ies)|cloud provider|naics|column templates?|template catalog|ready[- ]made|research question|company research|offers? demos?|gives? demos?)\b/.test(query);
|
|
1039
|
+
}
|
|
1040
|
+
function isCompanyUrlRecoveryIntent(query) {
|
|
1041
|
+
const urlNoun = /\b(websites?|web ?sites?|website urls?|domains?|urls?|homepages?)\b/;
|
|
1042
|
+
const companyNoun = /\b(compan(?:y|ies)|accounts?|organi[sz]ations?|brands?|vendors?)\b/;
|
|
1043
|
+
const recover = /\b(recover|resolve|look ?up|lookup|missing|get|find|fill(?: in)?|add)\b/;
|
|
1044
|
+
const fromNames = /\b(names?|list|missing|these|those)\b/;
|
|
1045
|
+
// An email / phone / contact ask that merely MENTIONS domains is enrichment,
|
|
1046
|
+
// and a column / table ask is a Tables ask — both keep their owners.
|
|
1047
|
+
const otherOwner = /\b(e-?mails?|phones?|mobiles?|numbers?|contacts?|people|persons?|columns?|tables?|enrich\w*|verif\w*)\b/;
|
|
1048
|
+
return urlNoun.test(query) && companyNoun.test(query) && recover.test(query) && fromNames.test(query)
|
|
1049
|
+
&& !/\b(scrape|engagers?|comments?|reactions?|posts?)\b/.test(query)
|
|
1050
|
+
&& !otherOwner.test(query);
|
|
1051
|
+
}
|
|
951
1052
|
function isInboxAvatarIntent(query) {
|
|
952
1053
|
return /\b(avatar|profile (?:picture|photo)|headshot|hosted (?:picture|image)|mailbox (?:picture|photo))\b/.test(query);
|
|
953
1054
|
}
|
|
@@ -974,6 +1075,14 @@ function isMailboxDeleteIntent(query) {
|
|
|
974
1075
|
const scopedDetach = /\b(remove|disconnect)\b.{0,48}\b(mailbox(?:es)?|inbox(?:es)?|sender accounts?|email accounts?)\b.{0,24}\b(?:from|in|on)\b.{0,24}\b(sequence|campaign|cadence|sender profile)\b/.test(query);
|
|
975
1076
|
return mailboxScope && removal && !scopedDetach;
|
|
976
1077
|
}
|
|
1078
|
+
// The composed inbound-led outbound motion: engagers → ICP gate → capped
|
|
1079
|
+
// sequence → CRM. Hyphens are already normalised to spaces by the caller.
|
|
1080
|
+
function isInboundLedOutboundIntent(query) {
|
|
1081
|
+
return /\binbound led outbound\b/.test(query)
|
|
1082
|
+
|| /\bengagers?\b.{0,40}\b(into|to) (a |my )?(sequence|pipeline)\b/.test(query)
|
|
1083
|
+
|| /\bwarm outbound\b.{0,40}\b(posts?|engagement)\b/.test(query)
|
|
1084
|
+
|| /\b(whole|end to end|complete) (gtm )?motion\b/.test(query);
|
|
1085
|
+
}
|
|
977
1086
|
function isNetNewLinkedInInitiation(query) {
|
|
978
1087
|
const mentionsLinkedIn = /\blinkedin\b|\bdm\b/.test(query);
|
|
979
1088
|
const startsConversation = /\b(send|message|dm|contact|reach out|initiate|start)\b/.test(query)
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* may contain a dot or a space. Such a field carries `referenceable: false` and
|
|
27
27
|
* is display-only.
|
|
28
28
|
*/
|
|
29
|
-
import { buildResearchCellSchema, deriveResearchOutputContract, usesServerManagedResearchSchema, } from "./research-output-contract.js";
|
|
29
|
+
import { buildResearchCellSchema, isResearchEnvelopeSchema, researchEvidenceRequired, deriveResearchOutputContract, usesServerManagedResearchSchema, } from "./research-output-contract.js";
|
|
30
30
|
import { isTemplateSafePath, parseJsonPath, readJsonPath } from "./json-path.js";
|
|
31
31
|
const MAX_FIELDS = 40;
|
|
32
32
|
const MAX_SCHEMA_DEPTH = 5;
|
|
@@ -263,13 +263,21 @@ function researchContract(definition) {
|
|
|
263
263
|
const prompt = readString(definition.prompt) ?? "";
|
|
264
264
|
const webSearch = isRecord(definition.webSearch) ? definition.webSearch : null;
|
|
265
265
|
const contract = deriveResearchOutputContract(prompt, {
|
|
266
|
-
evidenceRequired: webSearch
|
|
266
|
+
evidenceRequired: researchEvidenceRequired(webSearch),
|
|
267
267
|
});
|
|
268
268
|
// The STORED shape, not the model-facing one: a research cell carries
|
|
269
|
-
// `sources`, never the `citations` the model answered with.
|
|
269
|
+
// `sources`, never the `citations` the model answered with. A column whose
|
|
270
|
+
// own schema is still the research envelope (a catalog template's typed
|
|
271
|
+
// answer) gets the same rewrite, so `answer.plan_count` is offered and
|
|
272
|
+
// `citations` is not; a hand-authored schema that replaced the envelope is
|
|
273
|
+
// used verbatim, because the cell then holds exactly what it declares.
|
|
270
274
|
const schema = serverManaged
|
|
271
275
|
? buildResearchCellSchema(contract)
|
|
272
|
-
:
|
|
276
|
+
: isRecord(definition.outputSchema)
|
|
277
|
+
? (isResearchEnvelopeSchema(definition.outputSchema)
|
|
278
|
+
? buildResearchCellSchema(null, definition.outputSchema)
|
|
279
|
+
: definition.outputSchema)
|
|
280
|
+
: null;
|
|
273
281
|
if (!schema)
|
|
274
282
|
return EMPTY_CONTRACT;
|
|
275
283
|
return {
|
|
@@ -21,6 +21,9 @@ const WORKER_STEP_TIMEOUT_MESSAGE = /\bWorker step '[^']+' exceeded \d+ms deadli
|
|
|
21
21
|
export function customerFacingCopilotError(input) {
|
|
22
22
|
const code = input.code ?? null;
|
|
23
23
|
const message = input.message ?? null;
|
|
24
|
+
if (code === "agent_response_incomplete") {
|
|
25
|
+
return { code, message: "I stopped before completing the requested work. Completed actions are saved; ask me to continue." };
|
|
26
|
+
}
|
|
24
27
|
const isTimeout = code === "worker_step_timeout" ||
|
|
25
28
|
code === COPILOT_TURN_TIMEOUT_CODE ||
|
|
26
29
|
code === COPILOT_TURN_DEADLINE_EXCEEDED_CODE ||
|
|
@@ -8,6 +8,24 @@ export type CopilotJourney = {
|
|
|
8
8
|
seedMessage: string;
|
|
9
9
|
};
|
|
10
10
|
export declare const LINKEDIN_NETWORK_COPILOT_JOURNEY_SLUG = "inbound-led-outbound";
|
|
11
|
-
export declare const COPILOT_JOURNEYS: readonly
|
|
11
|
+
export declare const COPILOT_JOURNEYS: readonly [{
|
|
12
|
+
readonly slug: "inbound-led-outbound";
|
|
13
|
+
readonly title: "Inbound-led outbound";
|
|
14
|
+
readonly description: "Turn the people who engage with your LinkedIn posts into a capped, evidence-gated warm sequence with replies routed to your CRM.";
|
|
15
|
+
readonly recipeSlugs: readonly ["inbound-led-outbound", "linkedin-network-first-motion", "founder-posting-system", "reply-to-meeting"];
|
|
16
|
+
readonly seedMessage: "I chose the inbound-led-outbound recipe. Help me set up inbound-led outbound for my LinkedIn posts: watch who engages with them every day, assess each person against my ICP, hand me the strong fits, put the possible fits into a draft LinkedIn sequence on a schedule, move replies into my CRM, and write what we learn back into my knowledge wiki. Inspect the workspace first, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.";
|
|
17
|
+
}, {
|
|
18
|
+
readonly slug: "email-infra-setup";
|
|
19
|
+
readonly title: "Outbound infrastructure";
|
|
20
|
+
readonly description: "Domains, inboxes, warmup, sender posture — reviewed and approved before anything is bought or sent.";
|
|
21
|
+
readonly recipeSlugs: readonly ["email-infra-setup"];
|
|
22
|
+
readonly seedMessage: "Inspect my current sending setup (domains, inboxes, warmup, senders, caps, deliverability) and propose what's missing for a safe cold-email motion. Show quotes and costs — do not buy or change anything without my approval.";
|
|
23
|
+
}, {
|
|
24
|
+
readonly slug: "tam-sourcing";
|
|
25
|
+
readonly title: "Map your market";
|
|
26
|
+
readonly description: "Source and segment your addressable market from your ICP, starting with a bounded sample.";
|
|
27
|
+
readonly recipeSlugs: readonly ["icp-to-account-map", "outbound-pilot-50"];
|
|
28
|
+
readonly seedMessage: "Help me map my total addressable market from my ICP. Ground it in my company profile and website, source a bounded sample first, and show me coverage and credit estimates before expanding.";
|
|
29
|
+
}];
|
|
12
30
|
export type CopilotJourneySlug = (typeof COPILOT_JOURNEYS)[number]["slug"];
|
|
13
31
|
export declare function getCopilotJourney(slug: string): CopilotJourney | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const COPILOT_JOURNEYS_GENERATED: readonly [{
|
|
2
|
+
readonly slug: "inbound-led-outbound";
|
|
3
|
+
readonly title: "Inbound-led outbound";
|
|
4
|
+
readonly description: "Turn the people who engage with your LinkedIn posts into a capped, evidence-gated warm sequence with replies routed to your CRM.";
|
|
5
|
+
readonly recipeSlugs: readonly ["inbound-led-outbound", "linkedin-network-first-motion", "founder-posting-system", "reply-to-meeting"];
|
|
6
|
+
readonly seedMessage: "I chose the inbound-led-outbound recipe. Help me set up inbound-led outbound for my LinkedIn posts: watch who engages with them every day, assess each person against my ICP, hand me the strong fits, put the possible fits into a draft LinkedIn sequence on a schedule, move replies into my CRM, and write what we learn back into my knowledge wiki. Inspect the workspace first, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.";
|
|
7
|
+
}, {
|
|
8
|
+
readonly slug: "email-infra-setup";
|
|
9
|
+
readonly title: "Outbound infrastructure";
|
|
10
|
+
readonly description: "Domains, inboxes, warmup, sender posture — reviewed and approved before anything is bought or sent.";
|
|
11
|
+
readonly recipeSlugs: readonly ["email-infra-setup"];
|
|
12
|
+
readonly seedMessage: "Inspect my current sending setup (domains, inboxes, warmup, senders, caps, deliverability) and propose what's missing for a safe cold-email motion. Show quotes and costs — do not buy or change anything without my approval.";
|
|
13
|
+
}, {
|
|
14
|
+
readonly slug: "tam-sourcing";
|
|
15
|
+
readonly title: "Map your market";
|
|
16
|
+
readonly description: "Source and segment your addressable market from your ICP, starting with a bounded sample.";
|
|
17
|
+
readonly recipeSlugs: readonly ["icp-to-account-map", "outbound-pilot-50"];
|
|
18
|
+
readonly seedMessage: "Help me map my total addressable market from my ICP. Ground it in my company profile and website, source a bounded sample first, and show me coverage and credit estimates before expanding.";
|
|
19
|
+
}];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// GENERATED by apps/web/scripts/generate-copilot-journeys.ts from the recipe
|
|
2
|
+
// catalog (`journey:` blocks in apps/web/src/agent-skills/oxygen-recipes/recipes).
|
|
3
|
+
// Do not edit by hand — run `npm run skills:metadata -w @oxygen/web`.
|
|
4
|
+
export const COPILOT_JOURNEYS_GENERATED = [
|
|
5
|
+
{
|
|
6
|
+
slug: "inbound-led-outbound",
|
|
7
|
+
title: "Inbound-led outbound",
|
|
8
|
+
description: "Turn the people who engage with your LinkedIn posts into a capped, evidence-gated warm sequence with replies routed to your CRM.",
|
|
9
|
+
recipeSlugs: ["inbound-led-outbound", "linkedin-network-first-motion", "founder-posting-system", "reply-to-meeting"],
|
|
10
|
+
seedMessage: "I chose the inbound-led-outbound recipe. Help me set up inbound-led outbound for my LinkedIn posts: watch who engages with them every day, assess each person against my ICP, hand me the strong fits, put the possible fits into a draft LinkedIn sequence on a schedule, move replies into my CRM, and write what we learn back into my knowledge wiki. Inspect the workspace first, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
slug: "email-infra-setup",
|
|
14
|
+
title: "Outbound infrastructure",
|
|
15
|
+
description: "Domains, inboxes, warmup, sender posture — reviewed and approved before anything is bought or sent.",
|
|
16
|
+
recipeSlugs: ["email-infra-setup"],
|
|
17
|
+
seedMessage: "Inspect my current sending setup (domains, inboxes, warmup, senders, caps, deliverability) and propose what's missing for a safe cold-email motion. Show quotes and costs — do not buy or change anything without my approval.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
slug: "tam-sourcing",
|
|
21
|
+
title: "Map your market",
|
|
22
|
+
description: "Source and segment your addressable market from your ICP, starting with a bounded sample.",
|
|
23
|
+
recipeSlugs: ["icp-to-account-map", "outbound-pilot-50"],
|
|
24
|
+
seedMessage: "Help me map my total addressable market from my ICP. Ground it in my company profile and website, source a bounded sample first, and show me coverage and credit estimates before expanding.",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
@@ -1,47 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
// the runtime (system-prompt grounding on the named recipes) and the web journey
|
|
3
|
-
// cards (seeding a session's first message) — the data is pure and client-safe.
|
|
4
|
-
// Each journey is a thin descriptor over EXISTING advisory catalog Recipes:
|
|
5
|
-
// journeys add no runtime behavior, and every guardrail (never enroll/send/buy/
|
|
6
|
-
// change DNS without approval) rides those recipes' own approval gates.
|
|
1
|
+
import { COPILOT_JOURNEYS_GENERATED } from "./copilot-journeys.generated.js";
|
|
7
2
|
// Stable slug for sessions deliberately launched from the matching Recipe.
|
|
8
3
|
// It is never a default: first-touch surfaces stay outcome-neutral.
|
|
9
4
|
export const LINKEDIN_NETWORK_COPILOT_JOURNEY_SLUG = "inbound-led-outbound";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// are a thin, anonymity-capped sample by comparison. Posting is second because
|
|
18
|
-
// it CREATES the engager stream this play captures, and replies close the loop.
|
|
19
|
-
recipeSlugs: [
|
|
20
|
-
"linkedin-network-first-motion",
|
|
21
|
-
"founder-posting-system",
|
|
22
|
-
"reply-to-meeting",
|
|
23
|
-
],
|
|
24
|
-
// Deliberately free of steps, commands, table names, and caps: encoding
|
|
25
|
-
// mechanics here would fork a second, unversioned copy of the play that no
|
|
26
|
-
// catalog gate can lint (ADR 0013 §11 — a journey is a thin pointer at
|
|
27
|
-
// advisory recipes, never a hardcoded kit).
|
|
28
|
-
seedMessage: "I chose the linkedin-network-first-motion recipe. Help me turn the LinkedIn network I already have — my connections and the people who engage with my posts — into a small, safe, approval-gated outreach motion. Inspect the relevant workspace primitives, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
slug: "tam-sourcing",
|
|
32
|
-
title: "Map your market",
|
|
33
|
-
description: "Source and segment your addressable market from your ICP, starting with a bounded sample.",
|
|
34
|
-
recipeSlugs: ["icp-to-account-map", "outbound-pilot-50"],
|
|
35
|
-
seedMessage: "Help me map my total addressable market from my ICP. Ground it in my company profile and website, source a bounded sample first, and show me coverage and credit estimates before expanding.",
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
slug: "email-infra-setup",
|
|
39
|
-
title: "Outbound infrastructure",
|
|
40
|
-
description: "Domains, inboxes, warmup, sender posture — reviewed and approved before anything is bought or sent.",
|
|
41
|
-
recipeSlugs: ["email-infra-setup"],
|
|
42
|
-
seedMessage: "Inspect my current sending setup (domains, inboxes, warmup, senders, caps, deliverability) and propose what's missing for a safe cold-email motion. Show quotes and costs — do not buy or change anything without my approval.",
|
|
43
|
-
},
|
|
44
|
-
];
|
|
5
|
+
// Derived from the recipe catalog (ADR 0025): every recipe carrying a
|
|
6
|
+
// `journey:` block leads one journey. The list is generated into
|
|
7
|
+
// ./copilot-journeys.generated.ts by apps/web/scripts/generate-copilot-journeys.ts
|
|
8
|
+
// (run through `npm run skills:metadata -w @oxygen/web`) and gated against the
|
|
9
|
+
// catalog by apps/web/src/lib/recipes/copilot-journeys.test.ts, because this
|
|
10
|
+
// package cannot read the markdown the catalog is served from.
|
|
11
|
+
export const COPILOT_JOURNEYS = COPILOT_JOURNEYS_GENERATED;
|
|
45
12
|
export function getCopilotJourney(slug) {
|
|
46
13
|
return COPILOT_JOURNEYS.find((journey) => journey.slug === slug) ?? null;
|
|
47
14
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type CopilotPlaybookSlug = "tam-sourcing" | "linkedin-content-strategy" | "inbound-led-outbound" | "signal-based-outbound";
|
|
2
|
+
export type CopilotPlaybook = {
|
|
3
|
+
/**
|
|
4
|
+
* The skill slug the Copilot loads through its `skills` tool, and the
|
|
5
|
+
* `name:` in the playbook file's frontmatter. The two are gated equal.
|
|
6
|
+
*/
|
|
7
|
+
slug: CopilotPlaybookSlug;
|
|
8
|
+
/** Customer-facing name, exactly as it is spoken in the composer prompt and shown on the Home chip. */
|
|
9
|
+
title: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const COPILOT_PLAYBOOKS: readonly CopilotPlaybook[];
|
|
12
|
+
/**
|
|
13
|
+
* The sentence a Home card writes into the composer. It names the skill the way
|
|
14
|
+
* a person would say it, and the Copilot's prompt maps that title back to the
|
|
15
|
+
* slug — so the user reads and sends exactly what the card promised.
|
|
16
|
+
*/
|
|
17
|
+
export declare function copilotPlaybookPrompt(playbook: Pick<CopilotPlaybook, "title">): string;
|
|
18
|
+
export declare function getCopilotPlaybook(slug: string): CopilotPlaybook | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The four end-to-end GTM playbooks the Workspace Copilot can run, as pure,
|
|
2
|
+
// client-safe data. One canonical copy is read by three consumers: the Home
|
|
3
|
+
// cards (which pre-write the prompt below into the composer), the Copilot's
|
|
4
|
+
// system prompt (which maps the spoken title back to the skill slug the model
|
|
5
|
+
// loads through its `skills` tool), and the apps/web gate that checks every slug
|
|
6
|
+
// here has a served playbook file with a matching frontmatter `name`.
|
|
7
|
+
//
|
|
8
|
+
// A playbook is the operator contract for one whole motion — stages, owners,
|
|
9
|
+
// exact commands, caps, approval gates, stop conditions, learning loop — served
|
|
10
|
+
// as a product skill (`apps/web/src/agent-skills/oxygen-playbooks/playbooks/`).
|
|
11
|
+
// Loading one is never permission to spend or send: every gate inside it still
|
|
12
|
+
// fires. Founder decision 2026-09-16: Home offers exactly these four, above the
|
|
13
|
+
// composer as compact glass chips (title only), and a chip writes the sentence
|
|
14
|
+
// into the box rather than starting a session, so there stays exactly one door.
|
|
15
|
+
export const COPILOT_PLAYBOOKS = [
|
|
16
|
+
{
|
|
17
|
+
slug: "tam-sourcing",
|
|
18
|
+
title: "TAM sourcing",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
slug: "linkedin-content-strategy",
|
|
22
|
+
title: "LinkedIn content strategy",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
slug: "inbound-led-outbound",
|
|
26
|
+
title: "Inbound-led outbound",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
slug: "signal-based-outbound",
|
|
30
|
+
title: "Signal-based outbound",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* The sentence a Home card writes into the composer. It names the skill the way
|
|
35
|
+
* a person would say it, and the Copilot's prompt maps that title back to the
|
|
36
|
+
* slug — so the user reads and sends exactly what the card promised.
|
|
37
|
+
*/
|
|
38
|
+
export function copilotPlaybookPrompt(playbook) {
|
|
39
|
+
return `Use the ${playbook.title} skill to get started.`;
|
|
40
|
+
}
|
|
41
|
+
export function getCopilotPlaybook(slug) {
|
|
42
|
+
return COPILOT_PLAYBOOKS.find((playbook) => playbook.slug === slug) ?? null;
|
|
43
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type CopilotSkillSnapshot = {
|
|
2
|
+
/** What the model passes to the `skills` tool; equals the bundle's frontmatter `name`. */
|
|
3
|
+
slug: string;
|
|
4
|
+
/** Catalog title, shown when the frontmatter carries no description. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Served skill files bundled into `content`, as `skill/path`, for provenance. */
|
|
7
|
+
sources: readonly string[];
|
|
8
|
+
/** The exact bundle the tool serves, frontmatter included. */
|
|
9
|
+
content: string;
|
|
10
|
+
/** Lowercase hex sha256 of `content`; the runtime re-hashes every served body against it. */
|
|
11
|
+
sha256: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const COPILOT_SKILL_SNAPSHOTS: readonly CopilotSkillSnapshot[];
|
|
14
|
+
export declare const COPILOT_SKILL_SLUGS: readonly string[];
|
|
15
|
+
export declare function getCopilotSkillSnapshot(slug: string): CopilotSkillSnapshot | null;
|