@burdenoff/microfe-vibecontrols 2026.910.1 → 2026.915.2
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 +19 -29
- package/dist/components/assistant/AIAssistantPanel.js.map +1 -1
- package/dist/components/assistant/assistantProduct.js +20 -0
- package/dist/components/assistant/assistantProduct.js.map +1 -0
- package/dist/components/assistant/index.js +0 -8
- package/dist/components/shared/GetHelpButton.js.map +1 -1
- package/dist/config/assistantUsecases.js +24 -27
- package/dist/config/assistantUsecases.js.map +1 -1
- package/dist/hooks/useOfferedAssistantMode.js +17 -0
- package/dist/hooks/useOfferedAssistantMode.js.map +1 -0
- package/dist/hooks/useOpenAssistantHelp.js +5 -5
- package/dist/hooks/useOpenAssistantHelp.js.map +1 -1
- package/dist/hooks/useWorkspaceWriteAccess.js +3 -3
- package/dist/hooks/useWorkspaceWriteAccess.js.map +1 -1
- package/dist/index.js +4 -5
- package/dist/pages/SessionsPage.js +14 -14
- package/dist/services/assistantApi.js +16 -17
- package/dist/services/assistantApi.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +5 -5
- package/dist/types/assistant.js.map +1 -1
- package/dist/utils/assistantPrompt.js.map +1 -1
- package/dist/utils/pageContext.js +10 -10
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +3 -2
- package/dist/components/assistant/AssistantChatArea.js +0 -11
- package/dist/components/assistant/AssistantChatInput.js +0 -4
- package/dist/components/assistant/AssistantConversationList.js +0 -6
- package/dist/components/assistant/AssistantExportMenu.js +0 -5
- package/dist/components/assistant/AssistantMessageItem.js +0 -9
- package/dist/components/assistant/AssistantMessageList.js +0 -4
- package/dist/components/assistant/AssistantModeSelector.js +0 -5
- package/dist/components/assistant/AssistantWelcome.js +0 -4
- package/dist/store/assistantStore.js +0 -165
- package/dist/store/assistantStore.js.map +0 -1
|
@@ -44,9 +44,9 @@ import { Fragment as bt, jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
|
44
44
|
import { PagePurpose as xt } from "@burdenoff/fe-libs/ui";
|
|
45
45
|
import { useEventBus as St } from "@burdenoff/fe-libs/shared/events";
|
|
46
46
|
import { AccessDenied as Ct, PermissionButton as wt } from "@burdenoff/fe-libs/shared/components";
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
47
|
+
import { MotionItem as Tt, MotionList as Et, MotionPage as Dt } from "@burdenoff/fe-libs/shared/motion";
|
|
48
|
+
import { useSafeShellUi as Ot } from "@burdenoff/fe-libs/shared/providers/shell/ShellUiProvider";
|
|
49
|
+
import { SaveToFilesButton as kt } from "@burdenoff/fe-libs/storage";
|
|
50
50
|
//#region src/pages/SessionsPage.tsx
|
|
51
51
|
var At = 3e5;
|
|
52
52
|
function jt(e, t) {
|
|
@@ -133,7 +133,7 @@ function Nt() {
|
|
|
133
133
|
};
|
|
134
134
|
return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
135
135
|
}, [en]);
|
|
136
|
-
let [yn, bn] = p(""), [xn, Sn] = p(""), [Cn, wn] = p("default"), [I, Tn] = p("TMUX"), [En, Dn] = p(!1), { terminalTabs: On, activeTerminalTabId: kn, sessionsViewMode: An, sessionsGroupBy: jn, sessionsTargetFilter: Mn, setTerminalTabs: Nn, setActiveTerminalTabId: Pn, setSessionsViewMode: Fn, setSessionsGroupBy: In, setSessionsTargetFilter: Ln, sessionInfoPanelOpen: Rn, setSessionInfoPanelOpen: zn, activeSessionId: L, lastUsedAgentId: Bn, lastUsedVibeId: Vn, lastUsedSessionType: Hn, lastUsedProfile: Un, setLastUsedSessionDefaults: Wn } = ve(), Gn =
|
|
136
|
+
let [yn, bn] = p(""), [xn, Sn] = p(""), [Cn, wn] = p("default"), [I, Tn] = p("TMUX"), [En, Dn] = p(!1), { terminalTabs: On, activeTerminalTabId: kn, sessionsViewMode: An, sessionsGroupBy: jn, sessionsTargetFilter: Mn, setTerminalTabs: Nn, setActiveTerminalTabId: Pn, setSessionsViewMode: Fn, setSessionsGroupBy: In, setSessionsTargetFilter: Ln, sessionInfoPanelOpen: Rn, setSessionInfoPanelOpen: zn, activeSessionId: L, lastUsedAgentId: Bn, lastUsedVibeId: Vn, lastUsedSessionType: Hn, lastUsedProfile: Un, setLastUsedSessionDefaults: Wn } = ve(), Gn = Ot(), Kn = f(Gn);
|
|
137
137
|
Kn.current = Gn, u(() => {
|
|
138
138
|
let e = Kn.current;
|
|
139
139
|
if (!e || O !== "terminal") return;
|
|
@@ -1327,11 +1327,11 @@ function Nt() {
|
|
|
1327
1327
|
running: J.filter((e) => e.status === "RUNNING").length,
|
|
1328
1328
|
stopped: J.filter((e) => e.status === "STOPPED").length
|
|
1329
1329
|
};
|
|
1330
|
-
if (It) return /* @__PURE__ */ m(
|
|
1330
|
+
if (It) return /* @__PURE__ */ m(Dt, {
|
|
1331
1331
|
className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
|
|
1332
1332
|
children: /* @__PURE__ */ m(xe, { message: y("sessions.loadingPermissions", "Loading permissions...") })
|
|
1333
1333
|
});
|
|
1334
|
-
if (Lt && Lt.permissionStrings?.length > 0 && !Ht) return /* @__PURE__ */ m(
|
|
1334
|
+
if (Lt && Lt.permissionStrings?.length > 0 && !Ht) return /* @__PURE__ */ m(Dt, {
|
|
1335
1335
|
className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
|
|
1336
1336
|
children: /* @__PURE__ */ m(Ct, {
|
|
1337
1337
|
title: y("common.accessDeniedTitle", "Access Denied"),
|
|
@@ -1339,11 +1339,11 @@ function Nt() {
|
|
|
1339
1339
|
details: y("sessions.requiredPermission", "Required permission: session:list")
|
|
1340
1340
|
})
|
|
1341
1341
|
});
|
|
1342
|
-
if (Br && !zr && J.length === 0) return /* @__PURE__ */ m(
|
|
1342
|
+
if (Br && !zr && J.length === 0) return /* @__PURE__ */ m(Dt, {
|
|
1343
1343
|
className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
|
|
1344
1344
|
children: /* @__PURE__ */ m(xe, { message: y("sessions.loading", "Loading sessions...") })
|
|
1345
1345
|
});
|
|
1346
|
-
if (Vr && J.length === 0) return /* @__PURE__ */ m(
|
|
1346
|
+
if (Vr && J.length === 0) return /* @__PURE__ */ m(Dt, {
|
|
1347
1347
|
className: "space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen",
|
|
1348
1348
|
children: /* @__PURE__ */ m(Se, {
|
|
1349
1349
|
title: y("sessions.failedLoad", "Failed to load sessions"),
|
|
@@ -1468,7 +1468,7 @@ function Nt() {
|
|
|
1468
1468
|
session: Q
|
|
1469
1469
|
})
|
|
1470
1470
|
] });
|
|
1471
|
-
if (O === "terminal") return /* @__PURE__ */ h(
|
|
1471
|
+
if (O === "terminal") return /* @__PURE__ */ h(Dt, {
|
|
1472
1472
|
className: "flex flex-col bg-bg-surface min-h-0 flex-1",
|
|
1473
1473
|
children: [
|
|
1474
1474
|
/* @__PURE__ */ h("div", {
|
|
@@ -1636,10 +1636,10 @@ function Nt() {
|
|
|
1636
1636
|
ta
|
|
1637
1637
|
]
|
|
1638
1638
|
});
|
|
1639
|
-
let na = (e) => /* @__PURE__ */ m(
|
|
1639
|
+
let na = (e) => /* @__PURE__ */ m(Et, {
|
|
1640
1640
|
stagger: .04,
|
|
1641
1641
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
|
|
1642
|
-
children: e.map((e) => /* @__PURE__ */ m(
|
|
1642
|
+
children: e.map((e) => /* @__PURE__ */ m(Tt, { children: /* @__PURE__ */ m(Re, {
|
|
1643
1643
|
session: e,
|
|
1644
1644
|
onOpen: mi,
|
|
1645
1645
|
onStart: yi,
|
|
@@ -1665,7 +1665,7 @@ function Nt() {
|
|
|
1665
1665
|
onDeselectAll: ai,
|
|
1666
1666
|
hideHeaderSelectAll: t
|
|
1667
1667
|
});
|
|
1668
|
-
return /* @__PURE__ */ h(
|
|
1668
|
+
return /* @__PURE__ */ h(Dt, {
|
|
1669
1669
|
className: "flex flex-col bg-bg-surface min-h-0 flex-1",
|
|
1670
1670
|
children: [
|
|
1671
1671
|
/* @__PURE__ */ h("div", {
|
|
@@ -1700,7 +1700,7 @@ function Nt() {
|
|
|
1700
1700
|
title: y("sessions.refreshTitle", "Refresh sessions"),
|
|
1701
1701
|
children: /* @__PURE__ */ m(ct, { className: `size-4 ${Br ? "animate-spin" : ""}` })
|
|
1702
1702
|
}),
|
|
1703
|
-
/* @__PURE__ */ m(
|
|
1703
|
+
/* @__PURE__ */ m(kt, {
|
|
1704
1704
|
label: y("sessions.export", "Export"),
|
|
1705
1705
|
disabled: dn,
|
|
1706
1706
|
testId: "sessions-export",
|
|
@@ -1930,7 +1930,7 @@ function Nt() {
|
|
|
1930
1930
|
}) : null,
|
|
1931
1931
|
/* @__PURE__ */ m(yt, {
|
|
1932
1932
|
mode: "wait",
|
|
1933
|
-
children: Y.length > 0 ? E === "none" ? O === "grid" ? /* @__PURE__ */ m("div", { children: na(Y) }, "grid-view") : /* @__PURE__ */ m(
|
|
1933
|
+
children: Y.length > 0 ? E === "none" ? O === "grid" ? /* @__PURE__ */ m("div", { children: na(Y) }, "grid-view") : /* @__PURE__ */ m(Tt, { children: ra(Y, !1) }, "list-view") : /* @__PURE__ */ m("div", {
|
|
1934
1934
|
className: "space-y-3",
|
|
1935
1935
|
"data-testid": "sessions-grouped",
|
|
1936
1936
|
children: ni.map((e) => {
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import { reportAssistantAiTokenUsage as e } from "@burdenoff/fe-libs/shared/assistant";
|
|
1
|
+
import { reportAssistantAiTokenUsage as e } from "@burdenoff/fe-libs/shared/assistant/api";
|
|
2
2
|
//#region src/services/assistantApi.ts
|
|
3
|
-
var t = "vibecontrols"
|
|
4
|
-
function
|
|
5
|
-
return
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
await r(null, t, n, i, a, o);
|
|
3
|
+
var t = "vibecontrols";
|
|
4
|
+
function n(e) {
|
|
5
|
+
return async ({ workspaceId: t, sessionId: n, messageId: r, model: i, tokens: a }) => {
|
|
6
|
+
if (!r || !i) {
|
|
7
|
+
console.warn("[assistant] skipping AI-credit charge — turn is missing messageId or model", {
|
|
8
|
+
sessionId: n,
|
|
9
|
+
hasMessageId: !!r,
|
|
10
|
+
hasModel: !!i
|
|
11
|
+
});
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await e(t, n, r, i, a, {});
|
|
15
|
+
};
|
|
18
16
|
}
|
|
17
|
+
var r = n(e);
|
|
19
18
|
//#endregion
|
|
20
|
-
export { t as PRODUCT,
|
|
19
|
+
export { t as PRODUCT, r as reportAssistantTokenUsage };
|
|
21
20
|
|
|
22
21
|
//# sourceMappingURL=assistantApi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n * What VibeControls' assistant needs from the shared assistant API, and nothing more.\n *\n * ★ This file used to be a ~265-line adapter mapping the pre-migration panel's\n * apollo-first signatures onto `@burdenoff/fe-libs/shared/assistant`. That panel\n * (the pre-migration fork, with its own persisted store) is deleted; the mounted\n * panel is fe-libs' own and calls the shared API directly. What remains is the\n * two answers only this product can give: its id, and how a turn's cost is\n * charged.\n */\n\nimport { reportAssistantAiTokenUsage } from '@burdenoff/fe-libs/shared/assistant/api';\n\n/**\n * The product id. The SAME value fe-libs sends as `AI_ASSISTANT_PRODUCT` and the\n * prefix of every `vibecontrols.assistant.*` translation key, so it has exactly\n * one source.\n */\nexport const PRODUCT = 'vibecontrols';\n\n/** The usage the shared panel reports once per completed turn (the seam's `reportTokenUsage`). */\nexport interface AssistantTokenUsage {\n workspaceId: string;\n sessionId: string;\n messageId?: string;\n model?: string;\n tokens: { input: number; output: number };\n}\n\n/** The shared charge, positional, as `@burdenoff/fe-libs/shared/assistant/api` declares it. */\nexport type ChargeAssistantTokenUsage = typeof reportAssistantAiTokenUsage;\n\n/**\n * Build the seam's `reportTokenUsage` over a positional charge.\n *\n * ★ The charge is a parameter so the adapter's decisions — what it forwards and\n * when it refuses — can be tested without a gateway. Production passes the\n * shared function and nothing else.\n *\n * ★★ A missing `messageId` or `model` is logged, not swallowed. This meter\n * exists because assistant spend was invisible; a silently skipped charge would\n * recreate exactly that blind spot, one turn at a time. The shared panel catches\n * a rejected charge, so a metering failure never breaks a turn.\n */\nexport function createAssistantTokenUsageReporter(\n charge: ChargeAssistantTokenUsage\n): (usage: AssistantTokenUsage) => Promise<void> {\n return async ({ workspaceId, sessionId, messageId, model, tokens }) => {\n if (!messageId || !model) {\n console.warn('[assistant] skipping AI-credit charge — turn is missing messageId or model', {\n sessionId,\n hasMessageId: Boolean(messageId),\n hasModel: Boolean(model),\n });\n return;\n }\n await charge(workspaceId, sessionId, messageId, model, tokens, {});\n };\n}\n\n/** VibeControls' `reportTokenUsage`: the shared `ReportAiTokenUsage` charge. */\nexport const reportAssistantTokenUsage = createAssistantTokenUsageReporter(\n reportAssistantAiTokenUsage\n);\n"],"mappings":";;AAkBA,IAAa,IAAU;AA0BvB,SAAgB,EACd,GAC+C;AAC/C,QAAO,OAAO,EAAE,gBAAa,cAAW,cAAW,UAAO,gBAAa;AACrE,MAAI,CAAC,KAAa,CAAC,GAAO;AACxB,WAAQ,KAAK,8EAA8E;IACzF;IACA,cAAc,EAAQ;IACtB,UAAU,EAAQ;IACnB,CAAC;AACF;;AAEF,QAAM,EAAO,GAAa,GAAW,GAAW,GAAO,GAAQ,EAAE,CAAC;;;AAKtE,IAAa,IAA4B,EACvC,EACD"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { dispatchAssistantQuotaExhausted as e, notifyAiCreditsUpdated as t } from "../utils/aiCredits.js";
|
|
2
|
+
import { gatherPageContext as n } from "../utils/pageContext.js";
|
|
3
3
|
import { useSandboxAssistantTransport as r } from "@burdenoff/fe-libs/shared/assistant";
|
|
4
4
|
//#region src/services/createSandboxAssistantTransport.ts
|
|
5
5
|
function i(i = "assistant") {
|
|
6
6
|
return r({
|
|
7
7
|
product: "vibecontrols",
|
|
8
8
|
mode: i,
|
|
9
|
-
gatherPageContext:
|
|
9
|
+
gatherPageContext: n,
|
|
10
10
|
enableCredits: !0,
|
|
11
|
-
onQuotaExhausted: () =>
|
|
12
|
-
onCreditsUpdated: () =>
|
|
11
|
+
onQuotaExhausted: () => e("ai-credits"),
|
|
12
|
+
onCreditsUpdated: () => t()
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant.js","names":[],"sources":["../../src/types/assistant.ts"],"sourcesContent":["/**\n * AI Assistant types.\n *\n *
|
|
1
|
+
{"version":3,"file":"assistant.js","names":[],"sources":["../../src/types/assistant.ts"],"sourcesContent":["/**\n * AI Assistant types.\n *\n * What the shared fe-libs panel reads from VibeControls: the mode ids this\n * product offers and their configs. The message, conversation, sandbox and\n * artifact shapes the pre-migration fork declared here are gone with it; the\n * mounted panel uses fe-libs' own.\n */\n\nimport type { AssistantModeConfig as SharedAssistantModeConfig } from '@burdenoff/fe-libs/shared/assistant/conversation';\nimport { ASSISTANT_USECASE_CONFIG, type AssistantUsecaseId } from '@/config/assistantUsecases';\n\nexport type AssistantMode = AssistantUsecaseId;\n\n/**\n * One mode as the shared panel reads it, narrowed to the IDs this product offers.\n *\n * ★ EXTENDS the fe-libs shape rather than restating it. The copy this replaced\n * listed four fields and not `requiresAdmin` / `writeCapable`, so nothing typed\n * against it could read the two flags the admin gate and the consent dialog run\n * on. Extending means a field fe-libs adds is here too, with its meaning.\n */\nexport interface AssistantModeConfig extends SharedAssistantModeConfig {\n id: AssistantMode;\n}\n\n/** The modes VibeControls offers, flags included. The product's answer, not the library's. */\nexport const ASSISTANT_MODES: AssistantModeConfig[] = [...ASSISTANT_USECASE_CONFIG];\n"],"mappings":";;AA2BA,IAAa,IAAyC,CAAC,GAAG,EAAyB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantPrompt.js","names":[],"sources":["../../src/utils/assistantPrompt.ts"],"sourcesContent":["/**\n * Soft confirm-before-mutate guardrail for the assistant's api-calls (write)\n * mode, plus the language and dictation instructions.\n *\n * The in-sandbox agent executes mutations autonomously — there is no hard\n * server-side confirmation — so we prepend a describe-and-confirm instruction to\n * the user's TURN, not to the session system prompt. Sending it as `system`\n * would REPLACE the whole mode prompt and silently delete the agent's api-calls\n * behaviour along with it.\n *\n * This is ADVISORY. The dependable guard is the host-side consent dialog plus\n * the admin gate.\n */\n\nimport type { AssistantMode } from '
|
|
1
|
+
{"version":3,"file":"assistantPrompt.js","names":[],"sources":["../../src/utils/assistantPrompt.ts"],"sourcesContent":["/**\n * Soft confirm-before-mutate guardrail for the assistant's api-calls (write)\n * mode, plus the language and dictation instructions.\n *\n * The in-sandbox agent executes mutations autonomously — there is no hard\n * server-side confirmation — so we prepend a describe-and-confirm instruction to\n * the user's TURN, not to the session system prompt. Sending it as `system`\n * would REPLACE the whole mode prompt and silently delete the agent's api-calls\n * behaviour along with it.\n *\n * This is ADVISORY. The dependable guard is the host-side consent dialog plus\n * the admin gate.\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 { 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\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 * 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/** A dictated turn is a SPOKEN conversation, so the reply is spoken too. */\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. The api-calls\n * preamble ends with its own copy, so it must not be added again — a second one\n * mid-prompt leaves the safety instructions sitting *after* a \"User request\"\n * marker, i.e. presented to the model as though the user had written them.\n */\nexport function buildPromptForMode(\n mode: AssistantMode,\n content: string,\n languageName?: string,\n isExplicitChoice = false,\n dictated = false\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}${API_CALLS_CONFIRM_PREAMBLE}\\n${content}`;\n return language ? `${language}${USER_REQUEST_DELIMITER}\\n${content}` : content;\n}\n"],"mappings":";AA0BA,IAAa,IAAyB,wBAEzB,IAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK;AAeZ,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,EAGN,IAAuB,CAC3B,qEACA,qGACD,CAAC,KAAK,KAAK;AAcZ,SAAgB,EACd,GACA,GACA,GACA,IAAmB,IACnB,IAAW,IACH;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,EAA2B,IAAI,MACvE,IAAW,GAAG,IAAW,EAAuB,IAAI,MAAY"}
|
|
@@ -38,16 +38,16 @@ function r(e, t) {
|
|
|
38
38
|
function i(e, t) {
|
|
39
39
|
let n = new URLSearchParams(t);
|
|
40
40
|
for (let [t, n] of [
|
|
41
|
-
[/^\/vibecontrols\/agents\/([^/]+)
|
|
42
|
-
[/^\/vibecontrols\/sessions\/([^/]+)
|
|
43
|
-
[/^\/vibecontrols\/vibes\/([^/]+)
|
|
44
|
-
[/^\/vibecontrols\/notes\/([^/]+)
|
|
45
|
-
[/^\/vibecontrols\/catalogs\/([^/]+)
|
|
46
|
-
[/^\/vibecontrols\/components\/([^/]+)
|
|
47
|
-
[/^\/vibecontrols\/templates\/([^/]+)
|
|
48
|
-
[/^\/vibecontrols\/tunnels\/([^/]+)
|
|
49
|
-
[/^\/vibecontrols\/calendar\/task\/([^/]+)
|
|
50
|
-
[/^\/vibecontrols\/ai\/sessions\/([^/]+)
|
|
41
|
+
[/^\/vibecontrols\/agents\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_AGENT"],
|
|
42
|
+
[/^\/vibecontrols\/sessions\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_SESSION"],
|
|
43
|
+
[/^\/vibecontrols\/vibes\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_VIBE"],
|
|
44
|
+
[/^\/vibecontrols\/notes\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_NOTE"],
|
|
45
|
+
[/^\/vibecontrols\/catalogs\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_CATALOG"],
|
|
46
|
+
[/^\/vibecontrols\/components\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_COMPONENT"],
|
|
47
|
+
[/^\/vibecontrols\/templates\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_TEMPLATE"],
|
|
48
|
+
[/^\/vibecontrols\/tunnels\/(?!new\/?$)([^/]+)\/?$/, "VIBECONTROLS_TUNNEL"],
|
|
49
|
+
[/^\/vibecontrols\/calendar\/task\/([^/]+)\/?$/, "VIBECONTROLS_TASK"],
|
|
50
|
+
[/^\/vibecontrols\/ai\/sessions\/([^/]+)\/?$/, "VIBECONTROLS_AI_SESSION"]
|
|
51
51
|
]) {
|
|
52
52
|
let i = r(t, e);
|
|
53
53
|
if (i) return {
|
|
@@ -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 const directMatches: Array<[RegExp, string]> = [\n [/^\\/vibecontrols\\/agents\\/([^/]+)
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-vibecontrols",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.915.2",
|
|
4
4
|
"description": "VibeControls microfrontend for Burdenoff products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
39
39
|
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
|
40
40
|
"type:check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
41
|
-
"
|
|
41
|
+
"test": "bun test ./src",
|
|
42
|
+
"sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format:check && bun run type:check && bun run test && bun run build && bun run build-storybook'",
|
|
42
43
|
"storybook": "storybook dev -p 6006",
|
|
43
44
|
"build-storybook": "storybook build",
|
|
44
45
|
"test:storybook": "test-storybook",
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import "../../shared/hooks/useTr.js";
|
|
2
|
-
import "../../services/assistantApi.js";
|
|
3
|
-
import "./AssistantChatInput.js";
|
|
4
|
-
import "./AssistantMessageList.js";
|
|
5
|
-
import "./AssistantModeSelector.js";
|
|
6
|
-
import "./AssistantWelcome.js";
|
|
7
|
-
import "../../store/assistantStore.js";
|
|
8
|
-
import "react";
|
|
9
|
-
import "@burdenoff/fe-libs/shared/graphql";
|
|
10
|
-
import "@burdenoff/fe-libs/shared/providers/shell";
|
|
11
|
-
import "react/jsx-runtime";
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import "../../shared/hooks/useTr.js";
|
|
2
|
-
import "../../services/assistantApi.js";
|
|
3
|
-
import "./ProductMarkdown.js";
|
|
4
|
-
import "react";
|
|
5
|
-
import "lucide-react";
|
|
6
|
-
import "@burdenoff/fe-libs/shared/graphql";
|
|
7
|
-
import "@burdenoff/fe-libs/shared/providers/shell";
|
|
8
|
-
import "react/jsx-runtime";
|
|
9
|
-
import "@burdenoff/fe-libs/storage";
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { createProfileScopedStorage as e } from "@burdenoff/fe-libs/shared/utils";
|
|
2
|
-
import { create as t } from "zustand";
|
|
3
|
-
import { persist as n } from "zustand/middleware";
|
|
4
|
-
//#region src/store/assistantStore.ts
|
|
5
|
-
function r() {
|
|
6
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
7
|
-
}
|
|
8
|
-
var i = t()(n((e, t) => ({
|
|
9
|
-
conversations: [],
|
|
10
|
-
activeConversationId: null,
|
|
11
|
-
mode: "assistant",
|
|
12
|
-
processingByConversationId: {},
|
|
13
|
-
conversationListOpen: !1,
|
|
14
|
-
sandboxesByMode: {},
|
|
15
|
-
pendingPrompt: null,
|
|
16
|
-
createConversation: (n, i) => {
|
|
17
|
-
let a = r(), o = (/* @__PURE__ */ new Date()).toISOString(), s = i ?? t().mode, c = {
|
|
18
|
-
id: a,
|
|
19
|
-
title: n || "New conversation",
|
|
20
|
-
mode: s,
|
|
21
|
-
messages: [],
|
|
22
|
-
backendSessionId: null,
|
|
23
|
-
createdAt: o,
|
|
24
|
-
updatedAt: o
|
|
25
|
-
};
|
|
26
|
-
return e((e) => ({
|
|
27
|
-
conversations: [c, ...e.conversations],
|
|
28
|
-
activeConversationId: a,
|
|
29
|
-
conversationListOpen: !1
|
|
30
|
-
})), a;
|
|
31
|
-
},
|
|
32
|
-
deleteConversation: (t) => e((e) => {
|
|
33
|
-
let n = e.conversations.filter((e) => e.id !== t), r = { ...e.processingByConversationId };
|
|
34
|
-
return delete r[t], {
|
|
35
|
-
conversations: n,
|
|
36
|
-
processingByConversationId: r,
|
|
37
|
-
activeConversationId: e.activeConversationId === t ? n[0]?.id ?? null : e.activeConversationId
|
|
38
|
-
};
|
|
39
|
-
}),
|
|
40
|
-
renameConversation: (t, n) => e((e) => ({ conversations: e.conversations.map((e) => e.id === t ? {
|
|
41
|
-
...e,
|
|
42
|
-
title: n,
|
|
43
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
44
|
-
} : e) })),
|
|
45
|
-
setActiveConversation: (t) => e((e) => ({
|
|
46
|
-
activeConversationId: t,
|
|
47
|
-
mode: e.conversations.find((e) => e.id === t)?.mode ?? e.mode,
|
|
48
|
-
conversationListOpen: !1
|
|
49
|
-
})),
|
|
50
|
-
setMode: (t) => e({ mode: t }),
|
|
51
|
-
setConversationMode: (t, n) => e((e) => ({
|
|
52
|
-
conversations: e.conversations.map((e) => e.id === t ? {
|
|
53
|
-
...e,
|
|
54
|
-
mode: n,
|
|
55
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
56
|
-
} : e),
|
|
57
|
-
mode: e.activeConversationId === t ? n : e.mode
|
|
58
|
-
})),
|
|
59
|
-
addMessage: (t, n) => e((e) => ({ conversations: e.conversations.map((e) => e.id === t ? {
|
|
60
|
-
...e,
|
|
61
|
-
messages: [...e.messages, n],
|
|
62
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
-
title: e.messages.length === 0 && n.role === "user" ? n.content.slice(0, 50) + (n.content.length > 50 ? "..." : "") : e.title
|
|
64
|
-
} : e) })),
|
|
65
|
-
updateMessage: (t, n, r) => e((e) => ({ conversations: e.conversations.map((e) => e.id === t ? {
|
|
66
|
-
...e,
|
|
67
|
-
messages: e.messages.map((e) => e.id === n ? {
|
|
68
|
-
...e,
|
|
69
|
-
content: r
|
|
70
|
-
} : e),
|
|
71
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
72
|
-
} : e) })),
|
|
73
|
-
patchMessage: (t, n, r) => e((e) => ({ conversations: e.conversations.map((e) => e.id === t ? {
|
|
74
|
-
...e,
|
|
75
|
-
messages: e.messages.map((e) => e.id === n ? {
|
|
76
|
-
...e,
|
|
77
|
-
...r
|
|
78
|
-
} : e),
|
|
79
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
80
|
-
} : e) })),
|
|
81
|
-
setConversationProcessing: (t, n, r = null, i = null) => e((e) => ({ processingByConversationId: {
|
|
82
|
-
...e.processingByConversationId,
|
|
83
|
-
[t]: {
|
|
84
|
-
status: n,
|
|
85
|
-
messageId: r,
|
|
86
|
-
error: i,
|
|
87
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
88
|
-
}
|
|
89
|
-
} })),
|
|
90
|
-
clearConversationProcessing: (t) => e((e) => {
|
|
91
|
-
let n = { ...e.processingByConversationId };
|
|
92
|
-
return delete n[t], { processingByConversationId: n };
|
|
93
|
-
}),
|
|
94
|
-
toggleConversationList: () => e((e) => ({ conversationListOpen: !e.conversationListOpen })),
|
|
95
|
-
setConversationListOpen: (t) => e({ conversationListOpen: t }),
|
|
96
|
-
clearConversation: (t) => e((e) => ({
|
|
97
|
-
conversations: e.conversations.map((e) => e.id === t ? {
|
|
98
|
-
...e,
|
|
99
|
-
messages: [],
|
|
100
|
-
backendSessionId: null,
|
|
101
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
102
|
-
} : e),
|
|
103
|
-
processingByConversationId: {
|
|
104
|
-
...e.processingByConversationId,
|
|
105
|
-
[t]: {
|
|
106
|
-
status: "idle",
|
|
107
|
-
messageId: null,
|
|
108
|
-
error: null,
|
|
109
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
})),
|
|
113
|
-
setBackendSessionId: (t, n) => e((e) => ({ conversations: e.conversations.map((e) => e.id === t ? {
|
|
114
|
-
...e,
|
|
115
|
-
backendSessionId: n
|
|
116
|
-
} : e) })),
|
|
117
|
-
clearBackendSessionId: (t) => e((e) => ({ conversations: e.conversations.map((e) => e.id === t ? {
|
|
118
|
-
...e,
|
|
119
|
-
backendSessionId: null
|
|
120
|
-
} : e) })),
|
|
121
|
-
setSandbox: (t, n, r) => e((e) => {
|
|
122
|
-
if (!n) {
|
|
123
|
-
let n = { ...e.sandboxesByMode };
|
|
124
|
-
return delete n[t], { sandboxesByMode: n };
|
|
125
|
-
}
|
|
126
|
-
return { sandboxesByMode: {
|
|
127
|
-
...e.sandboxesByMode,
|
|
128
|
-
[t]: {
|
|
129
|
-
sandboxId: n,
|
|
130
|
-
status: r
|
|
131
|
-
}
|
|
132
|
-
} };
|
|
133
|
-
}),
|
|
134
|
-
clearSandbox: (t) => e((e) => {
|
|
135
|
-
let n = { ...e.sandboxesByMode };
|
|
136
|
-
return delete n[t], { sandboxesByMode: n };
|
|
137
|
-
}),
|
|
138
|
-
resetStore: () => e({
|
|
139
|
-
conversations: [],
|
|
140
|
-
activeConversationId: null,
|
|
141
|
-
mode: "general",
|
|
142
|
-
processingByConversationId: {},
|
|
143
|
-
conversationListOpen: !1,
|
|
144
|
-
sandboxesByMode: {},
|
|
145
|
-
pendingPrompt: null
|
|
146
|
-
}),
|
|
147
|
-
submitPrompt: (t) => e({ pendingPrompt: t }),
|
|
148
|
-
consumePendingPrompt: () => {
|
|
149
|
-
let n = t().pendingPrompt;
|
|
150
|
-
return n !== null && e({ pendingPrompt: null }), n;
|
|
151
|
-
}
|
|
152
|
-
}), {
|
|
153
|
-
name: "vc-ai-assistant",
|
|
154
|
-
storage: e(),
|
|
155
|
-
partialize: (e) => ({
|
|
156
|
-
conversations: e.conversations.map(({ ...e }) => e),
|
|
157
|
-
activeConversationId: e.activeConversationId,
|
|
158
|
-
mode: e.mode,
|
|
159
|
-
conversationListOpen: e.conversationListOpen
|
|
160
|
-
})
|
|
161
|
-
}));
|
|
162
|
-
//#endregion
|
|
163
|
-
export { i as useAssistantStore };
|
|
164
|
-
|
|
165
|
-
//# sourceMappingURL=assistantStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"assistantStore.js","names":[],"sources":["../../src/store/assistantStore.ts"],"sourcesContent":["/**\n * AI Assistant state management.\n *\n * Zustand store with profile-scoped persistence for conversations,\n * active mode, and streaming state. Follows the same pattern as\n * vibeControlsStore.ts.\n */\n\nimport { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport type {\n AssistantConversationProcessing,\n AssistantConversationStatus,\n AssistantMode,\n AssistantSandboxRuntime,\n AssistantSandboxStatus,\n Conversation,\n Message,\n} from '@/types/assistant';\n\ninterface AssistantState {\n conversations: Conversation[];\n activeConversationId: string | null;\n mode: AssistantMode;\n processingByConversationId: Record<string, AssistantConversationProcessing>;\n conversationListOpen: boolean;\n /** Active sandboxes keyed by assistant mode */\n sandboxesByMode: Partial<Record<AssistantMode, AssistantSandboxRuntime>>;\n /**\n * A prompt queued up for auto-submit by the chat area. Used by the\n * `GetHelpButton` / `useOpenAssistantHelp` hook so pages can prompt the\n * assistant contextually from an empty state button without coupling to\n * the chat area internals.\n */\n pendingPrompt: string | null;\n\n // Actions\n createConversation: (title?: string, mode?: AssistantMode) => string;\n deleteConversation: (id: string) => void;\n renameConversation: (id: string, title: string) => void;\n setActiveConversation: (id: string | null) => void;\n setMode: (mode: AssistantMode) => void;\n setConversationMode: (conversationId: string, mode: AssistantMode) => void;\n addMessage: (conversationId: string, message: Message) => void;\n updateMessage: (conversationId: string, messageId: string, content: string) => void;\n patchMessage: (conversationId: string, messageId: string, patch: Partial<Message>) => void;\n setConversationProcessing: (\n conversationId: string,\n status: AssistantConversationStatus,\n messageId?: string | null,\n error?: string | null\n ) => void;\n clearConversationProcessing: (conversationId: string) => void;\n toggleConversationList: () => void;\n setConversationListOpen: (open: boolean) => void;\n clearConversation: (id: string) => void;\n setBackendSessionId: (conversationId: string, sessionId: string) => void;\n clearBackendSessionId: (conversationId: string) => void;\n setSandbox: (\n mode: AssistantMode,\n sandboxId: string | null,\n status: AssistantSandboxStatus\n ) => void;\n clearSandbox: (mode: AssistantMode) => void;\n resetStore: () => void;\n /** Queue a prompt for the chat area to auto-send on next render. */\n submitPrompt: (text: string) => void;\n /** Returns and clears any queued prompt. */\n consumePendingPrompt: () => string | null;\n}\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nexport const useAssistantStore = create<AssistantState>()(\n persist(\n (set, get) => ({\n conversations: [],\n activeConversationId: null,\n mode: 'assistant',\n processingByConversationId: {},\n conversationListOpen: false,\n sandboxesByMode: {},\n pendingPrompt: null,\n\n createConversation: (title?: string, mode?: AssistantMode) => {\n const id = generateId();\n const now = new Date().toISOString();\n const conversationMode = mode ?? get().mode;\n const conversation: Conversation = {\n id,\n title: title || 'New conversation',\n mode: conversationMode,\n messages: [],\n backendSessionId: null,\n createdAt: now,\n updatedAt: now,\n };\n set((state) => ({\n conversations: [conversation, ...state.conversations],\n activeConversationId: id,\n conversationListOpen: false,\n }));\n return id;\n },\n\n deleteConversation: (id) =>\n set((state) => {\n const filtered = state.conversations.filter((c) => c.id !== id);\n const nextProcessing = { ...state.processingByConversationId };\n delete nextProcessing[id];\n return {\n conversations: filtered,\n processingByConversationId: nextProcessing,\n activeConversationId:\n state.activeConversationId === id\n ? (filtered[0]?.id ?? null)\n : state.activeConversationId,\n };\n }),\n\n renameConversation: (id, title) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === id ? { ...c, title, updatedAt: new Date().toISOString() } : c\n ),\n })),\n\n setActiveConversation: (id) =>\n set((state) => {\n const selectedConversation = state.conversations.find(\n (conversation) => conversation.id === id\n );\n return {\n activeConversationId: id,\n mode: selectedConversation?.mode ?? state.mode,\n conversationListOpen: false,\n };\n }),\n\n setMode: (mode) => set({ mode }),\n\n setConversationMode: (conversationId, mode) =>\n set((state) => ({\n conversations: state.conversations.map((conversation) =>\n conversation.id === conversationId\n ? {\n ...conversation,\n mode,\n updatedAt: new Date().toISOString(),\n }\n : conversation\n ),\n mode: state.activeConversationId === conversationId ? mode : state.mode,\n })),\n\n addMessage: (conversationId, message) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: [...c.messages, message],\n updatedAt: new Date().toISOString(),\n // Auto-title from first user message\n title:\n c.messages.length === 0 && message.role === 'user'\n ? message.content.slice(0, 50) + (message.content.length > 50 ? '...' : '')\n : c.title,\n }\n : c\n ),\n })),\n\n updateMessage: (conversationId, messageId, content) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: c.messages.map((m) => (m.id === messageId ? { ...m, content } : m)),\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n })),\n\n patchMessage: (conversationId, messageId, patch) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n messages: c.messages.map((m) => (m.id === messageId ? { ...m, ...patch } : m)),\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n })),\n\n setConversationProcessing: (conversationId, status, messageId = null, error = null) =>\n set((state) => ({\n processingByConversationId: {\n ...state.processingByConversationId,\n [conversationId]: {\n status,\n messageId,\n error,\n updatedAt: new Date().toISOString(),\n },\n },\n })),\n\n clearConversationProcessing: (conversationId) =>\n set((state) => {\n const next = { ...state.processingByConversationId };\n delete next[conversationId];\n return { processingByConversationId: next };\n }),\n\n toggleConversationList: () =>\n set((state) => ({ conversationListOpen: !state.conversationListOpen })),\n\n setConversationListOpen: (open) => set({ conversationListOpen: open }),\n\n clearConversation: (id) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === id\n ? {\n ...c,\n messages: [],\n backendSessionId: null,\n updatedAt: new Date().toISOString(),\n }\n : c\n ),\n processingByConversationId: {\n ...state.processingByConversationId,\n [id]: {\n status: 'idle',\n messageId: null,\n error: null,\n updatedAt: new Date().toISOString(),\n },\n },\n })),\n\n setBackendSessionId: (conversationId, sessionId) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId\n ? {\n ...c,\n backendSessionId: sessionId,\n }\n : c\n ),\n })),\n\n clearBackendSessionId: (conversationId) =>\n set((state) => ({\n conversations: state.conversations.map((c) =>\n c.id === conversationId ? { ...c, backendSessionId: null } : c\n ),\n })),\n\n setSandbox: (mode, sandboxId, status) =>\n set((state) => {\n if (!sandboxId) {\n const nextSandboxesByMode = { ...state.sandboxesByMode };\n delete nextSandboxesByMode[mode];\n return { sandboxesByMode: nextSandboxesByMode };\n }\n\n return {\n sandboxesByMode: {\n ...state.sandboxesByMode,\n [mode]: {\n sandboxId,\n status,\n },\n },\n };\n }),\n\n clearSandbox: (mode) =>\n set((state) => {\n const nextSandboxesByMode = { ...state.sandboxesByMode };\n delete nextSandboxesByMode[mode];\n return { sandboxesByMode: nextSandboxesByMode };\n }),\n\n resetStore: () =>\n set({\n conversations: [],\n activeConversationId: null,\n mode: 'general',\n processingByConversationId: {},\n conversationListOpen: false,\n sandboxesByMode: {},\n pendingPrompt: null,\n }),\n\n submitPrompt: (text: string) => set({ pendingPrompt: text }),\n\n consumePendingPrompt: () => {\n const current = get().pendingPrompt;\n if (current !== null) set({ pendingPrompt: null });\n return current;\n },\n }),\n {\n name: 'vc-ai-assistant',\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n conversations: state.conversations.map(({ ...conversation }) => conversation),\n activeConversationId: state.activeConversationId,\n mode: state.mode,\n conversationListOpen: state.conversationListOpen,\n }),\n }\n )\n);\n"],"mappings":"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"}
|