@burdenoff/microfe-vibecontrols 2026.928.1 → 2026.1002.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/AIAssistantPanel.js.map +1 -1
- package/dist/components/assistant/assistantProduct.js +4 -1
- package/dist/components/assistant/assistantProduct.js.map +1 -1
- package/dist/config/assistantUsecases.js.map +1 -1
- package/dist/utils/assistantPrompt.js +18 -14
- package/dist/utils/assistantPrompt.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIAssistantPanel.js","names":[],"sources":["../../../src/components/assistant/AIAssistantPanel.tsx"],"sourcesContent":["/**\n * VibeControls' AI assistant.\n *\n * ★★ Everything below this file is now shared. The panel, the orchestrator, the\n * transport, the voice session, the composer and every component in them live in\n * `fe-libs/shared/assistant` and answer to any product that mounts them. What is\n * left here is the things that are genuinely VibeControls', and nothing else:\n *\n * modes which use cases this product offers.\n * describeCurrentPage what to CALL the page the user is on.\n * gatherPageContext the same page, as structured context for the agent.\n * buildPrompt the confirm-before-mutate guardrail, the language\n * directive and the dictation instructions.\n * reportTokenUsage the charge, against VibeControls' OWN reporter.\n * storageFolders where this product's attachments and artifacts live.\n *\n * All
|
|
1
|
+
{"version":3,"file":"AIAssistantPanel.js","names":[],"sources":["../../../src/components/assistant/AIAssistantPanel.tsx"],"sourcesContent":["/**\n * VibeControls' AI assistant.\n *\n * ★★ Everything below this file is now shared. The panel, the orchestrator, the\n * transport, the voice session, the composer and every component in them live in\n * `fe-libs/shared/assistant` and answer to any product that mounts them. What is\n * left here is the things that are genuinely VibeControls', and nothing else:\n *\n * modes which use cases this product offers.\n * describeCurrentPage what to CALL the page the user is on.\n * gatherPageContext the same page, as structured context for the agent.\n * buildPrompt the confirm-before-mutate guardrail, the language\n * directive and the dictation instructions.\n * reportTokenUsage the charge, against VibeControls' OWN reporter.\n * storageFolders where this product's attachments and artifacts live.\n * localUiCommands how the panel reads a UI command it runs itself.\n * reportTurnTimings whether finished turns feed the latency histograms.\n * writeApproval which write flow the assistant asks for (proposal-v1).\n *\n * All nine, plus the product id, are one `Required` value in\n * `assistantProduct.ts`, spread into the provider here, so a missing answer is a\n * type error. `confirmWrite` is left out on purpose: HIGH-risk proposals use\n * fe-libs' shared confirmation dialog.\n *\n * Plus `writeAccess`, which is a PROP rather than another seam field: which\n * resource types prove \"this user can write here\" is a product answer, but the\n * product can simply compute it and hand it down. A capability a product can\n * work out for itself does not belong in an interface every product must\n * satisfy.\n *\n * ★ The provider wraps from OUTSIDE the shared panel rather than sitting inside\n * its tree: the panel calls `useAssistantProduct()` and `useAssistantTr()` in its\n * own body, so a provider nested in its JSX would be below the consumer and read\n * as null — which does not throw, it silently renders English with no modes.\n *\n * ★ `AiCreditsMeter` is composed AROUND the shared panel, not inside it. The\n * shared panel has no slot for it and should not grow one: a credits strip is a\n * VibeControls billing decision, not something every product wants above its\n * assistant. It listens to the `notifyAiCreditsUpdated` event the shared chat\n * area already dispatches, so it keeps working untouched.\n */\n\nimport type { FC } from 'react';\nimport {\n AIAssistantPanel as SharedAIAssistantPanel,\n type AIAssistantPanelProps as SharedProps,\n} from '@burdenoff/fe-libs/shared/assistant/ui/AIAssistantPanel';\nimport { AssistantProductProvider } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { getActorId, getWorkspaceId } from '@burdenoff/fe-libs/shared/assistant/context';\nimport { useWorkspaceWriteAccess } from '@/hooks/useWorkspaceWriteAccess';\nimport { useOfferedAssistantMode } from '@/hooks/useOfferedAssistantMode';\nimport { AiCreditsMeter } from './AiCreditsMeter';\nimport { VIBECONTROLS_ASSISTANT_PRODUCT } from './assistantProduct';\n\nexport type AIAssistantPanelProps = Omit<SharedProps, 'writeAccess'>;\n\nexport const AIAssistantPanel: FC<AIAssistantPanelProps> = (props) => {\n /**\n * ★★ A stored mode VibeControls no longer offers (the retired `general`) lands\n * on Ask BEFORE the shared panel's mount effects read it; see the hook. Called\n * here, above the panel, because that ordering is what makes it work.\n */\n useOfferedAssistantMode();\n\n /**\n * ★ Advisory, never a gate — see `useWorkspaceWriteAccess`. Gating on it\n * would remove api-calls from the product entirely until RBAC rows are\n * seeded. It warns before the effort instead of after it, and self-corrects\n * with no code change the moment those rows exist.\n */\n const writeAccess = useWorkspaceWriteAccess(getActorId(), getWorkspaceId() || null);\n\n return (\n <AssistantProductProvider {...VIBECONTROLS_ASSISTANT_PRODUCT}>\n <div className=\"flex flex-col h-full w-full min-h-0\">\n <AiCreditsMeter />\n <div className=\"flex-1 min-h-0\">\n <SharedAIAssistantPanel {...props} writeAccess={writeAccess} />\n </div>\n </div>\n </AssistantProductProvider>\n );\n};\n"],"mappings":";;;;;;;;;AAwDA,IAAa,KAA+C,MAAU;AAMpE,IAAyB;CAQzB,IAAM,IAAc,EAAwB,GAAY,EAAE,GAAgB,IAAI,KAAK;AAEnF,QACE,kBAAC,GAAD;EAA0B,GAAI;YAC5B,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAkB,CAAA,EAClB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAwB,GAAI;KAAoB;KAAe,CAAA;IAC3D,CAAA,CACF;;EACmB,CAAA"}
|
|
@@ -12,7 +12,10 @@ var o = {
|
|
|
12
12
|
storageFolders: {
|
|
13
13
|
attachments: "/VibeControls/AI Assistant/Attachments",
|
|
14
14
|
artifacts: "/VibeControls/AI Assistant/Artifacts"
|
|
15
|
-
}
|
|
15
|
+
},
|
|
16
|
+
localUiCommands: { themeWords: "mode-only" },
|
|
17
|
+
reportTurnTimings: !0,
|
|
18
|
+
writeApproval: "proposal-v1"
|
|
16
19
|
};
|
|
17
20
|
//#endregion
|
|
18
21
|
export { o as VIBECONTROLS_ASSISTANT_PRODUCT };
|
|
@@ -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.\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 { 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';\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
|
|
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 { 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';\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 * 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 = { themeWords: 'mode-only' };\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":";;;;AA2EA,IAAa,IAA+D;CAC1E,SAAS;CACT,OAAO;CACP;CACA;CACA,aAAa;CACb,kBAAkB;CAClB,gBA9C6B;EAC7B,aAAa;EACb,WAAW;EACZ;CA4CC,iBA1BsD,EAAE,YAAY,aAAa;CAiCjF,mBAAmB;CAiBnB,eAAe;CAChB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantUsecases.js","names":[],"sources":["../../src/config/assistantUsecases.ts"],"sourcesContent":["/**\n * VibeControls AI assistant modes.\n *\n * ★ These are the modes the shared fe-libs panel OFFERS. The first switch into a\n * `writeCapable` mode opens the one-time consent dialog, enforced in fe-libs\n * (`shared/assistant/ui/AssistantChatArea`); what is decided HERE is which modes\n * carry that flag. Same mechanism as HealthyBowl's `assistantUsecases.ts`.\n *\n * ★★★ NO MODE IS ADMIN-GATED (BOFF-7800). Every mode is offered to every\n * member, and RBAC alone decides what any of them may actually do. The decision:\n * \"Allow anyone to use any mode keeping auto as default — but RBAC will control\n * everything behind the scenes. If they can't do something, RBAC won't allow.\"\n *\n * ★★ The gate this replaces was asking the wrong question at the wrong scope.\n * It read the caller's role at the ACTIVE ORG (`myRoles` via\n * `getOrganizationId()`), while product writes are governed by the WORKSPACE\n * scope — so a workspace Editor, whom the server already permits, was refused\n * by the UI for being an org Viewer. Verified server-side: `sandbox:create` is\n * granted to Editor and above (wspace-rbac-svc seed-unified-rbac.ts:5836-5842),\n * so this exposes what the server already allows rather than granting anything.\n * A workspace Viewer is still refused — by RBAC, where the refusal belongs, and\n * it now reads as a permission answer rather than a malfunction (fe-libs #259).\n *\n * assistant \"Ask\". Every member. The sandbox runs with\n * AI_ASSISTANT_READONLY, so its GraphQL helper refuses mutations.\n * api-calls \"Take action\". Admins only. Writes through the gateway, and\n * the sandbox runs with AI_ASSISTANT_CONFIRM_WRITES, so each\n * write waits for the user's approval.\n * vibe-plugins \"Build\". Admins only. Builds plugin artifacts, but its sandbox\n * is NOT read-only; see the note on BUILD below. Not flagged\n * `writeCapable` (yet), for the reason given there.\n *\n * ★★ `general` is gone. A stored `general`, or any mode not listed here, lands\n * on Ask; see `resolveOfferedMode` and `useOfferedAssistantMode`.\n *\n * ★ The mode IDs are transport values and must not be renamed: they choose the\n * sandbox image, its environment and the agent's persona. Only labels change.\n */\n\nexport interface AssistantUsecase {\n id: string;\n label: string;\n description: string;\n suggestions: readonly string[];\n /**\n * ★★★ DELIBERATELY UNSET ON EVERY MODE (BOFF-7800). The field remains because\n * fe-libs still reads it, and declaring it is what switches the org-role check\n * back on — `useAssistantApiAccess` derives `hasAdminGatedMode` from exactly\n * this flag, so one `requiresAdmin: true` here silently restores the gate and\n * fires a `myRoles` query on every session. A test asserts no mode sets it.\n */\n requiresAdmin?: boolean;\n /** Can change live data, so the consent dialog and the composer's write-access warning apply. */\n writeCapable?: boolean;\n}\n\n/**\n * \"Ask\": read-only, for everyone.\n *\n * ★★ The copy must not promise a write. This mode's sandbox refuses every\n * mutation, so \"Create a new vibe called demo\", the suggestion it used to carry,\n * could only ever end in a refusal after the user had waited for a sandbox.\n * `assistantUsecases.test.ts` fails on write verbs anywhere in this copy.\n */\nconst ASK = {\n id: 'assistant',\n label: 'Ask',\n description:\n 'Ask about VibeControls and your workspace. It reads your vibes, sessions and agents, explains what you are looking at and points you to the right page. Read-only.',\n suggestions: [\n 'What can I do on this screen?',\n 'List my recent sessions',\n 'Where do I manage my agents?',\n 'How do tunnels work?',\n ],\n} as const satisfies AssistantUsecase;\n\n/** \"Take action\": the write mode. Admin-only, consent on first switch, approval per write. */\nconst TAKE_ACTION = {\n // Display label only; the mode ID stays 'api-calls'.\n id: 'api-calls',\n label: 'Take action',\n description:\n 'Let the assistant make changes for you: create vibes, rename them, stop sessions. It shows you each change and waits for your approval before anything is saved.',\n suggestions: [\n 'Create a new vibe called demo',\n 'Rename one of my vibes',\n 'Stop a session I no longer need',\n 'List my recent sessions',\n ],\n writeCapable: true,\n} as const satisfies AssistantUsecase;\n\n/**\n * \"Build\": Vibe Plugin authoring. Admin-only.\n *\n * ★★ Why admin-only, when its job is producing plugin files: the sandbox is\n * not confined to that job, and can write.\n * - fe-libs forwards the user's own access and workspace tokens on EVERY proxy\n * request, whatever the mode (`shared/assistant/api.ts`, `proxyToSandbox`).\n * - wspace-sandbox-svc passes them into the sandbox for every assistant mode\n * (`ensureAssistantProxyAccess`, `X-AI-Assistant-Auth-Token`).\n * - ai-assistant keeps them whenever the sandbox holds no delegated app\n * credentials, which `vibe-plugins` never does (proxy-token auth), and its\n * GraphQL helper then authenticates as the user (`api-auth.ts`).\n * - The mode has `bash`, the gateway URLs are in its environment, and it gets\n * NEITHER brake: AI_ASSISTANT_READONLY is set only for `assistant`,\n * AI_ASSISTANT_CONFIRM_WRITES only for `api-calls`.\n * So it can change workspace data within the user's own permissions, and\n * decision D4 makes every such mode admin-only.\n *\n * ★★ Why NOT `writeCapable`, although it can write: in fe-libs (2026.914.3 and\n * earlier) that flag routes the first switch into the mode through\n * `AssistantApiCallsConsentDialog`, whose copy is written for Take action only.\n * It is titled \"Switch to Take action mode?\", its button reads \"Enable Take\n * action\", and it promises the assistant will ask before making a change. For\n * Build all three are false: no AI_ASSISTANT_CONFIRM_WRITES, no confirm\n * preamble. Its one `apiCallsAcknowledged` flag also covers every\n * `writeCapable` mode, so accepting it from Build would skip Take action's real\n * consent. Showing no dialog is more honest than showing that one. The cost:\n * the composer's write-access warning (`writeAccessDenied`) no longer shows in\n * Build. Restore the flag once fe-libs words the dialog, and keeps the\n * acknowledgment, per mode
|
|
1
|
+
{"version":3,"file":"assistantUsecases.js","names":[],"sources":["../../src/config/assistantUsecases.ts"],"sourcesContent":["/**\n * VibeControls AI assistant modes.\n *\n * ★ These are the modes the shared fe-libs panel OFFERS. The first switch into a\n * `writeCapable` mode opens the one-time consent dialog, enforced in fe-libs\n * (`shared/assistant/ui/AssistantChatArea`); what is decided HERE is which modes\n * carry that flag. Same mechanism as HealthyBowl's `assistantUsecases.ts`.\n *\n * ★★★ NO MODE IS ADMIN-GATED (BOFF-7800). Every mode is offered to every\n * member, and RBAC alone decides what any of them may actually do. The decision:\n * \"Allow anyone to use any mode keeping auto as default — but RBAC will control\n * everything behind the scenes. If they can't do something, RBAC won't allow.\"\n *\n * ★★ The gate this replaces was asking the wrong question at the wrong scope.\n * It read the caller's role at the ACTIVE ORG (`myRoles` via\n * `getOrganizationId()`), while product writes are governed by the WORKSPACE\n * scope — so a workspace Editor, whom the server already permits, was refused\n * by the UI for being an org Viewer. Verified server-side: `sandbox:create` is\n * granted to Editor and above (wspace-rbac-svc seed-unified-rbac.ts:5836-5842),\n * so this exposes what the server already allows rather than granting anything.\n * A workspace Viewer is still refused — by RBAC, where the refusal belongs, and\n * it now reads as a permission answer rather than a malfunction (fe-libs #259).\n *\n * assistant \"Ask\". Every member. The sandbox runs with\n * AI_ASSISTANT_READONLY, so its GraphQL helper refuses mutations.\n * api-calls \"Take action\". Admins only. Writes through the gateway, and\n * the sandbox runs with AI_ASSISTANT_CONFIRM_WRITES, so each\n * write waits for the user's approval.\n * vibe-plugins \"Build\". Admins only. Builds plugin artifacts, but its sandbox\n * is NOT read-only; see the note on BUILD below. Not flagged\n * `writeCapable` (yet), for the reason given there.\n *\n * ★★ `general` is gone. A stored `general`, or any mode not listed here, lands\n * on Ask; see `resolveOfferedMode` and `useOfferedAssistantMode`.\n *\n * ★ The mode IDs are transport values and must not be renamed: they choose the\n * sandbox image, its environment and the agent's persona. Only labels change.\n */\n\nexport interface AssistantUsecase {\n id: string;\n label: string;\n description: string;\n suggestions: readonly string[];\n /**\n * ★★★ DELIBERATELY UNSET ON EVERY MODE (BOFF-7800). The field remains because\n * fe-libs still reads it, and declaring it is what switches the org-role check\n * back on — `useAssistantApiAccess` derives `hasAdminGatedMode` from exactly\n * this flag, so one `requiresAdmin: true` here silently restores the gate and\n * fires a `myRoles` query on every session. A test asserts no mode sets it.\n */\n requiresAdmin?: boolean;\n /** Can change live data, so the consent dialog and the composer's write-access warning apply. */\n writeCapable?: boolean;\n}\n\n/**\n * \"Ask\": read-only, for everyone.\n *\n * ★★ The copy must not promise a write. This mode's sandbox refuses every\n * mutation, so \"Create a new vibe called demo\", the suggestion it used to carry,\n * could only ever end in a refusal after the user had waited for a sandbox.\n * `assistantUsecases.test.ts` fails on write verbs anywhere in this copy.\n */\nconst ASK = {\n id: 'assistant',\n label: 'Ask',\n description:\n 'Ask about VibeControls and your workspace. It reads your vibes, sessions and agents, explains what you are looking at and points you to the right page. Read-only.',\n suggestions: [\n 'What can I do on this screen?',\n 'List my recent sessions',\n 'Where do I manage my agents?',\n 'How do tunnels work?',\n ],\n} as const satisfies AssistantUsecase;\n\n/** \"Take action\": the write mode. Admin-only, consent on first switch, approval per write. */\nconst TAKE_ACTION = {\n // Display label only; the mode ID stays 'api-calls'.\n id: 'api-calls',\n label: 'Take action',\n description:\n 'Let the assistant make changes for you: create vibes, rename them, stop sessions. It shows you each change and waits for your approval before anything is saved.',\n suggestions: [\n 'Create a new vibe called demo',\n 'Rename one of my vibes',\n 'Stop a session I no longer need',\n 'List my recent sessions',\n ],\n writeCapable: true,\n} as const satisfies AssistantUsecase;\n\n/**\n * \"Build\": Vibe Plugin authoring. Admin-only.\n *\n * ★★ Why admin-only, when its job is producing plugin files: the sandbox is\n * not confined to that job, and can write.\n * - fe-libs forwards the user's own access and workspace tokens on EVERY proxy\n * request, whatever the mode (`shared/assistant/api.ts`, `proxyToSandbox`).\n * - wspace-sandbox-svc passes them into the sandbox for every assistant mode\n * (`ensureAssistantProxyAccess`, `X-AI-Assistant-Auth-Token`).\n * - ai-assistant keeps them whenever the sandbox holds no delegated app\n * credentials, which `vibe-plugins` never does (proxy-token auth), and its\n * GraphQL helper then authenticates as the user (`api-auth.ts`).\n * - The mode has `bash`, the gateway URLs are in its environment, and it gets\n * NEITHER brake: AI_ASSISTANT_READONLY is set only for `assistant`,\n * AI_ASSISTANT_CONFIRM_WRITES only for `api-calls`.\n * So it can change workspace data within the user's own permissions, and\n * decision D4 makes every such mode admin-only.\n *\n * ★★ Why NOT `writeCapable`, although it can write: in fe-libs (2026.914.3 and\n * earlier) that flag routes the first switch into the mode through\n * `AssistantApiCallsConsentDialog`, whose copy is written for Take action only.\n * It is titled \"Switch to Take action mode?\", its button reads \"Enable Take\n * action\", and it promises the assistant will ask before making a change. For\n * Build all three are false: no AI_ASSISTANT_CONFIRM_WRITES, no confirm\n * preamble. Its one `apiCallsAcknowledged` flag also covers every\n * `writeCapable` mode, so accepting it from Build would skip Take action's real\n * consent. Showing no dialog is more honest than showing that one. The cost:\n * the composer's write-access warning (`writeAccessDenied`) no longer shows in\n * Build. Restore the flag once fe-libs words the dialog, and keeps the\n * acknowledgment, per mode. ★ fe-libs #241 (BOFF-7308, 2026.920.x) now does\n * both, so restoring it is an open product decision rather than a blocked one.\n * It is user-visible (a consent dialog on the first switch into Build, and\n * voice refused there like in any writing mode), so it was not made alongside\n * the fe-libs 2026.929.1 pin bump that surfaced it (BOFF-7803).\n * `assistantUsecases.test.ts` pins both the per-mode dialog and today's flags.\n *\n * ★ The flags gate the UI; they add no sandbox brake. That is why this copy\n * claims no approval step for the mode: the sandbox does not enforce one.\n */\nconst BUILD = {\n id: 'vibe-plugins',\n label: 'Build',\n description:\n 'Build a Vibe Plugin that extends VibeControls. The assistant scaffolds and compiles it from the platform guides and gives you the files to download.',\n suggestions: [\n 'How do I create a new Vibe Plugin?',\n 'What is the plugin manifest structure?',\n 'Explain the plugin lifecycle hooks',\n 'Show me a plugin example with frontend UI',\n ],\n} as const satisfies AssistantUsecase;\n\nexport const ASSISTANT_USECASE_CONFIG = [ASK, TAKE_ACTION, BUILD] as const;\n\nexport type AssistantUsecaseId = (typeof ASSISTANT_USECASE_CONFIG)[number]['id'];\n\n/**\n * Where a user lands when their mode is not offered.\n *\n * ★ The same literal fe-libs falls back to (`AssistantChatArea` resets to\n * `\"assistant\"` once the role check settles). Typed as a mode ID, so removing\n * Ask from the list above fails the typecheck here.\n */\nexport const DEFAULT_ASSISTANT_MODE: AssistantUsecaseId = 'assistant';\n\nfunction isOfferedMode(mode: unknown): mode is AssistantUsecaseId {\n return ASSISTANT_USECASE_CONFIG.some((config) => config.id === mode);\n}\n\n/**\n * The mode to use for a stored one: itself when this product offers it, Ask\n * when it does not (a retired `general`, another product's mode, a corrupt\n * value).\n *\n * ★ It answers only \"does this mode EXIST here\". Whether the user may use an\n * admin-only mode is the role check's question and stays with fe-libs: resetting\n * an admin's stored Take action here, before `myRoles` answers, would throw them\n * out of that mode on every reload.\n */\nexport function resolveOfferedMode(mode: unknown): AssistantUsecaseId {\n return isOfferedMode(mode) ? mode : DEFAULT_ASSISTANT_MODE;\n}\n"],"mappings":"AAiJA,IAAa,IAA2B;CAjF5B;EACV,IAAI;EACJ,OAAO;EACP,aACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CAGmB;EAElB,IAAI;EACJ,OAAO;EACP,aACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACD,cAAc;EACf;CAyCa;EACZ,IAAI;EACJ,OAAO;EACP,aACE;EACF,aAAa;GACX;GACA;GACA;GACA;GACD;EACF;CAEgE,EAWpD,IAA6C;AAE1D,SAAS,EAAc,GAA2C;AAChE,QAAO,EAAyB,MAAM,MAAW,EAAO,OAAO,EAAK;;AAatE,SAAgB,EAAmB,GAAmC;AACpE,QAAO,EAAc,EAAK,GAAG,IAAO"}
|
|
@@ -1,34 +1,38 @@
|
|
|
1
|
+
import { PROPOSAL_WRITE_RULES as e } from "@burdenoff/fe-libs/shared/assistant/proposalWriteRules";
|
|
1
2
|
//#region src/utils/assistantPrompt.ts
|
|
2
|
-
var
|
|
3
|
+
var t = "--- User request ---", n = [
|
|
3
4
|
"You are in api-calls mode for VibeControls and can run GraphQL queries and mutations on the user's behalf.",
|
|
4
5
|
"Before performing ANY write (create, update, delete — any mutation):",
|
|
5
6
|
"1. State exactly what you are about to change (entity, fields, and values).",
|
|
6
7
|
"2. Ask the user to confirm, and WAIT for an explicit \"yes\"/\"confirm\" before executing it.",
|
|
7
8
|
"Read-only queries may run without confirmation. Confirm each write separately; never batch mutations behind a single confirmation.",
|
|
8
9
|
"",
|
|
9
|
-
|
|
10
|
-
].join("\n")
|
|
11
|
-
function
|
|
10
|
+
t
|
|
11
|
+
].join("\n"), r = `${e("VibeControls")}\n\n${t}`;
|
|
12
|
+
function i(e) {
|
|
13
|
+
return e?.writeApproval === "proposal-v1" ? r : n;
|
|
14
|
+
}
|
|
15
|
+
function a(e, t) {
|
|
12
16
|
return [
|
|
13
17
|
t ? `Reply in ${e}. The user has explicitly chosen this language, so use it even if they write to you in another one.` : `Reply in ${e}, matching the language the user wrote in.`,
|
|
14
18
|
`Translate any English documentation you ground your answer in into ${e} rather than switching to English to quote it.`,
|
|
15
19
|
"Keep identifiers, code, CLI flags, API field names and product nouns in their original form — translating them would make them wrong."
|
|
16
20
|
].join("\n");
|
|
17
21
|
}
|
|
18
|
-
var
|
|
22
|
+
var o = [
|
|
19
23
|
"This turn was DICTATED, so treat unusual words as probable speech-recognition errors rather than as what the user meant.",
|
|
20
24
|
"Repair obvious mis-hearings of technical terms, product nouns, CLI flags and command names using the surrounding context.",
|
|
21
25
|
"Do NOT guess at a mis-heard identifier, id, quantity, or destination — ask which one the user meant before acting on it."
|
|
22
|
-
].join("\n"),
|
|
23
|
-
function
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
l ?
|
|
27
|
-
l ?
|
|
28
|
-
].filter(Boolean).join("\n\n"),
|
|
29
|
-
return
|
|
26
|
+
].join("\n"), s = ["Your reply will be read aloud. Write it to be heard, not scanned:", "short sentences, no markdown syntax, no tables, and no code blocks unless the user asked for code."].join("\n");
|
|
27
|
+
function c(e, n, r, c = !1, l = !1, u) {
|
|
28
|
+
let d = [
|
|
29
|
+
r ? a(r, c) : "",
|
|
30
|
+
l ? o : "",
|
|
31
|
+
l ? s : ""
|
|
32
|
+
].filter(Boolean).join("\n\n"), f = d ? `${d}\n\n` : "";
|
|
33
|
+
return e === "api-calls" ? `${f}${i(u)}\n${n}` : f ? `${f}${t}\n${n}` : n;
|
|
30
34
|
}
|
|
31
35
|
//#endregion
|
|
32
|
-
export {
|
|
36
|
+
export { c as buildPromptForMode };
|
|
33
37
|
|
|
34
38
|
//# sourceMappingURL=assistantPrompt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantPrompt.js","names":[],"sources":["../../src/utils/assistantPrompt.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"assistantPrompt.js","names":[],"sources":["../../src/utils/assistantPrompt.ts"],"sourcesContent":["/**\n * The write rules for the assistant's api-calls (Take action) mode, plus the\n * language and dictation instructions.\n *\n * The write rules depend on what the sandbox the turn goes to ENFORCES, which\n * fe-libs passes as `buildPrompt`'s sixth argument from that sandbox's own\n * `/health` (BOFF-7803 5a):\n *\n * - `legacy`: the in-sandbox agent executes mutations itself, so the turn asks\n * it to describe each write and wait for the user's \"yes\". ADVISORY: the\n * dependable guard is the host-side consent dialog plus the server's checks.\n * - `proposal-v1`: the helper only PROPOSES a mutation, and only the button on\n * its card confirms it. Asking for a typed \"yes\" there would contradict the\n * sandbox, so the turn carries fe-libs' `PROPOSAL_WRITE_RULES` instead.\n *\n * Either way the rules are prepended to the user's TURN, not to the session\n * system prompt. Sending them as `system` would REPLACE the whole mode prompt\n * and silently delete the agent's api-calls behaviour along with it.\n */\n\n/**\n * ★ The TRANSPORT's mode type, not this product's narrower list. The shared panel\n * calls `buildPrompt` with whatever mode its store holds, which can be one this\n * product no longer offers (a stored `general`) until the panel resets it, so\n * the parameter has to accept every mode the panel can send.\n */\nimport type { AssistantPromptOptions } from '@burdenoff/fe-libs/shared/assistant/product';\nimport { PROPOSAL_WRITE_RULES } from '@burdenoff/fe-libs/shared/assistant/proposalWriteRules';\nimport type { AssistantMode } from '@burdenoff/fe-libs/shared/assistant/types';\n\n/**\n * Marks where our instructions stop and the user's words begin. Exactly one may\n * exist in a prompt, and it must be last — see `buildPromptForMode`.\n */\nexport const USER_REQUEST_DELIMITER = '--- User request ---';\n\n/**\n * The write rules for a Take-action turn to a LEGACY sandbox, which runs a\n * mutation itself. Unchanged by BOFF-7803 5a, byte for byte.\n */\nexport const API_CALLS_CONFIRM_PREAMBLE = [\n \"You are in api-calls mode for VibeControls and can run GraphQL queries and mutations on the user's behalf.\",\n 'Before performing ANY write (create, update, delete — any mutation):',\n '1. State exactly what you are about to change (entity, fields, and values).',\n '2. Ask the user to confirm, and WAIT for an explicit \"yes\"/\"confirm\" before executing it.',\n 'Read-only queries may run without confirmation. Confirm each write separately; never batch mutations behind a single confirmation.',\n '',\n USER_REQUEST_DELIMITER,\n].join('\\n');\n\n/**\n * The write rules for a Take-action turn to a PROPOSAL-V1 sandbox (BOFF-7803\n * 5a), composed exactly as fe-libs documents it: the shared rules, a blank\n * line, then this product's own delimiter.\n *\n * ★★ The rules are fe-libs' `PROPOSAL_WRITE_RULES`, not a VibeControls rewrite.\n * They are kept consistent with the sandbox's own proposal policy, so a local\n * copy would be a second text to keep in step with it. They carry no delimiter\n * of their own, so this one is the only one, and it is last, as after the\n * legacy preamble.\n */\nexport const API_CALLS_PROPOSAL_PREAMBLE = `${PROPOSAL_WRITE_RULES('VibeControls')}\\n\\n${USER_REQUEST_DELIMITER}`;\n\n/**\n * The write preamble for a Take-action turn, by what the receiving sandbox\n * enforces. Only exactly `proposal-v1` selects the proposal rules; anything\n * else, or no options at all, keeps the legacy text, which is what fe-libs\n * sends for a sandbox that has not reported the v1 protocol.\n */\nfunction writePreambleFor(options: AssistantPromptOptions | undefined): string {\n return options?.writeApproval === 'proposal-v1'\n ? API_CALLS_PROPOSAL_PREAMBLE\n : API_CALLS_CONFIRM_PREAMBLE;\n}\n\n/**\n * Ask for the reply in the user's language.\n *\n * ★ Nothing else does. The sandbox system prompt carries no language\n * instruction and the grounding documentation is entirely in English, so every\n * force in the session pushes the answer towards English regardless of what the\n * user wrote. The model's tendency to mirror its input is the only thing\n * resisting that, and it loses.\n *\n * The second clause is the one that matters: **translate the docs, do not\n * switch to their language.** Without it a grounded answer drifts back to\n * English mid-reply simply because the source material is English.\n */\nfunction buildLanguagePreamble(languageName: string, isExplicitChoice: boolean): string {\n return [\n isExplicitChoice\n ? `Reply in ${languageName}. The user has explicitly chosen this language, so use it even if they write to you in another one.`\n : `Reply in ${languageName}, matching the language the user wrote in.`,\n `Translate any English documentation you ground your answer in into ${languageName} rather than switching to English to quote it.`,\n 'Keep identifiers, code, CLI flags, API field names and product nouns in their original form — translating them would make them wrong.',\n ].join('\\n');\n}\n\n/**\n * Told to the model when the turn was SPOKEN rather than typed.\n *\n * ★ Speech recognition does not fail uniformly — it fails on specific words,\n * and in VibeControls the ones it fails on are predictable: identifiers, CLI\n * flags, and the product's own nouns. A recogniser has no model for \"vibe\",\n * \"tunnel\", or a session id read aloud, so a sentence comes back mostly right\n * with the load-bearing words wrong.\n *\n * The model is far better placed to repair that than the recogniser is, because\n * it has the product context — but only if it is TOLD the text was dictated.\n * Otherwise it answers the garbled token literally, which is what makes the\n * assistant look like it misunderstood.\n *\n * ★ The refusal to guess is the important half. Charitable interpretation is\n * right for a mangled verb and wrong for a mangled IDENTIFIER: acting on a\n * misheard session or agent id operates on someone else's resource. One extra\n * question costs a few seconds.\n */\nconst DICTATION_PREAMBLE = [\n 'This turn was DICTATED, so treat unusual words as probable speech-recognition errors rather than as what the user meant.',\n 'Repair obvious mis-hearings of technical terms, product nouns, CLI flags and command names using the surrounding context.',\n 'Do NOT guess at a mis-heard identifier, id, quantity, or destination — ask which one the user meant before acting on it.',\n].join('\\n');\n\n/**\n * A dictated turn is a SPOKEN conversation, so the reply is spoken too.\n *\n * ★★ In Take action only against a PROPOSAL-V1 sandbox. fe-libs refuses a\n * dictated send in a mode that confirms writes (\"Voice can't be used in a mode\n * that can change your data\"), and 2026.929.1 lifts that only when the turn is\n * proposal-bound: this product's flag is `proposal-v1` AND the sandbox's own\n * `/health` says v1 (BOFF-7803 5a, FE9). Then nothing spoken can run a change,\n * only a card's button can. So `dictated` and `api-calls` occur together only\n * with the proposal rules, never with the legacy \"wait for yes\".\n */\nconst VOICE_REPLY_PREAMBLE = [\n 'Your reply will be read aloud. Write it to be heard, not scanned:',\n 'short sentences, no markdown syntax, no tables, and no code blocks unless the user asked for code.',\n].join('\\n');\n\n/**\n * Compose the turn actually sent to the agent.\n *\n * Ordering is deliberate. Dictation goes AFTER the language lines and before the\n * delimiter: it is an instruction about the user's text, so it belongs with the\n * instructions rather than inside them.\n *\n * ★ `USER_REQUEST_DELIMITER` is emitted exactly once, here. Both api-calls\n * preambles end with their own copy, so it must not be added again — a second\n * one mid-prompt leaves the safety instructions sitting *after* a \"User\n * request\" marker, i.e. presented to the model as though the user had written\n * them.\n *\n * ★★ `options` is fe-libs' sixth argument (BOFF-7803 5a): what the sandbox this\n * turn goes to enforces, from its own `/health` — never this product's flag,\n * which only says what VibeControls ASKS for. A proposal-v1 sandbox gets the\n * proposal rules; every other turn is composed exactly as before.\n */\nexport function buildPromptForMode(\n mode: AssistantMode,\n content: string,\n languageName?: string,\n isExplicitChoice = false,\n dictated = false,\n options?: AssistantPromptOptions\n): string {\n const preamble = [\n languageName ? buildLanguagePreamble(languageName, isExplicitChoice) : '',\n dictated ? DICTATION_PREAMBLE : '',\n dictated ? VOICE_REPLY_PREAMBLE : '',\n ]\n .filter(Boolean)\n .join('\\n\\n');\n const language = preamble ? `${preamble}\\n\\n` : '';\n if (mode === 'api-calls') return `${language}${writePreambleFor(options)}\\n${content}`;\n return language ? `${language}${USER_REQUEST_DELIMITER}\\n${content}` : content;\n}\n"],"mappings":";;AAkCA,IAAa,IAAyB,wBAMzB,IAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK,EAaC,IAA8B,GAAG,EAAqB,eAAe,CAAC,MAAM;AAQzF,SAAS,EAAiB,GAAqD;AAC7E,QAAO,GAAS,kBAAkB,gBAC9B,IACA;;AAgBN,SAAS,EAAsB,GAAsB,GAAmC;AACtF,QAAO;EACL,IACI,YAAY,EAAa,uGACzB,YAAY,EAAa;EAC7B,sEAAsE,EAAa;EACnF;EACD,CAAC,KAAK,KAAK;;AAsBd,IAAM,IAAqB;CACzB;CACA;CACA;CACD,CAAC,KAAK,KAAK,EAaN,IAAuB,CAC3B,qEACA,qGACD,CAAC,KAAK,KAAK;AAoBZ,SAAgB,EACd,GACA,GACA,GACA,IAAmB,IACnB,IAAW,IACX,GACQ;CACR,IAAM,IAAW;EACf,IAAe,EAAsB,GAAc,EAAiB,GAAG;EACvE,IAAW,IAAqB;EAChC,IAAW,IAAuB;EACnC,CACE,OAAO,QAAQ,CACf,KAAK,OAAO,EACT,IAAW,IAAW,GAAG,EAAS,QAAQ;AAEhD,QADI,MAAS,cAAoB,GAAG,IAAW,EAAiB,EAAQ,CAAC,IAAI,MACtE,IAAW,GAAG,IAAW,EAAuB,IAAI,MAAY"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-vibecontrols",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1002.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.1002.1",
|
|
67
67
|
"@tanstack/react-query": "^5.90.16",
|
|
68
68
|
"@xyflow/react": "^12.10.2",
|
|
69
69
|
"clsx": "^2.1.1",
|