@burdenoff/microfe-vibecontrols 2026.1005.2 → 2026.1008.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.
- package/dist/components/assistant/assistantProduct.js +0 -13
- package/dist/components/assistant/assistantProduct.js.map +1 -1
- package/dist/components/assistant/recordAdapters.js +24 -28
- package/dist/components/assistant/recordAdapters.js.map +1 -1
- package/dist/utils/pageContext.js +15 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +2 -2
|
@@ -30,19 +30,6 @@ var s = {
|
|
|
30
30
|
fallback: "vibe"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
-
{
|
|
34
|
-
id: "vibecontrols-agent",
|
|
35
|
-
pattern: "/vibecontrols/agents/:id",
|
|
36
|
-
params: { id: {
|
|
37
|
-
kind: "id",
|
|
38
|
-
exclude: ["new"]
|
|
39
|
-
} },
|
|
40
|
-
visibleWith: "vibecontrols-agents",
|
|
41
|
-
noun: {
|
|
42
|
-
key: "entity.agent",
|
|
43
|
-
fallback: "agent"
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
33
|
{
|
|
47
34
|
id: "vibecontrols-session",
|
|
48
35
|
pattern: "/vibecontrols/sessions/:id",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantProduct.js","names":[],"sources":["../../../src/components/assistant/assistantProduct.ts"],"sourcesContent":["/**\n * VibeControls' answers to the shared assistant's product seam, as one value.\n *\n * ★★ Every field of fe-libs' `AssistantProductProvider` is OPTIONAL, and a\n * missing one does not throw — it silently degrades. No `gatherPageContext`\n * sends every turn with no page context; no `buildPrompt` drops the\n * confirm-before-mutate guardrail and the language directive; no\n * `reportTokenUsage` meters nothing; no `storageFolders` starts a second folder\n * beside the one holding every saved attachment; no `writeApproval` keeps the\n * legacy write flow.\n *\n * So the value is typed `Required<…>` over the provider's own props: forgetting\n * a field, or fe-libs adding one, is a type error in this file rather than a\n * quiet regression in production. The wrapper spreads it into the provider, and\n * `assistantProduct.test.tsx` reads it back through fe-libs' real context.\n */\n\nimport type { LocalUiCommandOptions } from '@burdenoff/fe-libs/shared/assistant/localUiCommands';\nimport type { AssistantEntityRoute } from '@burdenoff/fe-libs/shared/assistant/uiActions/entityRoutes';\nimport type { AssistantProductProviderProps } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { ASSISTANT_MODES } from '@/types/assistant';\nimport { PRODUCT, reportAssistantTokenUsage } from '@/services/assistantApi';\nimport { describeCurrentPage, gatherPageContext } from '@/utils/pageContext';\nimport { buildPromptForMode } from '@/utils/assistantPrompt';\nimport { VIBECONTROLS_RECORD_ADAPTERS } from './recordAdapters';\n\n/**\n * Where this product's assistant files live.\n *\n * ★★ These strings are LOAD-BEARING once users have saved anything. They are the\n * real folders in the workspace file library holding every attachment and\n * artifact. Changing them does not fail — it starts a second folder beside the\n * first and orphans everything in the original.\n *\n * ★ Note the capitalisation: `/VibeControls/…`, while the product id is\n * `vibecontrols`. That mismatch is exactly why fe-libs cannot derive this and\n * why it is supplied here.\n */\nexport const STORAGE_FOLDERS = {\n attachments: '/VibeControls/AI Assistant/Attachments',\n artifacts: '/VibeControls/AI Assistant/Artifacts',\n} as const;\n\n/**\n * The pages of ONE record that no sidebar entry names (BOFF-8060 N1b), so \"take me\n * there\" can open them from a link in the assistant's own reply.\n *\n * ★★ `exclude` lists the router's STATIC siblings of each `:id` segment\n * (`VibeControlsRoutes.tsx`): `vibes/new` redirects to the create dialog, so it is\n * not a vibe. `entityRoutes.test.ts` renders every template through the real\n * route table and checks every static sibling against them.\n *\n * ★ `/integrations/connections` is the app shell's Connected Systems page, shown\n * to whoever can see the Integrations entry.\n */\nexport const ENTITY_ROUTES: readonly AssistantEntityRoute[] = [\n {\n id: 'vibecontrols-vibe',\n pattern: '/vibecontrols/vibes/:id',\n params: { id: { kind: 'id', exclude: ['new'] } },\n visibleWith: 'vibecontrols-vibes',\n noun: { key: 'entity.vibe', fallback: 'vibe' },\n },\n {\n id: 'vibecontrols-
|
|
1
|
+
{"version":3,"file":"assistantProduct.js","names":[],"sources":["../../../src/components/assistant/assistantProduct.ts"],"sourcesContent":["/**\n * VibeControls' answers to the shared assistant's product seam, as one value.\n *\n * ★★ Every field of fe-libs' `AssistantProductProvider` is OPTIONAL, and a\n * missing one does not throw — it silently degrades. No `gatherPageContext`\n * sends every turn with no page context; no `buildPrompt` drops the\n * confirm-before-mutate guardrail and the language directive; no\n * `reportTokenUsage` meters nothing; no `storageFolders` starts a second folder\n * beside the one holding every saved attachment; no `writeApproval` keeps the\n * legacy write flow.\n *\n * So the value is typed `Required<…>` over the provider's own props: forgetting\n * a field, or fe-libs adding one, is a type error in this file rather than a\n * quiet regression in production. The wrapper spreads it into the provider, and\n * `assistantProduct.test.tsx` reads it back through fe-libs' real context.\n */\n\nimport type { LocalUiCommandOptions } from '@burdenoff/fe-libs/shared/assistant/localUiCommands';\nimport type { AssistantEntityRoute } from '@burdenoff/fe-libs/shared/assistant/uiActions/entityRoutes';\nimport type { AssistantProductProviderProps } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { ASSISTANT_MODES } from '@/types/assistant';\nimport { PRODUCT, reportAssistantTokenUsage } from '@/services/assistantApi';\nimport { describeCurrentPage, gatherPageContext } from '@/utils/pageContext';\nimport { buildPromptForMode } from '@/utils/assistantPrompt';\nimport { VIBECONTROLS_RECORD_ADAPTERS } from './recordAdapters';\n\n/**\n * Where this product's assistant files live.\n *\n * ★★ These strings are LOAD-BEARING once users have saved anything. They are the\n * real folders in the workspace file library holding every attachment and\n * artifact. Changing them does not fail — it starts a second folder beside the\n * first and orphans everything in the original.\n *\n * ★ Note the capitalisation: `/VibeControls/…`, while the product id is\n * `vibecontrols`. That mismatch is exactly why fe-libs cannot derive this and\n * why it is supplied here.\n */\nexport const STORAGE_FOLDERS = {\n attachments: '/VibeControls/AI Assistant/Attachments',\n artifacts: '/VibeControls/AI Assistant/Artifacts',\n} as const;\n\n/**\n * The pages of ONE record that no sidebar entry names (BOFF-8060 N1b), so \"take me\n * there\" can open them from a link in the assistant's own reply.\n *\n * ★★ `exclude` lists the router's STATIC siblings of each `:id` segment\n * (`VibeControlsRoutes.tsx`): `vibes/new` redirects to the create dialog, so it is\n * not a vibe. `entityRoutes.test.ts` renders every template through the real\n * route table and checks every static sibling against them.\n *\n * ★★ BOFF-8686: there is NO agent route on purpose. The agent details page writes\n * on load (it rotates the agent's gateway OAuth secret and syncs session states),\n * so no assistant path may resolve to `/vibecontrols/agents/:id`: neither a\n * spoken \"open agent X\" nor a link, \"take me there\" or a navigate action in a\n * model reply (fe-libs resolves all of them through this table). The sidebar's\n * agents LIST page is unaffected. To switch it back on once that page is\n * read-only, restore the entry (and the agent record adapter).\n *\n * ★ `/integrations/connections` is the app shell's Connected Systems page, shown\n * to whoever can see the Integrations entry.\n */\nexport const ENTITY_ROUTES: readonly AssistantEntityRoute[] = [\n {\n id: 'vibecontrols-vibe',\n pattern: '/vibecontrols/vibes/:id',\n params: { id: { kind: 'id', exclude: ['new'] } },\n visibleWith: 'vibecontrols-vibes',\n noun: { key: 'entity.vibe', fallback: 'vibe' },\n },\n {\n id: 'vibecontrols-session',\n pattern: '/vibecontrols/sessions/:id',\n params: { id: { kind: 'id' } },\n visibleWith: 'vibecontrols-sessions',\n noun: { key: 'entity.session', fallback: 'session' },\n },\n {\n id: 'vibecontrols-connections',\n pattern: '/integrations/connections',\n params: {},\n visibleWith: 'integrations',\n noun: { key: 'entity.connections', fallback: 'Connected Systems' },\n },\n];\n\n/**\n * How the panel reads a UI command it carries out itself (BOFF-7802), for\n * VibeControls.\n *\n * ★★ `mode-only` because VibeControls' CONTENT has themes of its own. A docs\n * site has a Light/Dark theme (`DocsSettingsPanel`), and a VibeDeck has a colour\n * theme (`VibeDeckForm`). So \"use the dark theme\" on either page is a request\n * about that site or deck, and must reach the model rather than flip the app.\n * fe-libs names this exact case (`LocalUiCommandOptions.themeWords`). A miss\n * costs only a turn; a false match changes the app when the user meant their\n * content. \"<name> mode\" (\"dark mode\"), and a theme qualified as the app's\n * (\"change the app theme to dark\"), still change the app locally, with no turn.\n *\n * ★ Its own exported constant so the seam test can pin the field by identity,\n * like every other field on the seam value.\n */\nexport const LOCAL_UI_COMMANDS: LocalUiCommandOptions = {\n themeWords: 'mode-only',\n entityRoutes: ENTITY_ROUTES,\n // BOFF-8060 N1b: \"open the Staging vibe\", by name (vibes only: BOFF-8686).\n records: VIBECONTROLS_RECORD_ADAPTERS,\n};\n\n/**\n * Every seam field, required: the provider's props minus its children, and\n * minus the one answer VibeControls deliberately does not give.\n *\n * ★★ `confirmWrite` is omitted so a HIGH-risk write proposal is confirmed in\n * fe-libs' shared `AssistantWriteConfirmDialog` (BOFF-7803 5a). That is the\n * documented default when a product has no dialog of its own, and VibeControls\n * has none. A product dialog must meet the whole contract fe-libs sets for it\n * (readback, target count, consequences, the exact confirm label, sensitive\n * values masked, never `window.confirm`). The shared one already does, so a\n * VibeControls copy would only be a second place for that to go wrong.\n */\nexport type VibeControlsAssistantProduct = Required<\n Omit<AssistantProductProviderProps, 'children' | 'confirmWrite'>\n>;\n\nexport const VIBECONTROLS_ASSISTANT_PRODUCT: VibeControlsAssistantProduct = {\n product: PRODUCT,\n modes: ASSISTANT_MODES,\n describeCurrentPage,\n gatherPageContext,\n buildPrompt: buildPromptForMode,\n reportTokenUsage: reportAssistantTokenUsage,\n storageFolders: STORAGE_FOLDERS,\n localUiCommands: LOCAL_UI_COMMANDS,\n /**\n * ★ On. That is fe-libs' own default (on unless `false`, BOFF-7801) and what\n * VibeControls does today: this seam never set the field, so its turns have\n * been reported by default. The reports carry timings and coarse dimensions\n * only, and the proposal flow's latency is what the rollout measures.\n */\n reportTurnTimings: true,\n /**\n * ★★★ VibeControls asks for the proposal flow (BOFF-7803 5a). At\n * `'proposal-v1'` fe-libs provisions Ask and Take action (`api-calls`)\n * through the `assistant:use`-gated `provisionAssistantSandbox`, requests the\n * proposal flow for Take action, and lets a spoken Take-action turn through\n * when the sandbox's own `/health` also says v1. Every change then waits for\n * the button on its card; the sandbox and wspace-sandbox-svc decide whether\n * it runs. Build (`vibe-plugins`) is not affected: it is still provisioned\n * through `createSandbox`, exactly as before.\n *\n * ★★ That provisioning path has NO `createSandbox` fallback: while\n * wspace-sandbox-svc answers it with 503 (its session relay switched off),\n * the assistant cannot start Ask or Take action at all. This flag reaches\n * users only when vibecontrols-app pins the microfe version that carries it,\n * so the app bump is the release switch.\n */\n writeApproval: 'proposal-v1',\n};\n"],"mappings":";;;;;AA8HA,IAAa,IAA+D;CAC1E,SAAS;CACT,OAAO;CACP;CACA;CACA,aAAa;CACb,kBAAkB;CAClB,gBA/F6B;EAC7B,aAAa;EACb,WAAW;EACZ;CA6FC,iBA/BsD;EACtD,YAAY;EACZ,cA1C4D;GAC5D;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE,IAAI;KAAE,MAAM;KAAM,SAAS,CAAC,MAAM;KAAE,EAAE;IAChD,aAAa;IACb,MAAM;KAAE,KAAK;KAAe,UAAU;KAAQ;IAC/C;GACD;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE,IAAI,EAAE,MAAM,MAAM,EAAE;IAC9B,aAAa;IACb,MAAM;KAAE,KAAK;KAAkB,UAAU;KAAW;IACrD;GACD;IACE,IAAI;IACJ,SAAS;IACT,QAAQ,EAAE;IACV,aAAa;IACb,MAAM;KAAE,KAAK;KAAsB,UAAU;KAAqB;IACnE;GACF;EAsBC,SAAS;EACV;CAiCC,mBAAmB;CAiBnB,eAAe;CAChB"}
|
|
@@ -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 = {
|
|
3
4
|
graphqlFetch: t,
|
|
4
5
|
getWorkspaceId: e
|
|
5
|
-
}, a = (e) => typeof e == "string" && e.trim().length > 0 ? e.trim() : null, o =
|
|
6
|
+
}, a = (e) => typeof e == "string" && e.trim().length > 0 ? e.trim() : null, o = n;
|
|
6
7
|
function s(e, t) {
|
|
7
8
|
let n = o(e);
|
|
8
9
|
if (!n) return !1;
|
|
9
10
|
let r = o(t).split(" ");
|
|
10
11
|
return n.split(" ").every((e) => r.some((t) => t.startsWith(e)));
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
let
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
var c = (e, t) => {
|
|
14
|
+
let n = o(e);
|
|
15
|
+
return n.length > 0 && n === o(t);
|
|
16
|
+
};
|
|
17
|
+
async function l(e, t, n, r, i, a, o) {
|
|
18
|
+
let l = e.getWorkspaceId();
|
|
19
|
+
if (!l) 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: l,
|
|
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 !== l || e.deletedAt || e.archivedAt) continue;
|
|
32
37
|
let t = o(e);
|
|
33
|
-
t === null || !s(i, t) ||
|
|
38
|
+
t === null || !s(i, t) || (c(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 u(e = i) {
|
|
41
46
|
return [{
|
|
42
47
|
id: "vibecontrols-vibe",
|
|
43
48
|
nouns: ["vibe", "vibes"],
|
|
@@ -46,20 +51,11 @@ function l(e = i) {
|
|
|
46
51
|
fallback: "vibe"
|
|
47
52
|
},
|
|
48
53
|
entityRouteId: "vibecontrols-vibe",
|
|
49
|
-
find: (t,
|
|
50
|
-
}, {
|
|
51
|
-
id: "vibecontrols-agent",
|
|
52
|
-
nouns: ["agent", "agents"],
|
|
53
|
-
noun: {
|
|
54
|
-
key: "entity.agent",
|
|
55
|
-
fallback: "agent"
|
|
56
|
-
},
|
|
57
|
-
entityRouteId: "vibecontrols-agent",
|
|
58
|
-
find: (t, n) => c(e, r, "AssistantFindAgents", (e) => e.vibecontrolsAgents, t, n, (e) => a(e.name) ?? a(e.hostname))
|
|
54
|
+
find: (t, n) => l(e, r, "AssistantFindVibes", (e) => e.vibes?.items, t, n, (e) => a(e.name))
|
|
59
55
|
}];
|
|
60
56
|
}
|
|
61
|
-
var
|
|
57
|
+
var d = u();
|
|
62
58
|
//#endregion
|
|
63
|
-
export {
|
|
59
|
+
export { d as VIBECONTROLS_RECORD_ADAPTERS };
|
|
64
60
|
|
|
65
61
|
//# 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\",
|
|
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\".\n *\n * ★★ BOFF-8686: there is deliberately NO agent adapter. The agent details page\n * writes on load (it rotates the agent's gateway OAuth secret and syncs session\n * states), so the assistant must not open it from a spoken name. fe-libs opens a\n * record locally only when it finds BOTH an adapter and an entity route, so the\n * agent adapter here and the `vibecontrols-agent` entry in `ENTITY_ROUTES`\n * (`assistantProduct.ts`) were removed together. To switch it back on once that\n * page is read-only, restore both (git history of this file holds the adapter).\n *\n * An adapter only LOOKS A RECORD UP BY NAME through the READ API the product's\n * own pages already use (`vibes`), 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 * ★ The list query has no 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 string (the product's other non-hook reads do the same):\n * `recordAdapters.test.ts` validates it 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\n/** The two things the adapter needs 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 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 return [vibes];\n}\n\nexport const VIBECONTROLS_RECORD_ADAPTERS: readonly AssistantRecordAdapter[] =\n createVibeControlsRecordAdapters();\n"],"mappings":";;AA2CA,IAAa,IAA2B,8KAalC,IAA+B;CAAE;CAAc,gBAAgB;CAAsB,EAUrF,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;AAiBnC,QAAO,CAhB+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,CACa;;AAGhB,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.1008.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",
|