@burdenoff/microfe-vibecontrols 2026.1005.1 → 2026.1007.1
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.
|
@@ -1,43 +1,48 @@
|
|
|
1
1
|
import { getStoredWorkspaceId as e, graphqlFetch as t } from "@burdenoff/fe-libs/shared/graphql";
|
|
2
|
-
|
|
2
|
+
import { normaliseRecordName as n } from "@burdenoff/fe-libs/shared/assistant/uiActions/recordNames";
|
|
3
|
+
var r = "\n query AssistantFindVibes($pagination: PaginationInput) {\n vibes(pagination: $pagination) {\n items { id name workspaceId archivedAt deletedAt }\n }\n }\n", i = "\n query AssistantFindAgents($pagination: PaginationInput) {\n vibecontrolsAgents(pagination: $pagination) {\n id name hostname workspaceId deletedAt\n }\n }\n", a = {
|
|
3
4
|
graphqlFetch: t,
|
|
4
5
|
getWorkspaceId: e
|
|
5
|
-
},
|
|
6
|
-
function
|
|
7
|
-
let n =
|
|
6
|
+
}, o = (e) => typeof e == "string" && e.trim().length > 0 ? e.trim() : null, s = n;
|
|
7
|
+
function c(e, t) {
|
|
8
|
+
let n = s(e);
|
|
8
9
|
if (!n) return !1;
|
|
9
|
-
let r =
|
|
10
|
+
let r = s(t).split(" ");
|
|
10
11
|
return n.split(" ").every((e) => r.some((t) => t.startsWith(e)));
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
let
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
var l = (e, t) => {
|
|
14
|
+
let n = s(e);
|
|
15
|
+
return n.length > 0 && n === s(t);
|
|
16
|
+
};
|
|
17
|
+
async function u(e, t, n, r, i, a, o) {
|
|
18
|
+
let s = e.getWorkspaceId();
|
|
19
|
+
if (!s) return [];
|
|
20
|
+
let u = await e.graphqlFetch({
|
|
16
21
|
gateway: "workspace",
|
|
17
22
|
query: t,
|
|
18
23
|
operationName: n,
|
|
19
24
|
variables: { pagination: { limit: 200 } },
|
|
20
25
|
workspaceToken: !0,
|
|
21
|
-
workspaceId:
|
|
26
|
+
workspaceId: s,
|
|
22
27
|
organizationId: !0,
|
|
23
28
|
signal: a,
|
|
24
29
|
suppressGlobalErrorEvent: !0
|
|
25
30
|
});
|
|
26
|
-
if (
|
|
27
|
-
let
|
|
28
|
-
if (!Array.isArray(
|
|
29
|
-
let
|
|
30
|
-
for (let e of
|
|
31
|
-
if (!e || typeof e.id != "string" || e.id.length === 0 || typeof e.workspaceId == "string" && e.workspaceId !==
|
|
31
|
+
if (u.errors?.length || !u.data) return [];
|
|
32
|
+
let d = r(u.data);
|
|
33
|
+
if (!Array.isArray(d)) return [];
|
|
34
|
+
let f = [], p = [];
|
|
35
|
+
for (let e of d) {
|
|
36
|
+
if (!e || typeof e.id != "string" || e.id.length === 0 || typeof e.workspaceId == "string" && e.workspaceId !== s || e.deletedAt || e.archivedAt) continue;
|
|
32
37
|
let t = o(e);
|
|
33
|
-
t === null || !
|
|
38
|
+
t === null || !c(i, t) || (l(i, t) ? f : p).push({
|
|
34
39
|
params: { id: e.id },
|
|
35
40
|
name: t
|
|
36
41
|
});
|
|
37
42
|
}
|
|
38
|
-
return
|
|
43
|
+
return [...f, ...p];
|
|
39
44
|
}
|
|
40
|
-
function
|
|
45
|
+
function d(e = a) {
|
|
41
46
|
return [{
|
|
42
47
|
id: "vibecontrols-vibe",
|
|
43
48
|
nouns: ["vibe", "vibes"],
|
|
@@ -46,7 +51,7 @@ function l(e = i) {
|
|
|
46
51
|
fallback: "vibe"
|
|
47
52
|
},
|
|
48
53
|
entityRouteId: "vibecontrols-vibe",
|
|
49
|
-
find: (t,
|
|
54
|
+
find: (t, n) => u(e, r, "AssistantFindVibes", (e) => e.vibes?.items, t, n, (e) => o(e.name))
|
|
50
55
|
}, {
|
|
51
56
|
id: "vibecontrols-agent",
|
|
52
57
|
nouns: ["agent", "agents"],
|
|
@@ -55,11 +60,11 @@ function l(e = i) {
|
|
|
55
60
|
fallback: "agent"
|
|
56
61
|
},
|
|
57
62
|
entityRouteId: "vibecontrols-agent",
|
|
58
|
-
find: (t, n) =>
|
|
63
|
+
find: (t, n) => u(e, i, "AssistantFindAgents", (e) => e.vibecontrolsAgents, t, n, (e) => o(e.name) ?? o(e.hostname))
|
|
59
64
|
}];
|
|
60
65
|
}
|
|
61
|
-
var
|
|
66
|
+
var f = d();
|
|
62
67
|
//#endregion
|
|
63
|
-
export {
|
|
68
|
+
export { f as VIBECONTROLS_RECORD_ADAPTERS };
|
|
64
69
|
|
|
65
70
|
//# sourceMappingURL=recordAdapters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recordAdapters.js","names":[],"sources":["../../../src/components/assistant/recordAdapters.ts"],"sourcesContent":["/**\n * VibeControls' named-record adapters for the shared assistant (BOFF-8060 N1b):\n * \"open the Staging Tunnel vibe\", \"open the build-box agent\".\n *\n * An adapter only LOOKS A RECORD UP BY NAME through the READ API the product's\n * own pages already use (`vibes`, `vibecontrolsAgents`), as the signed-in person:\n * the workspace token is theirs and the gateway's `@rbac` filters the list, so a\n * record they may not read never comes back. The adapter produces route params,\n * never a path: fe-libs fills the entity route declared in `assistantProduct.ts`\n * (`ENTITY_ROUTES`) and re-validates it, and only for a person who can see the\n * route's sidebar entry.\n *\n * ★ Neither list query has a server-side `search`, so the adapter reads the same\n * first page the product's own pickers read (`LIST_LIMIT`) and matches the name\n * HERE. It must filter itself: fe-libs caps what an adapter may return, and an\n * unfiltered page would let the wanted record fall off the end.\n *\n * ★ Raw operation strings (the product's other non-hook reads do the same):\n * `recordAdapters.test.ts` validates both against the committed workspace schema.\n *\n * ★ Nothing here writes, opens a form, or touches the DOM.\n */\nimport
|
|
1
|
+
{"version":3,"file":"recordAdapters.js","names":[],"sources":["../../../src/components/assistant/recordAdapters.ts"],"sourcesContent":["/**\n * VibeControls' named-record adapters for the shared assistant (BOFF-8060 N1b):\n * \"open the Staging Tunnel vibe\", \"open the build-box agent\".\n *\n * An adapter only LOOKS A RECORD UP BY NAME through the READ API the product's\n * own pages already use (`vibes`, `vibecontrolsAgents`), as the signed-in person:\n * the workspace token is theirs and the gateway's `@rbac` filters the list, so a\n * record they may not read never comes back. The adapter produces route params,\n * never a path: fe-libs fills the entity route declared in `assistantProduct.ts`\n * (`ENTITY_ROUTES`) and re-validates it, and only for a person who can see the\n * route's sidebar entry.\n *\n * ★ Neither list query has a server-side `search`, so the adapter reads the same\n * first page the product's own pickers read (`LIST_LIMIT`) and matches the name\n * HERE. It must filter itself: fe-libs caps what an adapter may return, and an\n * unfiltered page would let the wanted record fall off the end.\n *\n * ★ Raw operation strings (the product's other non-hook reads do the same):\n * `recordAdapters.test.ts` validates both against the committed workspace schema.\n *\n * ★ BOFF-8680: names are compared in the planner's OWN form, fe-libs\n * `normaliseRecordName`, imported and never copied (see `normalise` below).\n *\n * ★ Nothing here writes, opens a form, or touches the DOM.\n */\nimport {\n normaliseRecordName,\n type AssistantRecordAdapter,\n type AssistantRecordMatch,\n} from '@burdenoff/fe-libs/shared/assistant/uiActions/recordNames';\nimport { getStoredWorkspaceId, graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\n/** The page size the product's own vibe picker reads (EntityVibeLinkControl). */\nexport const LIST_LIMIT = 200;\n\nexport const LIST_VIBES_FOR_ASSISTANT = `\n query AssistantFindVibes($pagination: PaginationInput) {\n vibes(pagination: $pagination) {\n items { id name workspaceId archivedAt deletedAt }\n }\n }\n`;\n\nexport const LIST_AGENTS_FOR_ASSISTANT = `\n query AssistantFindAgents($pagination: PaginationInput) {\n vibecontrolsAgents(pagination: $pagination) {\n id name hostname workspaceId deletedAt\n }\n }\n`;\n\n/** The two things the adapters need from the app; injectable so tests run without a network. */\nexport interface RecordAdapterDeps {\n graphqlFetch: typeof graphqlFetch;\n getWorkspaceId: () => string | null;\n}\nconst LIVE_DEPS: RecordAdapterDeps = { graphqlFetch, getWorkspaceId: getStoredWorkspaceId };\n\ninterface RawNode {\n id?: unknown;\n name?: unknown;\n hostname?: unknown;\n workspaceId?: unknown;\n archivedAt?: unknown;\n deletedAt?: unknown;\n}\n\nconst text = (value: unknown): string | null =>\n typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;\n\n/**\n * ★ BOFF-8680: the shared planner's own normalisation (`canonical`), applied to\n * the request and to every record name, so this prefilter can never drop a row\n * the planner would keep. fe-libs canonicalises the sentence BEFORE `find()`, so\n * \"Sales & Marketing\" arrives as \"sales and marketing\"; the local copy this\n * replaces turned the record's \"&\" into a gap, and its \"and\" then started no word\n * of the name. It also stripped combining marks, cutting a Hindi or Tamil name at\n * every vowel sign. Idempotent, so an already-normalised request is unchanged.\n */\nconst normalise = normaliseRecordName;\n\n/** Every word said is the start of a word in the name (the same rule fe-libs plans with). */\nexport function nameFits(said: string, name: string): boolean {\n const wanted = normalise(said);\n if (!wanted) return false;\n const words = normalise(name).split(' ');\n return wanted.split(' ').every((word) => words.some((candidate) => candidate.startsWith(word)));\n}\n\n/** The name said IS the record's name, in the planner's form (it then outranks every partial match). */\nconst isExact = (said: string, name: string): boolean => {\n const wanted = normalise(said);\n return wanted.length > 0 && wanted === normalise(name);\n};\n\nasync function findByName(\n deps: RecordAdapterDeps,\n query: string,\n operationName: string,\n read: (data: Record<string, unknown>) => unknown,\n name: string,\n signal: AbortSignal,\n label: (node: RawNode) => string | null\n): Promise<readonly AssistantRecordMatch[]> {\n const workspaceId = deps.getWorkspaceId();\n if (!workspaceId) return [];\n const result = await deps.graphqlFetch<Record<string, unknown>>({\n gateway: 'workspace',\n query,\n operationName,\n variables: { pagination: { limit: LIST_LIMIT } },\n // The person's own workspace token, minted for THIS workspace.\n workspaceToken: true,\n workspaceId,\n organizationId: true,\n signal,\n suppressGlobalErrorEvent: true,\n });\n if (result.errors?.length || !result.data) return [];\n const rows = read(result.data);\n if (!Array.isArray(rows)) return [];\n const exact: AssistantRecordMatch[] = [];\n const partial: AssistantRecordMatch[] = [];\n for (const row of rows as Array<RawNode | null>) {\n if (!row || typeof row.id !== 'string' || row.id.length === 0) continue;\n // A list answered for another workspace (a switch mid-request) is not used.\n if (typeof row.workspaceId === 'string' && row.workspaceId !== workspaceId) continue;\n if (row.deletedAt || row.archivedAt) continue;\n const shown = label(row);\n if (shown === null || !nameFits(name, shown)) continue;\n (isExact(name, shown) ? exact : partial).push({ params: { id: row.id }, name: shown });\n }\n // Exact names first, each group in the server's order: fe-libs keeps only an\n // adapter's first RECORD_MATCHES_MAX matches, and many partial matches must not\n // push the one record that was named exactly off the end.\n return [...exact, ...partial];\n}\n\nexport function createVibeControlsRecordAdapters(\n deps: RecordAdapterDeps = LIVE_DEPS\n): readonly AssistantRecordAdapter[] {\n const vibes: AssistantRecordAdapter = {\n id: 'vibecontrols-vibe',\n nouns: ['vibe', 'vibes'],\n noun: { key: 'entity.vibe', fallback: 'vibe' },\n entityRouteId: 'vibecontrols-vibe',\n find: (name, signal) =>\n findByName(\n deps,\n LIST_VIBES_FOR_ASSISTANT,\n 'AssistantFindVibes',\n (data) => (data.vibes as { items?: unknown } | null | undefined)?.items,\n name,\n signal,\n (node) => text(node.name)\n ),\n };\n const agents: AssistantRecordAdapter = {\n id: 'vibecontrols-agent',\n nouns: ['agent', 'agents'],\n noun: { key: 'entity.agent', fallback: 'agent' },\n entityRouteId: 'vibecontrols-agent',\n find: (name, signal) =>\n findByName(\n deps,\n LIST_AGENTS_FOR_ASSISTANT,\n 'AssistantFindAgents',\n (data) => data.vibecontrolsAgents,\n name,\n signal,\n // An agent with no display name is called by its host.\n (node) => text(node.name) ?? text(node.hostname)\n ),\n };\n return [vibes, agents];\n}\n\nexport const VIBECONTROLS_RECORD_ADAPTERS: readonly AssistantRecordAdapter[] =\n createVibeControlsRecordAdapters();\n"],"mappings":";;AAmCA,IAAa,IAA2B,8KAQ3B,IAA4B,gLAanC,IAA+B;CAAE;CAAc,gBAAgB;CAAsB,EAWrF,KAAQ,MACZ,OAAO,KAAU,YAAY,EAAM,MAAM,CAAC,SAAS,IAAI,EAAM,MAAM,GAAG,MAWlE,IAAY;AAGlB,SAAgB,EAAS,GAAc,GAAuB;CAC5D,IAAM,IAAS,EAAU,EAAK;AAC9B,KAAI,CAAC,EAAQ,QAAO;CACpB,IAAM,IAAQ,EAAU,EAAK,CAAC,MAAM,IAAI;AACxC,QAAO,EAAO,MAAM,IAAI,CAAC,OAAO,MAAS,EAAM,MAAM,MAAc,EAAU,WAAW,EAAK,CAAC,CAAC;;AAIjG,IAAM,KAAW,GAAc,MAA0B;CACvD,IAAM,IAAS,EAAU,EAAK;AAC9B,QAAO,EAAO,SAAS,KAAK,MAAW,EAAU,EAAK;;AAGxD,eAAe,EACb,GACA,GACA,GACA,GACA,GACA,GACA,GAC0C;CAC1C,IAAM,IAAc,EAAK,gBAAgB;AACzC,KAAI,CAAC,EAAa,QAAO,EAAE;CAC3B,IAAM,IAAS,MAAM,EAAK,aAAsC;EAC9D,SAAS;EACT;EACA;EACA,WAAW,EAAE,YAAY,EAAE,OAAA,KAAmB,EAAE;EAEhD,gBAAgB;EAChB;EACA,gBAAgB;EAChB;EACA,0BAA0B;EAC3B,CAAC;AACF,KAAI,EAAO,QAAQ,UAAU,CAAC,EAAO,KAAM,QAAO,EAAE;CACpD,IAAM,IAAO,EAAK,EAAO,KAAK;AAC9B,KAAI,CAAC,MAAM,QAAQ,EAAK,CAAE,QAAO,EAAE;CACnC,IAAM,IAAgC,EAAE,EAClC,IAAkC,EAAE;AAC1C,MAAK,IAAM,KAAO,GAA+B;AAI/C,MAHI,CAAC,KAAO,OAAO,EAAI,MAAO,YAAY,EAAI,GAAG,WAAW,KAExD,OAAO,EAAI,eAAgB,YAAY,EAAI,gBAAgB,KAC3D,EAAI,aAAa,EAAI,WAAY;EACrC,IAAM,IAAQ,EAAM,EAAI;AACpB,QAAU,QAAQ,CAAC,EAAS,GAAM,EAAM,KAC3C,EAAQ,GAAM,EAAM,GAAG,IAAQ,GAAS,KAAK;GAAE,QAAQ,EAAE,IAAI,EAAI,IAAI;GAAE,MAAM;GAAO,CAAC;;AAKxF,QAAO,CAAC,GAAG,GAAO,GAAG,EAAQ;;AAG/B,SAAgB,EACd,IAA0B,GACS;AAkCnC,QAAO,CAjC+B;EACpC,IAAI;EACJ,OAAO,CAAC,QAAQ,QAAQ;EACxB,MAAM;GAAE,KAAK;GAAe,UAAU;GAAQ;EAC9C,eAAe;EACf,OAAO,GAAM,MACX,EACE,GACA,GACA,uBACC,MAAU,EAAK,OAAkD,OAClE,GACA,IACC,MAAS,EAAK,EAAK,KAAK,CAC1B;EACJ,EACsC;EACrC,IAAI;EACJ,OAAO,CAAC,SAAS,SAAS;EAC1B,MAAM;GAAE,KAAK;GAAgB,UAAU;GAAS;EAChD,eAAe;EACf,OAAO,GAAM,MACX,EACE,GACA,GACA,wBACC,MAAS,EAAK,oBACf,GACA,IAEC,MAAS,EAAK,EAAK,KAAK,IAAI,EAAK,EAAK,SAAS,CACjD;EACJ,CACqB;;AAGxB,IAAa,IACX,GAAkC"}
|
|
@@ -12,7 +12,21 @@ var e = {
|
|
|
12
12
|
calendar: "Calendar",
|
|
13
13
|
ai: "AI",
|
|
14
14
|
settings: "Settings",
|
|
15
|
-
plugins: "Plugins"
|
|
15
|
+
plugins: "Plugins",
|
|
16
|
+
actions: "Actions",
|
|
17
|
+
"ai-builder": "AI Builder",
|
|
18
|
+
"ai-tools": "AI",
|
|
19
|
+
analytics: "Analytics",
|
|
20
|
+
audit: "Audit",
|
|
21
|
+
environments: "Environments",
|
|
22
|
+
"getting-started": "Getting Started",
|
|
23
|
+
health: "Health",
|
|
24
|
+
logs: "Logs",
|
|
25
|
+
targets: "Targets",
|
|
26
|
+
vibedeck: "Vibe Deck",
|
|
27
|
+
share: "Shared Session",
|
|
28
|
+
deck: "Vibe Deck",
|
|
29
|
+
webhooks: "Webhooks"
|
|
16
30
|
};
|
|
17
31
|
function t() {
|
|
18
32
|
let { currentPage: t } = n(), r = t.split("/").filter(Boolean), i = (r[0] === "vibecontrols" ? r[1] : r[0]) ?? "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering — captures the user's current browser context\n * to be sent alongside every assistant prompt for better AI awareness.\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * ★ A `type` alias, deliberately, not an `interface`.\n *\n * fe-libs' `PageContext` carries an index signature (`[key: string]: unknown`)\n * so products can add their own keys. TypeScript grants a type alias an\n * IMPLICIT index signature and an interface none, so declaring this as an\n * interface makes it un-assignable to the seam's `gatherPageContext` — with an\n * error that talks about index signatures rather than about the seam.\n * HealthyBowl's equivalent is a type alias for exactly this reason.\n */\nexport type PageContext = {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n};\n\n/**\n * Human-readable names for VibeControls' top-level routes.\n *\n * ★ This is the label a USER would use for the page, not the route segment. It\n * reaches the model as \"the user is on the Sessions page\", so `sessions` →\n * `Sessions` is fine but `ai` → `AI` matters: the fallback title-caser would\n * produce \"Ai\", and an assistant that calls it that sounds like it has never\n * seen the product.\n *\n * Unlisted segments fall through to the title-caser, so a new route degrades to\n * something reasonable rather than to nothing.\n */\nconst PAGE_LABELS: Record<string, string> = {\n home: 'Home',\n agents: 'Agents',\n sessions: 'Sessions',\n vibes: 'Vibes',\n notes: 'Notes',\n catalogs: 'Catalogs',\n components: 'Components',\n templates: 'Templates',\n tunnels: 'Tunnels',\n calendar: 'Calendar',\n ai: 'AI',\n settings: 'Settings',\n plugins: 'Plugins',\n};\n\n/**\n * What to CALL the page the user is on.\n *\n * Feeds the assistant's empty state (\"Ask about Sessions…\") and the shared\n * panel's live page label. Distinct from `gatherPageContext`, which produces\n * machine-readable context: this one is read by a human.\n */\nexport function describeCurrentPage(): string {\n const { currentPage } = gatherPageContext();\n // `currentPage` is up to three path segments; the product mount segment is\n // first when present, so skip it rather than labelling every page \"Vibecontrols\".\n const segments = currentPage.split('/').filter(Boolean);\n const first = (segments[0] === 'vibecontrols' ? segments[1] : segments[0]) ?? '';\n if (!first || first === 'home') return PAGE_LABELS.home;\n return (\n PAGE_LABELS[first] ??\n first\n .split('-')\n .map((word) => (word ? word.charAt(0).toUpperCase() + word.slice(1) : word))\n .join(' ')\n );\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveVibeControlsConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'vibecontrols',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.402.1',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveVibeControlsConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n /**\n * ★ `(?!new/?$)`: a create page is not an entity. `/vibecontrols/tunnels/new`\n * is the tunnel wizard, and without the lookahead it resolved to a tunnel whose\n * id is the literal string \"new\" — handing the agent a fabricated entity, which\n * is worse than none, because it answers confidently about something that does\n * not exist. `vibes/new` and `agents/new` redirect, but a turn sent mid-redirect\n * would read the same lie. A trailing slash is the same page.\n */\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/vibecontrols\\/agents\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_AGENT'],\n [/^\\/vibecontrols\\/sessions\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_SESSION'],\n [/^\\/vibecontrols\\/vibes\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_VIBE'],\n [/^\\/vibecontrols\\/notes\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_NOTE'],\n [/^\\/vibecontrols\\/catalogs\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_CATALOG'],\n [/^\\/vibecontrols\\/components\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_COMPONENT'],\n [/^\\/vibecontrols\\/templates\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_TEMPLATE'],\n [/^\\/vibecontrols\\/tunnels\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_TUNNEL'],\n [/^\\/vibecontrols\\/calendar\\/task\\/([^/]+)\\/?$/, 'VIBECONTROLS_TASK'],\n [/^\\/vibecontrols\\/ai\\/sessions\\/([^/]+)\\/?$/, 'VIBECONTROLS_AI_SESSION'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n if (/^\\/vibecontrols\\/vibes\\/[^/]+\\/docs\\//.test(pathname)) {\n const match = pathname.match(/^\\/vibecontrols\\/vibes\\/([^/]+)\\/docs\\//);\n return {\n entityType: 'VIBECONTROLS_DOCSSITE',\n entityId: match?.[1],\n };\n }\n\n if (/^\\/vibecontrols\\/catalogs\\/[^/]+\\/docs\\//.test(pathname)) {\n const match = pathname.match(/^\\/vibecontrols\\/catalogs\\/([^/]+)\\/docs\\//);\n return {\n entityType: 'VIBECONTROLS_DOCSPAGE',\n entityId: match?.[1],\n };\n }\n\n if (/^\\/vibecontrols\\/components\\/[^/]+\\/docs\\//.test(pathname)) {\n const match = pathname.match(/^\\/vibecontrols\\/components\\/([^/]+)\\/docs\\//);\n return {\n entityType: 'VIBECONTROLS_DOCSPAGE',\n entityId: match?.[1],\n };\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('selectedTargetId')\n ? { selectedTargetId: params.get('selectedTargetId') }\n : {}),\n ...(params.get('actionId') ? { actionId: params.get('actionId') } : {}),\n },\n };\n}\n"],"mappings":";AAkDA,IAAM,IAAsC;CAC1C,MAAM;CACN,QAAQ;CACR,UAAU;CACV,OAAO;CACP,OAAO;CACP,UAAU;CACV,YAAY;CACZ,WAAW;CACX,SAAS;CACT,UAAU;CACV,IAAI;CACJ,UAAU;CACV,SAAS;CACV;AASD,SAAgB,IAA8B;CAC5C,IAAM,EAAE,mBAAgB,GAAmB,EAGrC,IAAW,EAAY,MAAM,IAAI,CAAC,OAAO,QAAQ,EACjD,KAAS,EAAS,OAAO,iBAAiB,EAAS,KAAK,EAAS,OAAO;AAE9E,QADI,CAAC,KAAS,MAAU,SAAe,EAAY,OAEjD,EAAY,MACZ,EACG,MAAM,IAAI,CACV,KAAK,MAAU,KAAO,EAAK,OAAO,EAAE,CAAC,aAAa,GAAG,EAAK,MAAM,EAAE,CAAS,CAC3E,KAAK,IAAI;;AAIhB,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,OACjB;AAED,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAGH,SAAS,EAAU,GAAiB,GAA2C;AAC7E,QAAO,EAAS,MAAM,EAAQ;;AAGhC,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,EAAO;AAsB1C,MAAK,IAAM,CAAC,GAAS,MAb0B;EAC7C,CAAC,mDAAmD,qBAAqB;EACzE,CAAC,qDAAqD,uBAAuB;EAC7E,CAAC,kDAAkD,oBAAoB;EACvE,CAAC,kDAAkD,oBAAoB;EACvE,CAAC,qDAAqD,uBAAuB;EAC7E,CAAC,uDAAuD,yBAAyB;EACjF,CAAC,sDAAsD,wBAAwB;EAC/E,CAAC,oDAAoD,sBAAsB;EAC3E,CAAC,gDAAgD,oBAAoB;EACrE,CAAC,8CAA8C,0BAA0B;EAC1E,EAEkD;EACjD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,EACF,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EACR,cAAc,EAAQ,QACvB;GACF;;AAIL,KAAI,wCAAwC,KAAK,EAAS,CAExD,QAAO;EACL,YAAY;EACZ,UAHY,EAAS,MAAM,0CAA0C,GAGnD;EACnB;AAGH,KAAI,2CAA2C,KAAK,EAAS,CAE3D,QAAO;EACL,YAAY;EACZ,UAHY,EAAS,MAAM,6CAA6C,GAGtD;EACnB;AAGH,KAAI,6CAA6C,KAAK,EAAS,CAE7D,QAAO;EACL,YAAY;EACZ,UAHY,EAAS,MAAM,+CAA+C,GAGxD;EACnB;CAGH,IAAM,IACJ,EAAO,IAAI,MAAM,WACV;EACL,IAAM,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,SAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;KAC3D;AAEN,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU;GACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE;GAClC,GAAI,EAAO,IAAI,mBAAmB,GAC9B,EAAE,kBAAkB,EAAO,IAAI,mBAAmB,EAAE,GACpD,EAAE;GACN,GAAI,EAAO,IAAI,WAAW,GAAG,EAAE,UAAU,EAAO,IAAI,WAAW,EAAE,GAAG,EAAE;GACvE;EACF"}
|
|
1
|
+
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering — captures the user's current browser context\n * to be sent alongside every assistant prompt for better AI awareness.\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * ★ A `type` alias, deliberately, not an `interface`.\n *\n * fe-libs' `PageContext` carries an index signature (`[key: string]: unknown`)\n * so products can add their own keys. TypeScript grants a type alias an\n * IMPLICIT index signature and an interface none, so declaring this as an\n * interface makes it un-assignable to the seam's `gatherPageContext` — with an\n * error that talks about index signatures rather than about the seam.\n * HealthyBowl's equivalent is a type alias for exactly this reason.\n */\nexport type PageContext = {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n};\n\n/**\n * Human-readable names for VibeControls' top-level routes.\n *\n * ★ This is the label a USER would use for the page, not the route segment. It\n * reaches the model as \"the user is on the Sessions page\", so `sessions` →\n * `Sessions` is fine but `ai` → `AI` matters: the fallback title-caser would\n * produce \"Ai\", and an assistant that calls it that sounds like it has never\n * seen the product.\n *\n * Unlisted segments fall through to the title-caser, so a new route degrades to\n * something reasonable rather than to nothing.\n *\n * Each label is also seeded as `vibecontrols.assistant.page.<slug>`\n * (`config/assistantDisplayCopy.ts`) so the panel can SHOW it translated; the\n * English here is what the model is told.\n */\nexport const VIBECONTROLS_PAGE_LABELS: Readonly<Record<string, string>> = {\n home: 'Home',\n agents: 'Agents',\n sessions: 'Sessions',\n vibes: 'Vibes',\n notes: 'Notes',\n catalogs: 'Catalogs',\n components: 'Components',\n templates: 'Templates',\n tunnels: 'Tunnels',\n calendar: 'Calendar',\n ai: 'AI',\n settings: 'Settings',\n plugins: 'Plugins',\n // ★ Every other segment `VibeControlsRoutes.tsx` renders a page on (the test\n // there fails on an unlabelled one): a title-cased fallback has no seed key,\n // so it stayed English in every locale (\"Ai Builder\", \"Getting Started\").\n actions: 'Actions',\n 'ai-builder': 'AI Builder',\n // The legacy AI tools pages (plan, sessions) are the AI pages under an old path.\n 'ai-tools': 'AI',\n analytics: 'Analytics',\n audit: 'Audit',\n environments: 'Environments',\n 'getting-started': 'Getting Started',\n health: 'Health',\n logs: 'Logs',\n // A target's own page (`targets/:id`); the bare list redirects to Agents.\n targets: 'Targets',\n vibedeck: 'Vibe Deck',\n // The public share links: a shared session, and a shared Vibe Deck.\n share: 'Shared Session',\n deck: 'Vibe Deck',\n webhooks: 'Webhooks',\n};\n\n/**\n * What to CALL the page the user is on.\n *\n * Feeds the assistant's empty state (\"Ask about Sessions…\") and the shared\n * panel's live page label. Distinct from `gatherPageContext`, which produces\n * machine-readable context: this one is read by a human.\n */\nexport function describeCurrentPage(): string {\n const { currentPage } = gatherPageContext();\n // `currentPage` is up to three path segments; the product mount segment is\n // first when present, so skip it rather than labelling every page \"Vibecontrols\".\n const segments = currentPage.split('/').filter(Boolean);\n const first = (segments[0] === 'vibecontrols' ? segments[1] : segments[0]) ?? '';\n if (!first || first === 'home') return VIBECONTROLS_PAGE_LABELS.home;\n return (\n VIBECONTROLS_PAGE_LABELS[first] ??\n first\n .split('-')\n .map((word) => (word ? word.charAt(0).toUpperCase() + word.slice(1) : word))\n .join(' ')\n );\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveVibeControlsConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'vibecontrols',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.402.1',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveVibeControlsConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n /**\n * ★ `(?!new/?$)`: a create page is not an entity. `/vibecontrols/tunnels/new`\n * is the tunnel wizard, and without the lookahead it resolved to a tunnel whose\n * id is the literal string \"new\" — handing the agent a fabricated entity, which\n * is worse than none, because it answers confidently about something that does\n * not exist. `vibes/new` and `agents/new` redirect, but a turn sent mid-redirect\n * would read the same lie. A trailing slash is the same page.\n */\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/vibecontrols\\/agents\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_AGENT'],\n [/^\\/vibecontrols\\/sessions\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_SESSION'],\n [/^\\/vibecontrols\\/vibes\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_VIBE'],\n [/^\\/vibecontrols\\/notes\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_NOTE'],\n [/^\\/vibecontrols\\/catalogs\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_CATALOG'],\n [/^\\/vibecontrols\\/components\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_COMPONENT'],\n [/^\\/vibecontrols\\/templates\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_TEMPLATE'],\n [/^\\/vibecontrols\\/tunnels\\/(?!new\\/?$)([^/]+)\\/?$/, 'VIBECONTROLS_TUNNEL'],\n [/^\\/vibecontrols\\/calendar\\/task\\/([^/]+)\\/?$/, 'VIBECONTROLS_TASK'],\n [/^\\/vibecontrols\\/ai\\/sessions\\/([^/]+)\\/?$/, 'VIBECONTROLS_AI_SESSION'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n if (/^\\/vibecontrols\\/vibes\\/[^/]+\\/docs\\//.test(pathname)) {\n const match = pathname.match(/^\\/vibecontrols\\/vibes\\/([^/]+)\\/docs\\//);\n return {\n entityType: 'VIBECONTROLS_DOCSSITE',\n entityId: match?.[1],\n };\n }\n\n if (/^\\/vibecontrols\\/catalogs\\/[^/]+\\/docs\\//.test(pathname)) {\n const match = pathname.match(/^\\/vibecontrols\\/catalogs\\/([^/]+)\\/docs\\//);\n return {\n entityType: 'VIBECONTROLS_DOCSPAGE',\n entityId: match?.[1],\n };\n }\n\n if (/^\\/vibecontrols\\/components\\/[^/]+\\/docs\\//.test(pathname)) {\n const match = pathname.match(/^\\/vibecontrols\\/components\\/([^/]+)\\/docs\\//);\n return {\n entityType: 'VIBECONTROLS_DOCSPAGE',\n entityId: match?.[1],\n };\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('selectedTargetId')\n ? { selectedTargetId: params.get('selectedTargetId') }\n : {}),\n ...(params.get('actionId') ? { actionId: params.get('actionId') } : {}),\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-vibecontrols",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1007.1",
|
|
4
4
|
"description": "VibeControls microfrontend for Burdenoff products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"license": "PROPRIETARY",
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@apollo/client": "4.1.9",
|
|
66
|
-
"@burdenoff/fe-libs": "2026.
|
|
66
|
+
"@burdenoff/fe-libs": "2026.1007.6",
|
|
67
67
|
"@tanstack/react-query": "^5.90.16",
|
|
68
68
|
"@xyflow/react": "^12.10.2",
|
|
69
69
|
"clsx": "^2.1.1",
|