@oxygen-agent/cli 1.948.1 → 1.982.3
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 +13 -5
- package/dist/help.js +1 -0
- package/dist/index.js +1171 -240
- 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.js +122 -8
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +99 -15
- 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/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 +4 -0
- package/node_modules/@oxygen/shared/dist/index.js +4 -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 +17 -38
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +14 -39
- 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/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/sequences.d.ts +126 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +280 -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 +15 -0
- package/package.json +2 -1
|
@@ -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",
|
|
@@ -215,13 +215,13 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
215
215
|
gatewayTools: ["oxygen_tables_create", "oxygen_columns_add", "oxygen_enrich_column_preview", "oxygen_tables_link_bulk", "oxygen_callables_manage"],
|
|
216
216
|
gatewayCommands: ["tables create", "columns add", "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
|
-
intentTerms: ["table", "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"],
|
|
224
|
+
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"],
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
id: "messages",
|
|
@@ -324,7 +324,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
324
324
|
// to workspace-access — "identity" matched the AUTH sense. The nouns a user
|
|
325
325
|
// reaches for when naming a sending identity or the dialing pool have to land
|
|
326
326
|
// 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"],
|
|
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", "call", "cold call"],
|
|
328
328
|
},
|
|
329
329
|
{
|
|
330
330
|
id: "publishing",
|
|
@@ -526,6 +526,7 @@ export function inferCapabilityRoute(query) {
|
|
|
526
526
|
card,
|
|
527
527
|
recommendedTools: recommendations.tools,
|
|
528
528
|
recommendedCommands: recommendations.commands,
|
|
529
|
+
...(recommendations.exactCommand ? { exactCommand: recommendations.exactCommand } : {}),
|
|
529
530
|
};
|
|
530
531
|
}
|
|
531
532
|
export function inferPrimitiveRoute(query) {
|
|
@@ -549,6 +550,7 @@ export function serializeCapabilityRoute(route) {
|
|
|
549
550
|
endpoint_sections: route.card.endpointSections,
|
|
550
551
|
recommended_commands: route.recommendedCommands,
|
|
551
552
|
recommended_tools: route.recommendedTools,
|
|
553
|
+
...(route.exactCommand ? { exact_command: route.exactCommand } : {}),
|
|
552
554
|
hydrate: {
|
|
553
555
|
cli: "oxygen commands get <exact-command> --json",
|
|
554
556
|
mcp: "oxygen_capabilities_schema",
|
|
@@ -568,6 +570,11 @@ function isLinkedInProfileWatcherIntent(query) {
|
|
|
568
570
|
function explicitCapabilityIntent(query) {
|
|
569
571
|
if (/\b(infographic|graphic designer|render html|carousel pages|visual design|gtm flow image)\b/.test(query))
|
|
570
572
|
return ROUTE_BY_ID.get("visual-rendering") ?? null;
|
|
573
|
+
// A whole-motion ask ("set up inbound-led outbound for my posts") is a
|
|
574
|
+
// Recipe kit + playbook, not the connected-LinkedIn signals card that the
|
|
575
|
+
// "my LinkedIn … engagers" rule below would otherwise claim. Resolve it first.
|
|
576
|
+
if (isInboundLedOutboundIntent(query))
|
|
577
|
+
return ROUTE_BY_PRIMITIVE.get("recipes") ?? null;
|
|
571
578
|
// A unified sender profile is an owned Sequence identity, even when the ask
|
|
572
579
|
// names every attached channel (LinkedIn + WhatsApp + email). Resolve this
|
|
573
580
|
// before public LinkedIn research, whose generic "profile" wording would
|
|
@@ -666,9 +673,23 @@ function explicitCapabilityIntent(query) {
|
|
|
666
673
|
|| /\b(day\s*\d+|days?\s+\d|multi[ -]step|rotate senders?|sender rotation|stop on reply|until (?:they )?reply)\b/.test(query)) {
|
|
667
674
|
return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
|
|
668
675
|
}
|
|
676
|
+
// Existing reply handling is Messages even without a channel name. Keep this
|
|
677
|
+
// after the explicit cadence/enrollment rules so reply-stop programs retain
|
|
678
|
+
// Sequences. The onboarding baseline phrased this as "follow up on replies".
|
|
679
|
+
if (/\b(follow up|triage|answer|handle)\b.{0,40}\breplies\b/.test(query)) {
|
|
680
|
+
return ROUTE_BY_PRIMITIVE.get("messages") ?? null;
|
|
681
|
+
}
|
|
669
682
|
if (/\b(reply|respond)\b.{0,30}\b(conversation|thread|email|linkedin|whatsapp|dm)\b/.test(query)) {
|
|
670
683
|
return ROUTE_BY_PRIMITIVE.get("messages") ?? null;
|
|
671
684
|
}
|
|
685
|
+
// Resolving websites / domains / LinkedIn pages for company NAMES the user
|
|
686
|
+
// already holds is the companies-search `url_recovery` intent, not a fresh
|
|
687
|
+
// sourcing search and not an enrichment column. A 2026-09-11 blind user
|
|
688
|
+
// asking "get company website URL from company name" got NO route here
|
|
689
|
+
// (null), never discovered `companies search plan`, and priced the whole
|
|
690
|
+
// company_enrich preset instead.
|
|
691
|
+
if (isCompanyUrlRecoveryIntent(query))
|
|
692
|
+
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
672
693
|
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
694
|
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
674
695
|
}
|
|
@@ -677,16 +698,43 @@ function explicitCapabilityIntent(query) {
|
|
|
677
698
|
}
|
|
678
699
|
return null;
|
|
679
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Does `term` occur in `query` starting on a word boundary?
|
|
703
|
+
*
|
|
704
|
+
* A plain `query.includes(term)` is what sent "enrich these le**ad**s" and
|
|
705
|
+
* "build me a spre**ad**sheet" to the competitor-ad-intelligence card, and
|
|
706
|
+
* "our i**deal** customer" to CRM Records: a short term matches inside any
|
|
707
|
+
* longer word that happens to contain its letters, and nothing else scores, so
|
|
708
|
+
* the wrong card wins outright rather than narrowly.
|
|
709
|
+
*
|
|
710
|
+
* Only the START of the match is anchored, deliberately. Requiring a boundary at
|
|
711
|
+
* BOTH ends looks stricter and is wrong here, because the cards do not list
|
|
712
|
+
* their own plurals consistently — `sequence`, `workflow`, `table`, `agent` and
|
|
713
|
+
* `campaign` have no plural term beside them — so whole-word matching silently
|
|
714
|
+
* stops routing "sequences", "workflows" and "tables", which are the phrasings
|
|
715
|
+
* people actually type. Anchoring the head keeps every plural and inflection
|
|
716
|
+
* working while refusing the mid-word accidents, and it is the same
|
|
717
|
+
* word-boundary treatment v1.915.0 applied to the Tables guard this scorer sits
|
|
718
|
+
* behind.
|
|
719
|
+
*/
|
|
720
|
+
function matchesIntentTerm(query, term) {
|
|
721
|
+
for (let at = query.indexOf(term); at >= 0; at = query.indexOf(term, at + 1)) {
|
|
722
|
+
const preceding = at === 0 ? "" : query[at - 1] ?? "";
|
|
723
|
+
if (!/[a-z0-9]/.test(preceding))
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
680
728
|
function highestScoringRoute(query) {
|
|
681
729
|
let best = null;
|
|
682
730
|
for (const card of OXYGEN_CAPABILITY_ROUTES) {
|
|
683
731
|
let score = 0;
|
|
684
732
|
for (const term of card.intentTerms) {
|
|
685
|
-
if (query
|
|
733
|
+
if (matchesIntentTerm(query, term))
|
|
686
734
|
score += term.includes(" ") ? 6 : 2;
|
|
687
735
|
}
|
|
688
736
|
for (const term of card.negativeTerms ?? []) {
|
|
689
|
-
if (query
|
|
737
|
+
if (matchesIntentTerm(query, term))
|
|
690
738
|
score -= term.includes(" ") ? 8 : 3;
|
|
691
739
|
}
|
|
692
740
|
if (score > (best?.score ?? 0))
|
|
@@ -694,7 +742,16 @@ function highestScoringRoute(query) {
|
|
|
694
742
|
}
|
|
695
743
|
return best?.card ?? null;
|
|
696
744
|
}
|
|
745
|
+
/** The exact `companies search plan` invocation for the url_recovery intent. */
|
|
746
|
+
export const COMPANY_URL_RECOVERY_COMMAND = 'oxygen companies search plan --prompt "Find the websites for these companies: <company names>" --source-intent url_recovery';
|
|
697
747
|
function recommendationsFor(card, query) {
|
|
748
|
+
if (card.id === "sourcing-and-provider-tools" && isCompanyUrlRecoveryIntent(query)) {
|
|
749
|
+
return {
|
|
750
|
+
tools: ["oxygen_companies_search_plan", "oxygen_companies_search_run"],
|
|
751
|
+
commands: ["companies search plan", "companies search run"],
|
|
752
|
+
exactCommand: COMPANY_URL_RECOVERY_COMMAND,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
698
755
|
if (card.id === "connected-linkedin" && isMutualLinkedInConnectionsIntent(query)) {
|
|
699
756
|
return {
|
|
700
757
|
tools: ["oxygen_tools_get", "oxygen_tools_run_live", "oxygen_senders_list"],
|
|
@@ -948,6 +1005,25 @@ function isEmailVerificationIntent(query) {
|
|
|
948
1005
|
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
1006
|
return gradeThenAddress.test(query) || addressThenGrade.test(query);
|
|
950
1007
|
}
|
|
1008
|
+
// "get company website URL from company name", "find the websites for this
|
|
1009
|
+
// list of company names", "recover domains for company names" — a URL-ish
|
|
1010
|
+
// noun, a company noun, a recovery verb, and the hint that the input is NAMES
|
|
1011
|
+
// or a LIST (or that URLs are MISSING). Every leg is required so a sourcing
|
|
1012
|
+
// ask ("find companies with a website in Germany") keeps its owner. LinkedIn
|
|
1013
|
+
// page recovery is deliberately left to the LinkedIn rules above it, which
|
|
1014
|
+
// already resolve company pages through the public-research catalog.
|
|
1015
|
+
function isCompanyUrlRecoveryIntent(query) {
|
|
1016
|
+
const urlNoun = /\b(websites?|web ?sites?|website urls?|domains?|urls?|homepages?)\b/;
|
|
1017
|
+
const companyNoun = /\b(compan(?:y|ies)|accounts?|organi[sz]ations?|brands?|vendors?)\b/;
|
|
1018
|
+
const recover = /\b(recover|resolve|look ?up|lookup|missing|get|find|fill(?: in)?|add)\b/;
|
|
1019
|
+
const fromNames = /\b(names?|list|missing|these|those)\b/;
|
|
1020
|
+
// An email / phone / contact ask that merely MENTIONS domains is enrichment,
|
|
1021
|
+
// and a column / table ask is a Tables ask — both keep their owners.
|
|
1022
|
+
const otherOwner = /\b(e-?mails?|phones?|mobiles?|numbers?|contacts?|people|persons?|columns?|tables?|enrich\w*|verif\w*)\b/;
|
|
1023
|
+
return urlNoun.test(query) && companyNoun.test(query) && recover.test(query) && fromNames.test(query)
|
|
1024
|
+
&& !/\b(scrape|engagers?|comments?|reactions?|posts?)\b/.test(query)
|
|
1025
|
+
&& !otherOwner.test(query);
|
|
1026
|
+
}
|
|
951
1027
|
function isInboxAvatarIntent(query) {
|
|
952
1028
|
return /\b(avatar|profile (?:picture|photo)|headshot|hosted (?:picture|image)|mailbox (?:picture|photo))\b/.test(query);
|
|
953
1029
|
}
|
|
@@ -974,6 +1050,14 @@ function isMailboxDeleteIntent(query) {
|
|
|
974
1050
|
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
1051
|
return mailboxScope && removal && !scopedDetach;
|
|
976
1052
|
}
|
|
1053
|
+
// The composed inbound-led outbound motion: engagers → ICP gate → capped
|
|
1054
|
+
// sequence → CRM. Hyphens are already normalised to spaces by the caller.
|
|
1055
|
+
function isInboundLedOutboundIntent(query) {
|
|
1056
|
+
return /\binbound led outbound\b/.test(query)
|
|
1057
|
+
|| /\bengagers?\b.{0,40}\b(into|to) (a |my )?(sequence|pipeline)\b/.test(query)
|
|
1058
|
+
|| /\bwarm outbound\b.{0,40}\b(posts?|engagement)\b/.test(query)
|
|
1059
|
+
|| /\b(whole|end to end|complete) (gtm )?motion\b/.test(query);
|
|
1060
|
+
}
|
|
977
1061
|
function isNetNewLinkedInInitiation(query) {
|
|
978
1062
|
const mentionsLinkedIn = /\blinkedin\b|\bdm\b/.test(query);
|
|
979
1063
|
const startsConversation = /\b(send|message|dm|contact|reach out|initiate|start)\b/.test(query)
|
|
@@ -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,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a hosted inbox-avatar URL may be minted, and how.
|
|
3
|
+
*
|
|
4
|
+
* An avatar URL is handed to an external inbox vendor that fetches it server-side,
|
|
5
|
+
* later, possibly more than once — and Oxygen stores nothing about that hand-off
|
|
6
|
+
* afterwards, so a bad URL cannot be found again or reissued. A URL minted on a
|
|
7
|
+
* preview deployment would be baked into a vendor's mailbox record and die when
|
|
8
|
+
* that deployment does; one minted on localhost never resolves at all. So the
|
|
9
|
+
* origin is an allowlist, not "whatever host served this request", and the SAME
|
|
10
|
+
* allowlist binds every minter: the web request path (which prefers the inbound
|
|
11
|
+
* host) and the worker's LinkedIn-photo backfill (which only has the configured
|
|
12
|
+
* app URL). Two allowlists would drift, and the drift would ship as a mailbox
|
|
13
|
+
* photo that resolves from one deployment and 404s from the other.
|
|
14
|
+
*/
|
|
15
|
+
export declare const INBOX_AVATAR_ORIGIN_HOSTS: ReadonlySet<string>;
|
|
16
|
+
/**
|
|
17
|
+
* The first candidate that is an https URL on an allowed host, normalized to
|
|
18
|
+
* `protocol//host`, or null when none qualifies. Returning null rather than a
|
|
19
|
+
* best-effort URL is the point: on localhost the only honest answer is "not from
|
|
20
|
+
* here", and an `http://localhost` URL would sail through this function only to
|
|
21
|
+
* be rejected by an https-only check one step later and much harder to read.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveInboxAvatarOrigin(candidates: readonly (string | null | undefined)[]): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* The public, permanent URL for a stored inbox avatar on an already-resolved
|
|
26
|
+
* origin — or null when there is no origin, or the key is not an inbox avatar.
|
|
27
|
+
*/
|
|
28
|
+
export declare function inboxAvatarPublicUrlForOrigin(origin: string | null, storageKey: string): string | null;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a hosted inbox-avatar URL may be minted, and how.
|
|
3
|
+
*
|
|
4
|
+
* An avatar URL is handed to an external inbox vendor that fetches it server-side,
|
|
5
|
+
* later, possibly more than once — and Oxygen stores nothing about that hand-off
|
|
6
|
+
* afterwards, so a bad URL cannot be found again or reissued. A URL minted on a
|
|
7
|
+
* preview deployment would be baked into a vendor's mailbox record and die when
|
|
8
|
+
* that deployment does; one minted on localhost never resolves at all. So the
|
|
9
|
+
* origin is an allowlist, not "whatever host served this request", and the SAME
|
|
10
|
+
* allowlist binds every minter: the web request path (which prefers the inbound
|
|
11
|
+
* host) and the worker's LinkedIn-photo backfill (which only has the configured
|
|
12
|
+
* app URL). Two allowlists would drift, and the drift would ship as a mailbox
|
|
13
|
+
* photo that resolves from one deployment and 404s from the other.
|
|
14
|
+
*/
|
|
15
|
+
export const INBOX_AVATAR_ORIGIN_HOSTS = new Set([
|
|
16
|
+
"oxygen-agent.com",
|
|
17
|
+
"www.oxygen-agent.com",
|
|
18
|
+
"dev.oxygen-agent.com",
|
|
19
|
+
]);
|
|
20
|
+
const INBOX_AVATAR_KEY_PREFIX = "inbox-avatars/";
|
|
21
|
+
/**
|
|
22
|
+
* The first candidate that is an https URL on an allowed host, normalized to
|
|
23
|
+
* `protocol//host`, or null when none qualifies. Returning null rather than a
|
|
24
|
+
* best-effort URL is the point: on localhost the only honest answer is "not from
|
|
25
|
+
* here", and an `http://localhost` URL would sail through this function only to
|
|
26
|
+
* be rejected by an https-only check one step later and much harder to read.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveInboxAvatarOrigin(candidates) {
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
if (!candidate)
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
const url = new URL(candidate);
|
|
34
|
+
if (url.protocol === "https:" && INBOX_AVATAR_ORIGIN_HOSTS.has(url.host)) {
|
|
35
|
+
return `${url.protocol}//${url.host}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Not a URL at all — try the next candidate.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The public, permanent URL for a stored inbox avatar on an already-resolved
|
|
46
|
+
* origin — or null when there is no origin, or the key is not an inbox avatar.
|
|
47
|
+
*/
|
|
48
|
+
export function inboxAvatarPublicUrlForOrigin(origin, storageKey) {
|
|
49
|
+
if (!origin || !storageKey.startsWith(INBOX_AVATAR_KEY_PREFIX))
|
|
50
|
+
return null;
|
|
51
|
+
const path = storageKey
|
|
52
|
+
.slice(INBOX_AVATAR_KEY_PREFIX.length)
|
|
53
|
+
.split("/")
|
|
54
|
+
.map((segment) => encodeURIComponent(segment))
|
|
55
|
+
.join("/");
|
|
56
|
+
return `${origin}/api/inbox-avatars/${path}`;
|
|
57
|
+
}
|
|
@@ -47,6 +47,7 @@ export * from "./knowledge-constants.js";
|
|
|
47
47
|
export * from "./knowledge-bootstrap.js";
|
|
48
48
|
export * from "./knowledge-links.js";
|
|
49
49
|
export * from "./knowledge-markdown.js";
|
|
50
|
+
export * from "./knowledge-vault-markdown.js";
|
|
50
51
|
export * from "./knowledge-seed-content.js";
|
|
51
52
|
export * from "./langfuse.js";
|
|
52
53
|
export * from "./llm-usage.js";
|
|
@@ -119,3 +120,6 @@ export declare function isVersionGreater(a: string, b: string): boolean;
|
|
|
119
120
|
/** True when `a` is a strictly lesser semantic version than `b`. */
|
|
120
121
|
export declare function isVersionLess(a: string, b: string): boolean;
|
|
121
122
|
export * from "./ugc.js";
|
|
123
|
+
export * from "./ugc-amplification-identity.js";
|
|
124
|
+
export * from "./knowledge-repository.js";
|
|
125
|
+
export * from "./knowledge-bases.js";
|
|
@@ -47,6 +47,7 @@ export * from "./knowledge-constants.js";
|
|
|
47
47
|
export * from "./knowledge-bootstrap.js";
|
|
48
48
|
export * from "./knowledge-links.js";
|
|
49
49
|
export * from "./knowledge-markdown.js";
|
|
50
|
+
export * from "./knowledge-vault-markdown.js";
|
|
50
51
|
export * from "./knowledge-seed-content.js";
|
|
51
52
|
export * from "./langfuse.js";
|
|
52
53
|
export * from "./llm-usage.js";
|
|
@@ -155,3 +156,6 @@ export function isVersionLess(a, b) {
|
|
|
155
156
|
return compareSemver(a, b) < 0;
|
|
156
157
|
}
|
|
157
158
|
export * from "./ugc.js";
|
|
159
|
+
export * from "./ugc-amplification-identity.js";
|
|
160
|
+
export * from "./knowledge-repository.js";
|
|
161
|
+
export * from "./knowledge-bases.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type KnowledgeBaseViewType = "table" | "cards" | "kanban";
|
|
2
|
+
export type KnowledgeBaseFilter = string | {
|
|
3
|
+
and?: KnowledgeBaseFilter[];
|
|
4
|
+
or?: KnowledgeBaseFilter[];
|
|
5
|
+
not?: KnowledgeBaseFilter[];
|
|
6
|
+
};
|
|
7
|
+
export type KnowledgeBaseIssue = {
|
|
8
|
+
code: "invalid_base" | "unsupported_filter" | "unsupported_view" | "invalid_view";
|
|
9
|
+
message: string;
|
|
10
|
+
expression?: string;
|
|
11
|
+
view?: string;
|
|
12
|
+
};
|
|
13
|
+
export type KnowledgeBaseProperty = {
|
|
14
|
+
displayName?: string;
|
|
15
|
+
raw: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
export type KnowledgeBaseView = {
|
|
18
|
+
type: string;
|
|
19
|
+
name: string;
|
|
20
|
+
groupBy?: {
|
|
21
|
+
property: string;
|
|
22
|
+
direction: "ASC" | "DESC";
|
|
23
|
+
};
|
|
24
|
+
order: string[];
|
|
25
|
+
boardColumns: string[];
|
|
26
|
+
cardTitleProperty?: string;
|
|
27
|
+
sort: Array<{
|
|
28
|
+
property: string;
|
|
29
|
+
direction: "ASC" | "DESC";
|
|
30
|
+
}>;
|
|
31
|
+
filters?: KnowledgeBaseFilter;
|
|
32
|
+
columnColors: Record<string, string>;
|
|
33
|
+
wipLimits: Record<string, number>;
|
|
34
|
+
raw: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
export type KnowledgeBaseDefinition = {
|
|
37
|
+
path: string;
|
|
38
|
+
name: string;
|
|
39
|
+
filters?: KnowledgeBaseFilter;
|
|
40
|
+
properties: Record<string, KnowledgeBaseProperty>;
|
|
41
|
+
views: KnowledgeBaseView[];
|
|
42
|
+
raw: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
export type KnowledgeBaseFile = {
|
|
45
|
+
id: string;
|
|
46
|
+
path: string;
|
|
47
|
+
body?: string;
|
|
48
|
+
properties: Record<string, unknown>;
|
|
49
|
+
revision: number;
|
|
50
|
+
};
|
|
51
|
+
export type KnowledgeBaseParseResult = {
|
|
52
|
+
definition: KnowledgeBaseDefinition | null;
|
|
53
|
+
issues: KnowledgeBaseIssue[];
|
|
54
|
+
};
|
|
55
|
+
export type KnowledgeBaseEvaluation = {
|
|
56
|
+
files: KnowledgeBaseFile[];
|
|
57
|
+
issues: KnowledgeBaseIssue[];
|
|
58
|
+
};
|
|
59
|
+
export type KnowledgeBaseGroup = {
|
|
60
|
+
key: string;
|
|
61
|
+
title: string;
|
|
62
|
+
files: KnowledgeBaseFile[];
|
|
63
|
+
};
|
|
64
|
+
export declare function parseKnowledgeBase(path: string, source: string): KnowledgeBaseParseResult;
|
|
65
|
+
export declare function knowledgeBasePropertyKey(property: string): string;
|
|
66
|
+
export declare function knowledgeBasePropertyValue(file: KnowledgeBaseFile, property: string): unknown;
|
|
67
|
+
export declare function knowledgeBasePropertyText(file: KnowledgeBaseFile, property: string): string;
|
|
68
|
+
export declare function evaluateKnowledgeBaseView(definition: KnowledgeBaseDefinition, view: KnowledgeBaseView, files: readonly KnowledgeBaseFile[]): KnowledgeBaseEvaluation;
|
|
69
|
+
export declare function knowledgeBaseGroups(view: KnowledgeBaseView, files: readonly KnowledgeBaseFile[], configuredColumns?: readonly string[]): KnowledgeBaseGroup[];
|
|
70
|
+
export declare function knowledgeBaseDisplayName(definition: KnowledgeBaseDefinition, property: string): string;
|
|
71
|
+
export declare function knowledgeBaseCardTitle(file: KnowledgeBaseFile, view: KnowledgeBaseView): string;
|
|
72
|
+
export declare function parseBaseBoardColumnOrder(value: unknown): Record<string, string[]>;
|
|
73
|
+
/** Resolve Base Board's `<folder>::<view>::<property>` lane order, with view-name shorthand for API callers. */
|
|
74
|
+
export declare function resolveBaseBoardColumnOrder(definition: KnowledgeBaseDefinition, view: KnowledgeBaseView, configured: Readonly<Record<string, readonly string[]>>): string[];
|