@burdenoff/microfe-vibecontrols 2026.613.1 → 2026.618.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.
Files changed (62) hide show
  1. package/dist/components/agents/AgentConnectionRecovery.js +100 -94
  2. package/dist/components/agents/AgentConnectionRecovery.js.map +1 -1
  3. package/dist/components/agents/AgentMaintenanceTab.js +177 -160
  4. package/dist/components/agents/AgentMaintenanceTab.js.map +1 -1
  5. package/dist/components/agents/OpenInBrowserEditorButton.js +102 -94
  6. package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
  7. package/dist/components/assistant/AssistantChatArea.js +1 -0
  8. package/dist/components/assistant/AssistantChatArea.js.map +1 -1
  9. package/dist/components/assistant/AssistantModeSelector.js +20 -19
  10. package/dist/components/assistant/AssistantModeSelector.js.map +1 -1
  11. package/dist/components/tunnels/TunnelCard.js +3 -3
  12. package/dist/components/tunnels/TunnelCard.js.map +1 -1
  13. package/dist/components/tunnels/TunnelPublicAccessPanel.js +36 -36
  14. package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
  15. package/dist/config/assistantUsecases.js +11 -0
  16. package/dist/config/assistantUsecases.js.map +1 -1
  17. package/dist/constants/permissions.js +38 -6
  18. package/dist/constants/permissions.js.map +1 -1
  19. package/dist/index.js +4 -3
  20. package/dist/pages/CatalogDetailPage.js +155 -155
  21. package/dist/pages/CatalogDetailPage.js.map +1 -1
  22. package/dist/pages/CatalogPage.js +110 -109
  23. package/dist/pages/CatalogPage.js.map +1 -1
  24. package/dist/pages/ComponentDetailPage.js +338 -338
  25. package/dist/pages/ComponentDetailPage.js.map +1 -1
  26. package/dist/pages/ComponentsPage.js +104 -103
  27. package/dist/pages/ComponentsPage.js.map +1 -1
  28. package/dist/pages/TargetDetailsPage.js +140 -132
  29. package/dist/pages/TargetDetailsPage.js.map +1 -1
  30. package/dist/pages/TemplateDetailPage.js +201 -199
  31. package/dist/pages/TemplateDetailPage.js.map +1 -1
  32. package/dist/pages/TemplatesPage.js +62 -61
  33. package/dist/pages/TemplatesPage.js.map +1 -1
  34. package/dist/pages/VibeDetailsPage.js +568 -555
  35. package/dist/pages/VibeDetailsPage.js.map +1 -1
  36. package/dist/pages/ai/AIBookmarksPage.js +104 -89
  37. package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
  38. package/dist/pages/ai/AIPage.js +116 -107
  39. package/dist/pages/ai/AIPage.js.map +1 -1
  40. package/dist/pages/ai/AISessionDetailPage.js +46 -34
  41. package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
  42. package/dist/pages/ai/AgentManagerPage.js +112 -100
  43. package/dist/pages/ai/AgentManagerPage.js.map +1 -1
  44. package/dist/pages/tunnels/TunnelDetailsPage.js +177 -153
  45. package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
  46. package/dist/pages/tunnels/TunnelsTab.js +119 -111
  47. package/dist/pages/tunnels/TunnelsTab.js.map +1 -1
  48. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +24 -24
  49. package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
  50. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +9 -8
  51. package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
  52. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +13 -13
  53. package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
  54. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +19 -15
  55. package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
  56. package/dist/services/assistantApi.js +9 -3
  57. package/dist/services/assistantApi.js.map +1 -1
  58. package/dist/services/createSandboxAssistantTransport.js +120 -0
  59. package/dist/services/createSandboxAssistantTransport.js.map +1 -0
  60. package/dist/store/assistantStore.js +1 -1
  61. package/dist/store/assistantStore.js.map +1 -1
  62. package/package.json +5 -1
@@ -2,8 +2,8 @@ import { useTr as e } from "../../../shared/hooks/useTr.js";
2
2
  import { TunnelPublicAccessPanel as t } from "../../../components/tunnels/TunnelPublicAccessPanel.js";
3
3
  import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  //#region src/pages/tunnels/tabs/TunnelOverviewTab.tsx
5
- function i({ tunnel: i, onChanged: a }) {
6
- let o = e(), s = i.activeSessions?.length ?? 0, c = i.domains?.length ?? 0, l = i.primaryDomain?.hostname ? `https://${i.primaryDomain.hostname}` : null;
5
+ function i({ tunnel: i, canUpdate: a, onChanged: o }) {
6
+ let s = e(), c = i.activeSessions?.length ?? 0, l = i.domains?.length ?? 0, u = i.primaryDomain?.hostname ? `https://${i.primaryDomain.hostname}` : null;
7
7
  return /* @__PURE__ */ r("div", {
8
8
  className: "grid gap-6 md:grid-cols-2 max-w-4xl",
9
9
  children: [
@@ -11,7 +11,7 @@ function i({ tunnel: i, onChanged: a }) {
11
11
  className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
12
12
  children: [/* @__PURE__ */ n("h3", {
13
13
  className: "text-sm font-semibold text-text-primary mb-3",
14
- children: o("vibecontrols.tunnels.overview.configuration", "Configuration")
14
+ children: s("vibecontrols.tunnels.overview.configuration", "Configuration")
15
15
  }), /* @__PURE__ */ r("dl", {
16
16
  className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm",
17
17
  children: [
@@ -74,7 +74,7 @@ function i({ tunnel: i, onChanged: a }) {
74
74
  className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
75
75
  children: [/* @__PURE__ */ n("h3", {
76
76
  className: "text-sm font-semibold text-text-primary mb-3",
77
- children: o("vibecontrols.tunnels.overview.activity", "Activity")
77
+ children: s("vibecontrols.tunnels.overview.activity", "Activity")
78
78
  }), /* @__PURE__ */ r("dl", {
79
79
  className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm",
80
80
  children: [
@@ -84,7 +84,7 @@ function i({ tunnel: i, onChanged: a }) {
84
84
  }),
85
85
  /* @__PURE__ */ n("dd", {
86
86
  className: "text-text-primary",
87
- children: s
87
+ children: c
88
88
  }),
89
89
  /* @__PURE__ */ n("dt", {
90
90
  className: "text-text-muted",
@@ -92,7 +92,7 @@ function i({ tunnel: i, onChanged: a }) {
92
92
  }),
93
93
  /* @__PURE__ */ n("dd", {
94
94
  className: "text-text-primary",
95
- children: c
95
+ children: l
96
96
  }),
97
97
  /* @__PURE__ */ n("dt", {
98
98
  className: "text-text-muted",
@@ -125,8 +125,9 @@ function i({ tunnel: i, onChanged: a }) {
125
125
  tunnelId: i.id,
126
126
  tunnelStatus: i.status,
127
127
  publicAccessExpiresAt: i.publicAccessExpiresAt,
128
- publicUrl: l,
129
- onChanged: a
128
+ publicUrl: u,
129
+ canUpdate: a,
130
+ onChanged: o
130
131
  }),
131
132
  i.lastError && /* @__PURE__ */ r("section", {
132
133
  className: "md:col-span-2 rounded-lg bg-status-error-bg p-4 text-sm text-status-error-fg",
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelOverviewTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelOverviewTab.tsx"],"sourcesContent":["import type { GetTunnelQuery } from '@/generated/wspace-operations';\nimport { TunnelPublicAccessPanel } from '@/components/tunnels/TunnelPublicAccessPanel';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype TunnelDetails = NonNullable<GetTunnelQuery['tunnel']>;\n\nexport function TunnelOverviewTab({\n tunnel,\n onChanged,\n}: {\n tunnel: TunnelDetails;\n onChanged: () => void;\n}) {\n const tr = useTr();\n const sessionCount = tunnel.activeSessions?.length ?? 0;\n const domainCount = tunnel.domains?.length ?? 0;\n const publicUrl = tunnel.primaryDomain?.hostname\n ? `https://${tunnel.primaryDomain.hostname}`\n : null;\n return (\n <div className=\"grid gap-6 md:grid-cols-2 max-w-4xl\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.tunnels.overview.configuration', 'Configuration')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Slug</dt>\n <dd className=\"text-text-primary font-mono\">{tunnel.slug}</dd>\n <dt className=\"text-text-muted\">Provider</dt>\n <dd className=\"text-text-primary\">{tunnel.providerKey}</dd>\n <dt className=\"text-text-muted\">Protocol</dt>\n <dd className=\"text-text-primary\">{tunnel.protocol}</dd>\n <dt className=\"text-text-muted\">TLS mode</dt>\n <dd className=\"text-text-primary\">{tunnel.tlsMode}</dd>\n <dt className=\"text-text-muted\">Local target</dt>\n <dd className=\"text-text-primary font-mono\">\n {tunnel.localHost}:{tunnel.localPort}\n </dd>\n <dt className=\"text-text-muted\">Basic auth</dt>\n <dd className=\"text-text-primary\">\n {tunnel.hasBasicAuth ? (tunnel.basicAuthUser ?? 'configured') : 'off'}\n </dd>\n </dl>\n </section>\n\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.tunnels.overview.activity', 'Activity')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Active sessions</dt>\n <dd className=\"text-text-primary\">{sessionCount}</dd>\n <dt className=\"text-text-muted\">Attached domains</dt>\n <dd className=\"text-text-primary\">{domainCount}</dd>\n <dt className=\"text-text-muted\">Primary hostname</dt>\n <dd className=\"text-text-primary font-mono\">{tunnel.primaryDomain?.hostname ?? '—'}</dd>\n <dt className=\"text-text-muted\">Agent</dt>\n <dd className=\"text-text-primary font-mono\">{tunnel.agentId ?? '—'}</dd>\n <dt className=\"text-text-muted\">Created</dt>\n <dd className=\"text-text-primary\">{new Date(tunnel.createdAt).toLocaleString()}</dd>\n </dl>\n </section>\n\n <TunnelPublicAccessPanel\n tunnelId={tunnel.id}\n tunnelStatus={tunnel.status}\n publicAccessExpiresAt={tunnel.publicAccessExpiresAt}\n publicUrl={publicUrl}\n onChanged={onChanged}\n />\n\n {tunnel.lastError && (\n <section className=\"md:col-span-2 rounded-lg bg-status-error-bg p-4 text-sm text-status-error-fg\">\n <strong>Last error:</strong> {tunnel.lastError}\n </section>\n )}\n </div>\n );\n}\n"],"mappings":";;;;AAMA,SAAgB,EAAkB,EAChC,WACA,gBAIC;CACD,IAAM,IAAK,GAAO,EACZ,IAAe,EAAO,gBAAgB,UAAU,GAChD,IAAc,EAAO,SAAS,UAAU,GACxC,IAAY,EAAO,eAAe,WACpC,WAAW,EAAO,cAAc,aAChC;AACJ,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,+CAA+C,gBAAgB;KAChE,CAAA,EACL,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAS,CAAA;MACzC,kBAAC,MAAD;OAAI,WAAU;iBAA+B,EAAO;OAAU,CAAA;MAC9D,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAa,CAAA;MAC7C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,EAAO;OAAiB,CAAA;MAC3D,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAa,CAAA;MAC7C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,EAAO;OAAc,CAAA;MACxD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAa,CAAA;MAC7C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,EAAO;OAAa,CAAA;MACvD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAiB,CAAA;MACjD,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACG,EAAO;QAAU;QAAE,EAAO;QACxB;;MACL,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAe,CAAA;MAC/C,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO,eAAgB,EAAO,iBAAiB,eAAgB;OAC7D,CAAA;MACF;OACG;;GAEV,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,0CAA0C,WAAW;KACtD,CAAA,EACL,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAoB,CAAA;MACpD,kBAAC,MAAD;OAAI,WAAU;iBAAqB;OAAkB,CAAA;MACrD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAqB,CAAA;MACrD,kBAAC,MAAD;OAAI,WAAU;iBAAqB;OAAiB,CAAA;MACpD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAqB,CAAA;MACrD,kBAAC,MAAD;OAAI,WAAU;iBAA+B,EAAO,eAAe,YAAY;OAAS,CAAA;MACxF,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAU,CAAA;MAC1C,kBAAC,MAAD;OAAI,WAAU;iBAA+B,EAAO,WAAW;OAAS,CAAA;MACxE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAY,CAAA;MAC5C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,IAAI,KAAK,EAAO,UAAU,CAAC,gBAAgB;OAAM,CAAA;MACjF;OACG;;GAEV,kBAAC,GAAD;IACE,UAAU,EAAO;IACjB,cAAc,EAAO;IACrB,uBAAuB,EAAO;IACnB;IACA;IACX,CAAA;GAED,EAAO,aACN,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,UAAD,EAAA,UAAQ,eAAoB,CAAA;;KAAE,EAAO;KAC7B;;GAER"}
1
+ {"version":3,"file":"TunnelOverviewTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelOverviewTab.tsx"],"sourcesContent":["import type { GetTunnelQuery } from '@/generated/wspace-operations';\nimport { TunnelPublicAccessPanel } from '@/components/tunnels/TunnelPublicAccessPanel';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype TunnelDetails = NonNullable<GetTunnelQuery['tunnel']>;\n\nexport function TunnelOverviewTab({\n tunnel,\n canUpdate,\n onChanged,\n}: {\n tunnel: TunnelDetails;\n canUpdate: boolean;\n onChanged: () => void;\n}) {\n const tr = useTr();\n const sessionCount = tunnel.activeSessions?.length ?? 0;\n const domainCount = tunnel.domains?.length ?? 0;\n const publicUrl = tunnel.primaryDomain?.hostname\n ? `https://${tunnel.primaryDomain.hostname}`\n : null;\n return (\n <div className=\"grid gap-6 md:grid-cols-2 max-w-4xl\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.tunnels.overview.configuration', 'Configuration')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Slug</dt>\n <dd className=\"text-text-primary font-mono\">{tunnel.slug}</dd>\n <dt className=\"text-text-muted\">Provider</dt>\n <dd className=\"text-text-primary\">{tunnel.providerKey}</dd>\n <dt className=\"text-text-muted\">Protocol</dt>\n <dd className=\"text-text-primary\">{tunnel.protocol}</dd>\n <dt className=\"text-text-muted\">TLS mode</dt>\n <dd className=\"text-text-primary\">{tunnel.tlsMode}</dd>\n <dt className=\"text-text-muted\">Local target</dt>\n <dd className=\"text-text-primary font-mono\">\n {tunnel.localHost}:{tunnel.localPort}\n </dd>\n <dt className=\"text-text-muted\">Basic auth</dt>\n <dd className=\"text-text-primary\">\n {tunnel.hasBasicAuth ? (tunnel.basicAuthUser ?? 'configured') : 'off'}\n </dd>\n </dl>\n </section>\n\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n {tr('vibecontrols.tunnels.overview.activity', 'Activity')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Active sessions</dt>\n <dd className=\"text-text-primary\">{sessionCount}</dd>\n <dt className=\"text-text-muted\">Attached domains</dt>\n <dd className=\"text-text-primary\">{domainCount}</dd>\n <dt className=\"text-text-muted\">Primary hostname</dt>\n <dd className=\"text-text-primary font-mono\">{tunnel.primaryDomain?.hostname ?? '—'}</dd>\n <dt className=\"text-text-muted\">Agent</dt>\n <dd className=\"text-text-primary font-mono\">{tunnel.agentId ?? '—'}</dd>\n <dt className=\"text-text-muted\">Created</dt>\n <dd className=\"text-text-primary\">{new Date(tunnel.createdAt).toLocaleString()}</dd>\n </dl>\n </section>\n\n <TunnelPublicAccessPanel\n tunnelId={tunnel.id}\n tunnelStatus={tunnel.status}\n publicAccessExpiresAt={tunnel.publicAccessExpiresAt}\n publicUrl={publicUrl}\n canUpdate={canUpdate}\n onChanged={onChanged}\n />\n\n {tunnel.lastError && (\n <section className=\"md:col-span-2 rounded-lg bg-status-error-bg p-4 text-sm text-status-error-fg\">\n <strong>Last error:</strong> {tunnel.lastError}\n </section>\n )}\n </div>\n );\n}\n"],"mappings":";;;;AAMA,SAAgB,EAAkB,EAChC,WACA,cACA,gBAKC;CACD,IAAM,IAAK,GAAO,EACZ,IAAe,EAAO,gBAAgB,UAAU,GAChD,IAAc,EAAO,SAAS,UAAU,GACxC,IAAY,EAAO,eAAe,WACpC,WAAW,EAAO,cAAc,aAChC;AACJ,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,+CAA+C,gBAAgB;KAChE,CAAA,EACL,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAS,CAAA;MACzC,kBAAC,MAAD;OAAI,WAAU;iBAA+B,EAAO;OAAU,CAAA;MAC9D,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAa,CAAA;MAC7C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,EAAO;OAAiB,CAAA;MAC3D,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAa,CAAA;MAC7C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,EAAO;OAAc,CAAA;MACxD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAa,CAAA;MAC7C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,EAAO;OAAa,CAAA;MACvD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAiB,CAAA;MACjD,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACG,EAAO;QAAU;QAAE,EAAO;QACxB;;MACL,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAe,CAAA;MAC/C,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO,eAAgB,EAAO,iBAAiB,eAAgB;OAC7D,CAAA;MACF;OACG;;GAEV,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,0CAA0C,WAAW;KACtD,CAAA,EACL,kBAAC,MAAD;KAAI,WAAU;eAAd;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAoB,CAAA;MACpD,kBAAC,MAAD;OAAI,WAAU;iBAAqB;OAAkB,CAAA;MACrD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAqB,CAAA;MACrD,kBAAC,MAAD;OAAI,WAAU;iBAAqB;OAAiB,CAAA;MACpD,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAqB,CAAA;MACrD,kBAAC,MAAD;OAAI,WAAU;iBAA+B,EAAO,eAAe,YAAY;OAAS,CAAA;MACxF,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAU,CAAA;MAC1C,kBAAC,MAAD;OAAI,WAAU;iBAA+B,EAAO,WAAW;OAAS,CAAA;MACxE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAY,CAAA;MAC5C,kBAAC,MAAD;OAAI,WAAU;iBAAqB,IAAI,KAAK,EAAO,UAAU,CAAC,gBAAgB;OAAM,CAAA;MACjF;OACG;;GAEV,kBAAC,GAAD;IACE,UAAU,EAAO;IACjB,cAAc,EAAO;IACrB,uBAAuB,EAAO;IACnB;IACA;IACA;IACX,CAAA;GAED,EAAO,aACN,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,UAAD,EAAA,UAAQ,eAAoB,CAAA;;KAAE,EAAO;KAC7B;;GAER"}
@@ -6,21 +6,21 @@ import { TunnelSessionStatus as a } from "../../../generated/wspace-types.js";
6
6
  import { EmptyState as o } from "../../../components/shared/EmptyState.js";
7
7
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
8
  //#region src/pages/tunnels/tabs/TunnelSessionsTab.tsx
9
- function l({ tunnelId: l }) {
10
- let u = n(), { data: d, loading: f, error: p, refetch: m } = e({
9
+ function l({ tunnelId: l, canExecute: u }) {
10
+ let d = n(), { data: f, loading: p, error: m, refetch: h } = e({
11
11
  variables: {
12
12
  tunnelId: l,
13
13
  pagination: { limit: 50 }
14
14
  },
15
15
  fetchPolicy: "cache-and-network"
16
- }), [h] = t({ onCompleted: () => void m() }), g = d?.tunnelSessions?.items ?? [];
17
- return f && !d ? /* @__PURE__ */ s(r, { message: "Loading sessions" }) : p ? /* @__PURE__ */ s(i, {
16
+ }), [g] = t({ onCompleted: () => void h() }), _ = f?.tunnelSessions?.items ?? [];
17
+ return p && !f ? /* @__PURE__ */ s(r, { message: "Loading sessions" }) : m ? /* @__PURE__ */ s(i, {
18
18
  title: "Failed to load sessions",
19
- message: p.message,
20
- onRetry: () => m()
21
- }) : g.length === 0 ? /* @__PURE__ */ s(o, {
22
- title: u("vibecontrols.tunnels.sessions.emptySessionsTitle", "No sessions"),
23
- description: u("vibecontrols.tunnels.sessions.emptySessionsDescription", "Sessions appear here once a tunnel is started.")
19
+ message: m.message,
20
+ onRetry: () => h()
21
+ }) : _.length === 0 ? /* @__PURE__ */ s(o, {
22
+ title: d("vibecontrols.tunnels.sessions.emptySessionsTitle", "No sessions"),
23
+ description: d("vibecontrols.tunnels.sessions.emptySessionsDescription", "Sessions appear here once a tunnel is started.")
24
24
  }) : /* @__PURE__ */ s("div", {
25
25
  className: "rounded-lg border border-border-subtle bg-bg-surface max-w-4xl",
26
26
  children: /* @__PURE__ */ c("table", {
@@ -53,7 +53,7 @@ function l({ tunnelId: l }) {
53
53
  children: "Actions"
54
54
  })
55
55
  ] })
56
- }), /* @__PURE__ */ s("tbody", { children: g.map((e) => /* @__PURE__ */ c("tr", {
56
+ }), /* @__PURE__ */ s("tbody", { children: _.map((e) => /* @__PURE__ */ c("tr", {
57
57
  className: "border-b border-border-subtle last:border-b-0 hover:bg-bg-sunken",
58
58
  children: [
59
59
  /* @__PURE__ */ s("td", {
@@ -83,14 +83,14 @@ function l({ tunnelId: l }) {
83
83
  }),
84
84
  /* @__PURE__ */ s("td", {
85
85
  className: "px-4 py-3 text-right",
86
- children: (e.status === a.Issued || e.status === a.Connected) && /* @__PURE__ */ s("button", {
86
+ children: u && (e.status === a.Issued || e.status === a.Connected) && /* @__PURE__ */ s("button", {
87
87
  type: "button",
88
- onClick: () => void h({ variables: {
88
+ onClick: () => void g({ variables: {
89
89
  sessionId: e.id,
90
90
  reason: "revoked from UI"
91
91
  } }),
92
92
  className: "rounded-md border border-border-default px-3 py-1 text-xs text-status-error-fg hover:bg-status-error-bg",
93
- children: u("vibecontrols.tunnels.sessions.revoke", "Revoke")
93
+ children: d("vibecontrols.tunnels.sessions.revoke", "Revoke")
94
94
  })
95
95
  })
96
96
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelSessionsTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelSessionsTab.tsx"],"sourcesContent":["import {\n useListTunnelSessionsQuery,\n useRevokeTunnelSessionMutation,\n} from '@/generated/wspace-operations';\nimport { TunnelSessionStatus } from '@/generated/wspace-types';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { EmptyState } from '@/components/shared/EmptyState';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nexport function TunnelSessionsTab({ tunnelId }: { tunnelId: string }) {\n const tr = useTr();\n const { data, loading, error, refetch } = useListTunnelSessionsQuery({\n variables: { tunnelId, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n });\n const [revoke] = useRevokeTunnelSessionMutation({\n onCompleted: () => void refetch(),\n });\n\n const sessions = data?.tunnelSessions?.items ?? [];\n\n if (loading && !data) return <LoadingState message=\"Loading sessions\" />;\n if (error)\n return (\n <ErrorState\n title=\"Failed to load sessions\"\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n if (sessions.length === 0)\n return (\n <EmptyState\n title={tr('vibecontrols.tunnels.sessions.emptySessionsTitle', 'No sessions')}\n description={tr(\n 'vibecontrols.tunnels.sessions.emptySessionsDescription',\n 'Sessions appear here once a tunnel is started.'\n )}\n />\n );\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface max-w-4xl\">\n <table className=\"w-full text-sm\">\n <thead className=\"border-b border-border-subtle bg-bg-sunken text-text-secondary\">\n <tr>\n <th className=\"px-4 py-3 text-left font-medium\">Fingerprint</th>\n <th className=\"px-4 py-3 text-left font-medium\">Status</th>\n <th className=\"px-4 py-3 text-left font-medium\">Issued</th>\n <th className=\"px-4 py-3 text-left font-medium\">Expires</th>\n <th className=\"px-4 py-3 text-left font-medium\">Bytes</th>\n <th className=\"px-4 py-3 text-right font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {sessions.map((s: any) => (\n <tr\n key={s.id}\n className=\"border-b border-border-subtle last:border-b-0 hover:bg-bg-sunken\"\n >\n <td className=\"px-4 py-3 font-mono text-xs text-text-primary\">\n {s.tokenFingerprint}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">{s.status}</td>\n <td className=\"px-4 py-3 text-text-muted\">{new Date(s.issuedAt).toLocaleString()}</td>\n <td className=\"px-4 py-3 text-text-muted\">\n {new Date(s.expiresAt).toLocaleString()}\n </td>\n <td className=\"px-4 py-3 text-text-muted\">\n ↓ {s.bytesIn} / ↑ {s.bytesOut}\n </td>\n <td className=\"px-4 py-3 text-right\">\n {(s.status === TunnelSessionStatus.Issued ||\n s.status === TunnelSessionStatus.Connected) && (\n <button\n type=\"button\"\n onClick={() =>\n void revoke({\n variables: { sessionId: s.id, reason: 'revoked from UI' },\n })\n }\n className=\"rounded-md border border-border-default px-3 py-1 text-xs text-status-error-fg hover:bg-status-error-bg\"\n >\n {tr('vibecontrols.tunnels.sessions.revoke', 'Revoke')}\n </button>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,EAAkB,EAAE,eAAkC;CACpE,IAAM,IAAK,GAAO,EACZ,EAAE,SAAM,YAAS,UAAO,eAAY,EAA2B;EACnE,WAAW;GAAE;GAAU,YAAY,EAAE,OAAO,IAAI;GAAE;EAClD,aAAa;EACd,CAAC,EACI,CAAC,KAAU,EAA+B,EAC9C,mBAAmB,KAAK,GAAS,EAClC,CAAC,EAEI,IAAW,GAAM,gBAAgB,SAAS,EAAE;AAsBlD,QApBI,KAAW,CAAC,IAAa,kBAAC,GAAD,EAAc,SAAQ,oBAAqB,CAAA,GACpE,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,SAAS,EAAM;EACf,eAAe,GAAS;EACxB,CAAA,GAEF,EAAS,WAAW,IAEpB,kBAAC,GAAD;EACE,OAAO,EAAG,oDAAoD,cAAc;EAC5E,aAAa,EACX,0DACA,iDACD;EACD,CAAA,GAIJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,SAAD;GAAO,WAAU;aAAjB,CACE,kBAAC,SAAD;IAAO,WAAU;cACf,kBAAC,MAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAgB,CAAA;KAChE,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAW,CAAA;KAC3D,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAW,CAAA;KAC3D,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAY,CAAA;KAC5D,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAU,CAAA;KAC1D,kBAAC,MAAD;MAAI,WAAU;gBAAmC;MAAY,CAAA;KAC1D,EAAA,CAAA;IACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,MAAD;IAEE,WAAU;cAFZ;KAIE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAE;MACA,CAAA;KACL,kBAAC,MAAD;MAAI,WAAU;gBAAiC,EAAE;MAAY,CAAA;KAC7D,kBAAC,MAAD;MAAI,WAAU;gBAA6B,IAAI,KAAK,EAAE,SAAS,CAAC,gBAAgB;MAAM,CAAA;KACtF,kBAAC,MAAD;MAAI,WAAU;gBACX,IAAI,KAAK,EAAE,UAAU,CAAC,gBAAgB;MACpC,CAAA;KACL,kBAAC,MAAD;MAAI,WAAU;gBAAd;OAA0C;OACrC,EAAE;OAAQ;OAAM,EAAE;OAClB;;KACL,kBAAC,MAAD;MAAI,WAAU;iBACV,EAAE,WAAW,EAAoB,UACjC,EAAE,WAAW,EAAoB,cACjC,kBAAC,UAAD;OACE,MAAK;OACL,eACE,KAAK,EAAO,EACV,WAAW;QAAE,WAAW,EAAE;QAAI,QAAQ;QAAmB,EAC1D,CAAC;OAEJ,WAAU;iBAET,EAAG,wCAAwC,SAAS;OAC9C,CAAA;MAER,CAAA;KACF;MA9BE,EAAE,GA8BJ,CACL,EACI,CAAA,CACF;;EACJ,CAAA"}
1
+ {"version":3,"file":"TunnelSessionsTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelSessionsTab.tsx"],"sourcesContent":["import {\n useListTunnelSessionsQuery,\n useRevokeTunnelSessionMutation,\n} from '@/generated/wspace-operations';\nimport { TunnelSessionStatus } from '@/generated/wspace-types';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { EmptyState } from '@/components/shared/EmptyState';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nexport function TunnelSessionsTab({\n tunnelId,\n canExecute,\n}: {\n tunnelId: string;\n canExecute: boolean;\n}) {\n const tr = useTr();\n const { data, loading, error, refetch } = useListTunnelSessionsQuery({\n variables: { tunnelId, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n });\n const [revoke] = useRevokeTunnelSessionMutation({\n onCompleted: () => void refetch(),\n });\n\n const sessions = data?.tunnelSessions?.items ?? [];\n\n if (loading && !data) return <LoadingState message=\"Loading sessions\" />;\n if (error)\n return (\n <ErrorState\n title=\"Failed to load sessions\"\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n if (sessions.length === 0)\n return (\n <EmptyState\n title={tr('vibecontrols.tunnels.sessions.emptySessionsTitle', 'No sessions')}\n description={tr(\n 'vibecontrols.tunnels.sessions.emptySessionsDescription',\n 'Sessions appear here once a tunnel is started.'\n )}\n />\n );\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface max-w-4xl\">\n <table className=\"w-full text-sm\">\n <thead className=\"border-b border-border-subtle bg-bg-sunken text-text-secondary\">\n <tr>\n <th className=\"px-4 py-3 text-left font-medium\">Fingerprint</th>\n <th className=\"px-4 py-3 text-left font-medium\">Status</th>\n <th className=\"px-4 py-3 text-left font-medium\">Issued</th>\n <th className=\"px-4 py-3 text-left font-medium\">Expires</th>\n <th className=\"px-4 py-3 text-left font-medium\">Bytes</th>\n <th className=\"px-4 py-3 text-right font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {sessions.map((s: any) => (\n <tr\n key={s.id}\n className=\"border-b border-border-subtle last:border-b-0 hover:bg-bg-sunken\"\n >\n <td className=\"px-4 py-3 font-mono text-xs text-text-primary\">\n {s.tokenFingerprint}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">{s.status}</td>\n <td className=\"px-4 py-3 text-text-muted\">{new Date(s.issuedAt).toLocaleString()}</td>\n <td className=\"px-4 py-3 text-text-muted\">\n {new Date(s.expiresAt).toLocaleString()}\n </td>\n <td className=\"px-4 py-3 text-text-muted\">\n ↓ {s.bytesIn} / ↑ {s.bytesOut}\n </td>\n <td className=\"px-4 py-3 text-right\">\n {canExecute &&\n (s.status === TunnelSessionStatus.Issued ||\n s.status === TunnelSessionStatus.Connected) && (\n <button\n type=\"button\"\n onClick={() =>\n void revoke({\n variables: { sessionId: s.id, reason: 'revoked from UI' },\n })\n }\n className=\"rounded-md border border-border-default px-3 py-1 text-xs text-status-error-fg hover:bg-status-error-bg\"\n >\n {tr('vibecontrols.tunnels.sessions.revoke', 'Revoke')}\n </button>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,EAAkB,EAChC,aACA,iBAIC;CACD,IAAM,IAAK,GAAO,EACZ,EAAE,SAAM,YAAS,UAAO,eAAY,EAA2B;EACnE,WAAW;GAAE;GAAU,YAAY,EAAE,OAAO,IAAI;GAAE;EAClD,aAAa;EACd,CAAC,EACI,CAAC,KAAU,EAA+B,EAC9C,mBAAmB,KAAK,GAAS,EAClC,CAAC,EAEI,IAAW,GAAM,gBAAgB,SAAS,EAAE;AAsBlD,QApBI,KAAW,CAAC,IAAa,kBAAC,GAAD,EAAc,SAAQ,oBAAqB,CAAA,GACpE,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,SAAS,EAAM;EACf,eAAe,GAAS;EACxB,CAAA,GAEF,EAAS,WAAW,IAEpB,kBAAC,GAAD;EACE,OAAO,EAAG,oDAAoD,cAAc;EAC5E,aAAa,EACX,0DACA,iDACD;EACD,CAAA,GAIJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,SAAD;GAAO,WAAU;aAAjB,CACE,kBAAC,SAAD;IAAO,WAAU;cACf,kBAAC,MAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAgB,CAAA;KAChE,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAW,CAAA;KAC3D,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAW,CAAA;KAC3D,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAY,CAAA;KAC5D,kBAAC,MAAD;MAAI,WAAU;gBAAkC;MAAU,CAAA;KAC1D,kBAAC,MAAD;MAAI,WAAU;gBAAmC;MAAY,CAAA;KAC1D,EAAA,CAAA;IACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,MAAD;IAEE,WAAU;cAFZ;KAIE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAE;MACA,CAAA;KACL,kBAAC,MAAD;MAAI,WAAU;gBAAiC,EAAE;MAAY,CAAA;KAC7D,kBAAC,MAAD;MAAI,WAAU;gBAA6B,IAAI,KAAK,EAAE,SAAS,CAAC,gBAAgB;MAAM,CAAA;KACtF,kBAAC,MAAD;MAAI,WAAU;gBACX,IAAI,KAAK,EAAE,UAAU,CAAC,gBAAgB;MACpC,CAAA;KACL,kBAAC,MAAD;MAAI,WAAU;gBAAd;OAA0C;OACrC,EAAE;OAAQ;OAAM,EAAE;OAClB;;KACL,kBAAC,MAAD;MAAI,WAAU;gBACX,MACE,EAAE,WAAW,EAAoB,UAChC,EAAE,WAAW,EAAoB,cACjC,kBAAC,UAAD;OACE,MAAK;OACL,eACE,KAAK,EAAO,EACV,WAAW;QAAE,WAAW,EAAE;QAAI,QAAQ;QAAmB,EAC1D,CAAC;OAEJ,WAAU;iBAET,EAAG,wCAAwC,SAAS;OAC9C,CAAA;MAEV,CAAA;KACF;MA/BE,EAAE,GA+BJ,CACL,EACI,CAAA,CACF;;EACJ,CAAA"}
@@ -4,17 +4,17 @@ import { ConfirmDialog as r } from "../../../components/shared/ConfirmDialog.js"
4
4
  import { useState as i } from "react";
5
5
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
6
  //#region src/pages/tunnels/tabs/TunnelSettingsTab.tsx
7
- function s({ tunnel: s, onDeleted: c }) {
8
- let l = n(), [u, d] = i(!1), [f, { loading: p }] = t(), [m, { loading: h }] = e({ onCompleted: c });
7
+ function s({ tunnel: s, canUpdate: c, canDelete: l, onDeleted: u }) {
8
+ let d = n(), [f, p] = i(!1), [m, { loading: h }] = t(), [g, { loading: _ }] = e({ onCompleted: u });
9
9
  return /* @__PURE__ */ o("div", {
10
10
  className: "max-w-3xl space-y-6",
11
11
  children: [
12
- /* @__PURE__ */ o("section", {
12
+ c && /* @__PURE__ */ o("section", {
13
13
  className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
14
14
  children: [
15
15
  /* @__PURE__ */ a("h3", {
16
16
  className: "text-sm font-semibold text-text-primary mb-2",
17
- children: l("vibecontrols.tunnels.settings.rotateCredentials", "Rotate credentials")
17
+ children: d("vibecontrols.tunnels.settings.rotateCredentials", "Rotate credentials")
18
18
  }),
19
19
  /* @__PURE__ */ a("p", {
20
20
  className: "text-xs text-text-secondary mb-3",
@@ -22,19 +22,19 @@ function s({ tunnel: s, onDeleted: c }) {
22
22
  }),
23
23
  /* @__PURE__ */ a("button", {
24
24
  type: "button",
25
- disabled: p,
26
- onClick: () => void f({ variables: { id: s.id } }),
25
+ disabled: h,
26
+ onClick: () => void m({ variables: { id: s.id } }),
27
27
  className: "rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50",
28
- children: p ? l("vibecontrols.tunnels.settings.rotating", "Rotating...") : l("vibecontrols.tunnels.settings.rotateNow", "Rotate now")
28
+ children: h ? d("vibecontrols.tunnels.settings.rotating", "Rotating...") : d("vibecontrols.tunnels.settings.rotateNow", "Rotate now")
29
29
  })
30
30
  ]
31
31
  }),
32
- /* @__PURE__ */ o("section", {
32
+ l && /* @__PURE__ */ o("section", {
33
33
  className: "rounded-lg border border-status-error-bg bg-bg-surface p-4",
34
34
  children: [
35
35
  /* @__PURE__ */ a("h3", {
36
36
  className: "text-sm font-semibold text-status-error-fg mb-2",
37
- children: l("vibecontrols.tunnels.settings.deleteTunnel", "Delete tunnel")
37
+ children: d("vibecontrols.tunnels.settings.deleteTunnel", "Delete tunnel")
38
38
  }),
39
39
  /* @__PURE__ */ a("p", {
40
40
  className: "text-xs text-text-secondary mb-3",
@@ -42,21 +42,25 @@ function s({ tunnel: s, onDeleted: c }) {
42
42
  }),
43
43
  /* @__PURE__ */ a("button", {
44
44
  type: "button",
45
- onClick: () => d(!0),
45
+ onClick: () => p(!0),
46
46
  className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-fg hover:bg-status-error-bg-hover",
47
- children: l("vibecontrols.tunnels.settings.deleteTunnelBtn", "Delete tunnel")
47
+ children: d("vibecontrols.tunnels.settings.deleteTunnelBtn", "Delete tunnel")
48
48
  })
49
49
  ]
50
50
  }),
51
+ !c && !l && /* @__PURE__ */ a("section", {
52
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-text-secondary",
53
+ children: d("vibecontrols.tunnels.settings.noPermission", "No tunnel settings actions available.")
54
+ }),
51
55
  /* @__PURE__ */ a(r, {
52
- isOpen: u,
53
- onClose: () => d(!1),
54
- onConfirm: () => void m({ variables: { id: s.id } }),
56
+ isOpen: f,
57
+ onClose: () => p(!1),
58
+ onConfirm: () => void g({ variables: { id: s.id } }),
55
59
  title: "Delete tunnel",
56
60
  message: `Delete "${s.name}"? This cannot be undone.`,
57
61
  confirmLabel: "Delete",
58
62
  variant: "danger",
59
- isLoading: h
63
+ isLoading: _
60
64
  })
61
65
  ]
62
66
  });
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelSettingsTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelSettingsTab.tsx"],"sourcesContent":["import { useState } from 'react';\nimport type { GetTunnelQuery } from '@/generated/wspace-operations';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n useRotateTunnelCredentialsMutation,\n useDeleteTunnelMutation,\n} from '@/generated/wspace-operations';\nimport { ConfirmDialog } from '@/components/shared/ConfirmDialog';\n\ntype TunnelDetails = NonNullable<GetTunnelQuery['tunnel']>;\n\nexport function TunnelSettingsTab({\n tunnel,\n onDeleted,\n}: {\n tunnel: TunnelDetails;\n onDeleted: () => void;\n}) {\n const tr = useTr();\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const [rotateCredentials, { loading: rotating }] = useRotateTunnelCredentialsMutation();\n const [deleteTunnel, { loading: deleting }] = useDeleteTunnelMutation({\n onCompleted: onDeleted,\n });\n\n return (\n <div className=\"max-w-3xl space-y-6\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-2\">\n {tr('vibecontrols.tunnels.settings.rotateCredentials', 'Rotate credentials')}\n </h3>\n <p className=\"text-xs text-text-secondary mb-3\">\n Rotates basic-auth passwords and revokes every active session for this tunnel. The tunnel\n will restart automatically if currently active.\n </p>\n <button\n type=\"button\"\n disabled={rotating}\n onClick={() => void rotateCredentials({ variables: { id: tunnel.id } })}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50\"\n >\n {rotating\n ? tr('vibecontrols.tunnels.settings.rotating', 'Rotating...')\n : tr('vibecontrols.tunnels.settings.rotateNow', 'Rotate now')}\n </button>\n </section>\n\n <section className=\"rounded-lg border border-status-error-bg bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-status-error-fg mb-2\">\n {tr('vibecontrols.tunnels.settings.deleteTunnel', 'Delete tunnel')}\n </h3>\n <p className=\"text-xs text-text-secondary mb-3\">\n Permanently removes the tunnel, its sessions, and any managed subdomain reservations.\n Custom domains remain in the workspace and can be reattached.\n </p>\n <button\n type=\"button\"\n onClick={() => setConfirmDelete(true)}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-fg hover:bg-status-error-bg-hover\"\n >\n {tr('vibecontrols.tunnels.settings.deleteTunnelBtn', 'Delete tunnel')}\n </button>\n </section>\n\n <ConfirmDialog\n isOpen={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n onConfirm={() => void deleteTunnel({ variables: { id: tunnel.id } })}\n title=\"Delete tunnel\"\n message={`Delete \"${tunnel.name}\"? This cannot be undone.`}\n confirmLabel=\"Delete\"\n variant=\"danger\"\n isLoading={deleting}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;AAWA,SAAgB,EAAkB,EAChC,WACA,gBAIC;CACD,IAAM,IAAK,GAAO,EACZ,CAAC,GAAe,KAAoB,EAAS,GAAM,EAEnD,CAAC,GAAmB,EAAE,SAAS,OAAc,GAAoC,EACjF,CAAC,GAAc,EAAE,SAAS,OAAc,EAAwB,EACpE,aAAa,GACd,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,mDAAmD,qBAAqB;MACzE,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAG5C,CAAA;KACJ,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,eAAe,KAAK,EAAkB,EAAE,WAAW,EAAE,IAAI,EAAO,IAAI,EAAE,CAAC;MACvE,WAAU;gBAET,IACG,EAAG,0CAA0C,cAAc,GAC3D,EAAG,2CAA2C,aAAa;MACxD,CAAA;KACD;;GAEV,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8CAA8C,gBAAgB;MAC/D,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAG5C,CAAA;KACJ,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,GAAK;MACrC,WAAU;gBAET,EAAG,iDAAiD,gBAAgB;MAC9D,CAAA;KACD;;GAEV,kBAAC,GAAD;IACE,QAAQ;IACR,eAAe,EAAiB,GAAM;IACtC,iBAAiB,KAAK,EAAa,EAAE,WAAW,EAAE,IAAI,EAAO,IAAI,EAAE,CAAC;IACpE,OAAM;IACN,SAAS,WAAW,EAAO,KAAK;IAChC,cAAa;IACb,SAAQ;IACR,WAAW;IACX,CAAA;GACE"}
1
+ {"version":3,"file":"TunnelSettingsTab.js","names":[],"sources":["../../../../src/pages/tunnels/tabs/TunnelSettingsTab.tsx"],"sourcesContent":["import { useState } from 'react';\nimport type { GetTunnelQuery } from '@/generated/wspace-operations';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n useRotateTunnelCredentialsMutation,\n useDeleteTunnelMutation,\n} from '@/generated/wspace-operations';\nimport { ConfirmDialog } from '@/components/shared/ConfirmDialog';\n\ntype TunnelDetails = NonNullable<GetTunnelQuery['tunnel']>;\n\nexport function TunnelSettingsTab({\n tunnel,\n canUpdate,\n canDelete,\n onDeleted,\n}: {\n tunnel: TunnelDetails;\n canUpdate: boolean;\n canDelete: boolean;\n onDeleted: () => void;\n}) {\n const tr = useTr();\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const [rotateCredentials, { loading: rotating }] = useRotateTunnelCredentialsMutation();\n const [deleteTunnel, { loading: deleting }] = useDeleteTunnelMutation({\n onCompleted: onDeleted,\n });\n\n return (\n <div className=\"max-w-3xl space-y-6\">\n {canUpdate && (\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary mb-2\">\n {tr('vibecontrols.tunnels.settings.rotateCredentials', 'Rotate credentials')}\n </h3>\n <p className=\"text-xs text-text-secondary mb-3\">\n Rotates basic-auth passwords and revokes every active session for this tunnel. The\n tunnel will restart automatically if currently active.\n </p>\n <button\n type=\"button\"\n disabled={rotating}\n onClick={() => void rotateCredentials({ variables: { id: tunnel.id } })}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50\"\n >\n {rotating\n ? tr('vibecontrols.tunnels.settings.rotating', 'Rotating...')\n : tr('vibecontrols.tunnels.settings.rotateNow', 'Rotate now')}\n </button>\n </section>\n )}\n\n {canDelete && (\n <section className=\"rounded-lg border border-status-error-bg bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-status-error-fg mb-2\">\n {tr('vibecontrols.tunnels.settings.deleteTunnel', 'Delete tunnel')}\n </h3>\n <p className=\"text-xs text-text-secondary mb-3\">\n Permanently removes the tunnel, its sessions, and any managed subdomain reservations.\n Custom domains remain in the workspace and can be reattached.\n </p>\n <button\n type=\"button\"\n onClick={() => setConfirmDelete(true)}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-fg hover:bg-status-error-bg-hover\"\n >\n {tr('vibecontrols.tunnels.settings.deleteTunnelBtn', 'Delete tunnel')}\n </button>\n </section>\n )}\n\n {!canUpdate && !canDelete && (\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.tunnels.settings.noPermission',\n 'No tunnel settings actions available.'\n )}\n </section>\n )}\n\n <ConfirmDialog\n isOpen={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n onConfirm={() => void deleteTunnel({ variables: { id: tunnel.id } })}\n title=\"Delete tunnel\"\n message={`Delete \"${tunnel.name}\"? This cannot be undone.`}\n confirmLabel=\"Delete\"\n variant=\"danger\"\n isLoading={deleting}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;AAWA,SAAgB,EAAkB,EAChC,WACA,cACA,cACA,gBAMC;CACD,IAAM,IAAK,GAAO,EACZ,CAAC,GAAe,KAAoB,EAAS,GAAM,EAEnD,CAAC,GAAmB,EAAE,SAAS,OAAc,GAAoC,EACjF,CAAC,GAAc,EAAE,SAAS,OAAc,EAAwB,EACpE,aAAa,GACd,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,mDAAmD,qBAAqB;MACzE,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAG5C,CAAA;KACJ,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,eAAe,KAAK,EAAkB,EAAE,WAAW,EAAE,IAAI,EAAO,IAAI,EAAE,CAAC;MACvE,WAAU;gBAET,IACG,EAAG,0CAA0C,cAAc,GAC3D,EAAG,2CAA2C,aAAa;MACxD,CAAA;KACD;;GAGX,KACC,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8CAA8C,gBAAgB;MAC/D,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAG5C,CAAA;KACJ,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,GAAK;MACrC,WAAU;gBAET,EAAG,iDAAiD,gBAAgB;MAC9D,CAAA;KACD;;GAGX,CAAC,KAAa,CAAC,KACd,kBAAC,WAAD;IAAS,WAAU;cAChB,EACC,8CACA,wCACD;IACO,CAAA;GAGZ,kBAAC,GAAD;IACE,QAAQ;IACR,eAAe,EAAiB,GAAM;IACtC,iBAAiB,KAAK,EAAa,EAAE,WAAW,EAAE,IAAI,EAAO,IAAI,EAAE,CAAC;IACpE,OAAM;IACN,SAAS,WAAW,EAAO,KAAK;IAChC,cAAa;IACb,SAAQ;IACR,WAAW;IACX,CAAA;GACE"}
@@ -6,11 +6,12 @@ function p(e) {
6
6
  switch (e) {
7
7
  case "general": return l;
8
8
  case "vibe-plugins": return u;
9
+ case "assistant":
9
10
  case "api-calls": return d;
10
11
  }
11
12
  }
12
13
  function m(e) {
13
- return `${c}/wspace/ai-assistant-${e}:${e === "api-calls" ? d : e === "general" ? l : u}`;
14
+ return `${c}/wspace/ai-assistant-${e === "assistant" ? "api-calls" : e}:${p(e)}`;
14
15
  }
15
16
  function h(e) {
16
17
  switch (e) {
@@ -26,6 +27,7 @@ function h(e) {
26
27
  memoryRequest: "384Mi",
27
28
  memoryLimit: "1536Mi"
28
29
  };
30
+ case "assistant":
29
31
  case "api-calls": return {
30
32
  cpuRequest: "150m",
31
33
  cpuLimit: "750m",
@@ -36,9 +38,9 @@ function h(e) {
36
38
  }
37
39
  function g(e) {
38
40
  let t = e.metadata?.assistantMode;
39
- if (t === "general" || t === "api-calls" || t === "vibe-plugins") return t;
41
+ if (t === "assistant" || t === "general" || t === "api-calls" || t === "vibe-plugins") return t;
40
42
  let n = e.name ?? "";
41
- return n.startsWith("ai-assistant-vibe-plugins-") ? "vibe-plugins" : n.startsWith("ai-assistant-api-calls-") ? "api-calls" : n.startsWith("ai-assistant-general-") ? "general" : null;
43
+ return n.startsWith("ai-assistant-vibe-plugins-") ? "vibe-plugins" : n.startsWith("ai-assistant-api-calls-") ? "api-calls" : n.startsWith("ai-assistant-general-") ? "general" : n.startsWith("ai-assistant-assistant-") ? "assistant" : null;
42
44
  }
43
45
  function _(e, t) {
44
46
  let n = {};
@@ -92,6 +94,10 @@ async function x(e, n, r, i) {
92
94
  {
93
95
  name: "WORKSPACE_ID",
94
96
  value: r
97
+ },
98
+ {
99
+ name: "AI_ASSISTANT_PRODUCT",
100
+ value: "vibecontrols"
95
101
  }
96
102
  ], s = await y({
97
103
  document: t,
@@ -1 +1 @@
1
- {"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API service.\n *\n * All requests go through the wspace-public-gateway (GraphQL Federation):\n * Frontend → wspace-public-gateway → wspace-sandbox-svc → ai-assistant (K8s pod)\n *\n * Uses the host app's Apollo Client which already points to the gateway.\n */\n\nimport { type ApolloClient } from '@apollo/client';\nimport { print, type DocumentNode } from 'graphql';\nimport type { AssistantArtifact, AssistantMode } from '@/types/assistant';\nimport type { PageContext } from '@/utils/pageContext';\nimport {\n CreateSandboxDocument,\n GetSandboxDocument,\n GetActiveSandboxesDocument,\n ProxySandboxRequestDocument,\n ExtendSandboxTtlDocument,\n CheckAiCreditsDocument,\n ReportAiTokenUsageDocument,\n} from '@/generated/wspace-operations';\n\n// ── Image registry ─────────────────────────────────────────────────\n\nconst HARBOR_REGISTRY = 'burdenoffregistry.azurecr.io';\nconst DEFAULT_IMAGE_TAG = import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha';\nconst GENERAL_IMAGE_TAG = import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-proxy-auth-v1';\nconst VIBE_PLUGINS_IMAGE_TAG =\n import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-proxy-auth-v1';\nconst API_CALLS_IMAGE_TAG =\n import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-delegated-auth-v11';\nconst ASSISTANT_API_AUTH_STRATEGY = 'sandbox-app-client-headers-v1';\n\nfunction getImageTagForMode(mode: AssistantMode): string {\n switch (mode) {\n case 'general':\n return GENERAL_IMAGE_TAG;\n case 'vibe-plugins':\n return VIBE_PLUGINS_IMAGE_TAG;\n case 'api-calls':\n return API_CALLS_IMAGE_TAG;\n }\n}\n\nfunction getImageForMode(mode: AssistantMode): string {\n const imageTag =\n mode === 'api-calls'\n ? API_CALLS_IMAGE_TAG\n : mode === 'general'\n ? GENERAL_IMAGE_TAG\n : VIBE_PLUGINS_IMAGE_TAG;\n return `${HARBOR_REGISTRY}/wspace/ai-assistant-${mode}:${imageTag}`;\n}\n\nfunction getResourcesForMode(mode: AssistantMode): {\n cpuRequest: string;\n cpuLimit: string;\n memoryRequest: string;\n memoryLimit: string;\n} {\n switch (mode) {\n case 'general':\n return {\n cpuRequest: '100m',\n cpuLimit: '500m',\n memoryRequest: '256Mi',\n memoryLimit: '1Gi',\n };\n case 'vibe-plugins':\n return {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n };\n case 'api-calls':\n return {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n };\n }\n}\n\nfunction resolveSandboxAssistantMode(sandbox: {\n metadata: Record<string, unknown> | null;\n name?: string | null;\n}): AssistantMode | null {\n const metadataMode = sandbox.metadata?.assistantMode;\n if (\n metadataMode === 'general' ||\n metadataMode === 'api-calls' ||\n metadataMode === 'vibe-plugins'\n ) {\n return metadataMode;\n }\n\n const name = sandbox.name ?? '';\n if (name.startsWith('ai-assistant-vibe-plugins-')) return 'vibe-plugins';\n if (name.startsWith('ai-assistant-api-calls-')) return 'api-calls';\n if (name.startsWith('ai-assistant-general-')) return 'general';\n return null;\n}\n\n// ── GraphQL operations (colocated .graphql → generated DocumentNodes) ──\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface AssistantPromptResult {\n response: string;\n tokens?: { input: number; output: number };\n artifacts?: AssistantArtifact[];\n}\n\nexport interface AssistantRawMessagePart {\n type?: string;\n text?: string;\n tool?: string;\n state?: {\n status?: string;\n input?: unknown;\n output?: unknown;\n raw?: unknown;\n };\n}\n\nexport interface AssistantRawMessage {\n info?: {\n id?: string;\n role?: string;\n providerID?: string;\n modelID?: string;\n tokens?: { input: number; output: number };\n time?: {\n created?: number;\n completed?: number;\n };\n };\n parts?: AssistantRawMessagePart[];\n}\n\nexport interface AssistantSandboxAuthContext {\n accessToken?: string | null;\n workspaceToken?: string | null;\n userId?: string | null;\n organizationId?: string | null;\n}\n\nexport interface AssistantSessionInfo {\n id: string;\n mode: string;\n}\n\ninterface GraphQLResponse<TData> {\n data?: TData;\n errors?: Array<{ message: string }>;\n}\n\nexport interface DownloadedAssistantArtifact {\n filename: string;\n mimeType: string;\n contentBase64: string;\n}\n\ninterface ProxyResponse {\n proxySandboxRequest: {\n status: number;\n body: Record<string, unknown>;\n };\n}\n\nfunction getGatewayRequestContext(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): { headers?: Record<string, string> } | undefined {\n const headers: Record<string, string> = {};\n\n if (authContext?.accessToken) {\n headers.authorization = `Bearer ${authContext.accessToken}`;\n }\n\n if (authContext?.workspaceToken) {\n headers['x-workspace-authorization'] = `Bearer ${authContext.workspaceToken}`;\n }\n\n if (workspaceId) {\n headers['x-workspace-id'] = workspaceId;\n }\n\n if (authContext?.userId) {\n headers['x-actor-id'] = authContext.userId;\n headers['x-actor-type'] = 'user';\n }\n\n if (authContext?.organizationId) {\n headers['x-org-id'] = authContext.organizationId;\n }\n\n return Object.keys(headers).length > 0 ? { headers } : undefined;\n}\n\nfunction buildSandboxContextInput(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Record<string, unknown> {\n return {\n workspaceId,\n ...(authContext?.organizationId\n ? {\n custom: {\n organizationId: authContext.organizationId,\n },\n }\n : {}),\n };\n}\n\nasync function executeSandboxGraphQL<TData>(params: {\n document: DocumentNode;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const response = await fetch('/workspaces/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(getGatewayRequestContext(params.workspaceId, params.authContext)?.headers ?? {}),\n },\n body: JSON.stringify({\n query: print(params.document),\n variables: params.variables,\n }),\n });\n\n const result = (await response.json().catch(() => null)) as GraphQLResponse<TData> | null;\n\n if (!response.ok) {\n // Preserve the HTTP status and parsed body so quota detection works.\n // The wspace public gateway returns 429 + errors[].extensions.code =\n // QUOTA_EXHAUSTED for @usage limits (createSandbox, checkAiCredits, …);\n // a bare `HTTP 429` Error would be invisible to isQuotaExhaustedError.\n const messages = result?.errors?.map((error: { message: string }) => error.message).join(', ');\n const httpError = new Error(messages || `Sandbox GraphQL HTTP ${response.status}`) as Error & {\n statusCode?: number;\n result?: unknown;\n };\n httpError.statusCode = response.status;\n httpError.result = result ?? undefined;\n throw httpError;\n }\n\n if (!result) {\n throw new Error('Sandbox GraphQL returned empty response');\n }\n\n if (result.errors?.length) {\n throw new Error(result.errors.map((error: { message: string }) => error.message).join(', '));\n }\n\n if (!result.data) {\n throw new Error('Sandbox GraphQL returned no data');\n }\n\n return result.data;\n}\n\n// ── Find existing sandbox for a mode ───────────────────────────────\n\nexport async function findExistingSandbox(\n _apollo: ApolloClient,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<string | null> {\n const data = await executeSandboxGraphQL<{\n getActiveSandboxes: Array<{\n id: string;\n name: string;\n status: string;\n metadata: Record<string, unknown> | null;\n template: string;\n createdBy: string;\n }>;\n }>({\n document: GetActiveSandboxesDocument,\n variables: { context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const sandboxes = data.getActiveSandboxes ?? [];\n\n return (\n sandboxes.find(\n (s) =>\n (s.status === 'RUNNING' || s.status === 'PROVISIONING' || s.status === 'PENDING') &&\n s.template === 'AI_BUILDER' &&\n resolveSandboxAssistantMode(s) === mode &&\n (!authContext?.userId || s.createdBy === authContext.userId) &&\n s.metadata?.authStrategy === ASSISTANT_API_AUTH_STRATEGY &&\n s.metadata?.imageTag === getImageTagForMode(mode)\n )?.id ?? null\n );\n}\n\n// ── Sandbox lifecycle ──────────────────────────────────────────────\n\nexport async function createAssistantSandbox(\n _apollo: ApolloClient,\n mode: AssistantMode,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<string> {\n const resources = getResourcesForMode(mode);\n const env = [\n { name: 'AI_ASSISTANT_MODE', value: mode },\n { name: 'HOSTNAME', value: '0.0.0.0' },\n { name: 'WORKSPACE_ID', value: workspaceId },\n ];\n\n const data = await executeSandboxGraphQL<{ createSandbox: { id: string } }>({\n document: CreateSandboxDocument,\n variables: {\n input: {\n name: `ai-assistant-${mode}-${Date.now()}`,\n description: `AI Assistant sandbox (${mode} mode)`,\n template: 'AI_BUILDER',\n mode: 'DEPLOYMENT',\n containers: [\n {\n name: 'ai-assistant',\n image: getImageForMode(mode),\n ports: [{ containerPort: 80, name: 'http' }],\n env,\n },\n ],\n resources: {\n cpuRequest: resources.cpuRequest,\n cpuLimit: resources.cpuLimit,\n memoryRequest: resources.memoryRequest,\n memoryLimit: resources.memoryLimit,\n },\n ttlSeconds: 600,\n metadata: {\n assistantMode: mode,\n assistantOwnerUserId: authContext?.userId ?? null,\n assistantOrganizationId: authContext?.organizationId ?? null,\n authStrategy: ASSISTANT_API_AUTH_STRATEGY,\n imageTag: getImageTagForMode(mode),\n },\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n if (!data?.createSandbox?.id) {\n throw new Error('createSandbox returned empty response');\n }\n return data.createSandbox.id;\n}\n\nexport async function waitForSandboxReady(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 300_000,\n onProgress?: (message: string) => void\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < maxWaitMs) {\n const data = await executeSandboxGraphQL<{\n getSandbox: { status?: string; errorMessage?: string } | null;\n }>({\n document: GetSandboxDocument,\n variables: { id: sandboxId, context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const status = data?.getSandbox?.status;\n const errorMessage = data?.getSandbox?.errorMessage as string | undefined;\n if (status === 'RUNNING') return;\n if (status === 'FAILED' || status === 'STOPPED' || status === 'EXPIRED') {\n throw new Error(\n errorMessage\n ? `Sandbox ${status.toLowerCase()}: ${errorMessage}`\n : `Sandbox ${status.toLowerCase()}`\n );\n }\n const elapsedSeconds = Math.max(1, Math.round((Date.now() - start) / 1000));\n onProgress?.(`Starting assistant environment... (${elapsedSeconds}s)`);\n await new Promise((r) => setTimeout(r, 2000));\n }\n throw new Error('Sandbox startup timed out');\n}\n\n// ── Proxy calls to ai-assistant (via gateway → sandbox-svc) ───────\n\nasync function proxyToSandbox(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n path: string,\n method: string,\n body?: unknown,\n authContext?: AssistantSandboxAuthContext\n): Promise<Record<string, unknown>> {\n const result = await executeSandboxGraphQL<ProxyResponse>({\n document: ProxySandboxRequestDocument,\n variables: {\n input: {\n sandboxId,\n path,\n method,\n body,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n const proxyResult = result.proxySandboxRequest;\n if (!proxyResult || proxyResult.status >= 400) {\n const errBody = proxyResult?.body as Record<string, unknown> | undefined;\n throw new Error(\n (errBody?.error as string) ?? `Proxy error: ${proxyResult?.status ?? 'unknown'}`\n );\n }\n\n return proxyResult.body;\n}\n\nexport async function waitForAssistantServiceReady(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 90_000,\n onProgress?: (message: string) => void\n): Promise<void> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < maxWaitMs) {\n try {\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n '/health',\n 'GET',\n undefined,\n authContext\n );\n return;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (\n !/proxy error: 502|proxy error: 503|proxy error: 404|service not available/i.test(message)\n ) {\n throw error;\n }\n }\n\n const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));\n onProgress?.(`Connecting to assistant... (${elapsedSeconds}s)`);\n\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n throw new Error('Assistant service did not become healthy before timeout');\n}\n\n// ── Session management ─────────────────────────────────────────────\n\nexport async function createAssistantSession(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<{ sessionId: string }> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n '/sessions',\n 'POST',\n { mode },\n authContext\n );\n const session = body.session as Record<string, unknown>;\n return { sessionId: session.id as string };\n}\n\nexport async function getAssistantSession(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantSessionInfo> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}`,\n 'GET',\n undefined,\n authContext\n );\n const session = body.session as Record<string, unknown> | undefined;\n\n return {\n id: (session?.id as string | undefined) ?? sessionId,\n mode: (body.mode as string | undefined) ?? 'unknown',\n };\n}\n\n// ── Send prompt ────────────────────────────────────────────────────\n\nexport async function sendAssistantPrompt(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantPromptResult> {\n const requestBody = {\n mode,\n prompt,\n pageContext: pageContext\n ? {\n activeUrl: pageContext.activeUrl,\n currentPage: pageContext.currentPage,\n platform: pageContext.platform,\n version: pageContext.version,\n viewportSize: pageContext.viewportSize,\n }\n : undefined,\n };\n\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt`,\n 'POST',\n requestBody,\n authContext\n );\n\n const message = body.message as Record<string, unknown> | undefined;\n const tokens = message?.tokens as { input: number; output: number } | undefined;\n\n return { response: body.text as string, tokens };\n}\n\nexport async function sendAssistantPromptAsync(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n const requestBody = {\n mode,\n prompt,\n pageContext: pageContext ?? undefined,\n };\n\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt-async`,\n 'POST',\n requestBody,\n authContext\n );\n}\n\nexport async function getAssistantMessages(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext,\n limit = 50\n): Promise<AssistantRawMessage[]> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/messages?limit=${limit}`,\n 'GET',\n undefined,\n authContext\n );\n\n return (body.messages as AssistantRawMessage[] | undefined) ?? [];\n}\n\n// ── AI credits (per-token metering) ────────────────────────────────\n\n/**\n * Pre-flight AI-credit gate. Resolves to `true` when the caller still has\n * AI-credit budget; throws a quota error (HTTP 429 / QUOTA_EXHAUSTED, from\n * the wspace gateway's @usage check) when the monthly token budget is\n * depleted. Detect the rejection with `isQuotaExhaustedError()`.\n */\nexport async function checkAssistantAiCredits(\n _apollo: ApolloClient,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<boolean> {\n const data = await executeSandboxGraphQL<{ checkAiCredits: boolean }>({\n document: CheckAiCreditsDocument,\n variables: {},\n workspaceId,\n authContext,\n });\n return data.checkAiCredits;\n}\n\n/**\n * Post-turn charge. Reports the turn's raw input/output token counts + the\n * model so the svc computes COST-WEIGHTED credits server-side and charges them\n * against `vibecontrols.ai.credits` (the amount is never sent from the client).\n * `messageId` makes the charge idempotent (retries don't double-charge).\n * No-ops when there are no tokens, or when the model is unknown (the charge\n * requires it). Best-effort — callers must not block the displayed answer on it.\n */\nexport async function reportAssistantAiTokenUsage(\n _apollo: ApolloClient,\n workspaceId: string,\n sessionId: string,\n messageId: string,\n model: string | undefined,\n tokens: { input: number; output: number },\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n const inputTokens = Math.max(0, Math.round(tokens.input ?? 0));\n const outputTokens = Math.max(0, Math.round(tokens.output ?? 0));\n if (inputTokens + outputTokens === 0) return;\n // The svc requires a model to price the charge; skip rather than send a turn\n // that can't be priced (the meter under-counts that turn — acceptable).\n if (!model) return;\n\n await executeSandboxGraphQL<{ reportAiTokenUsage: boolean }>({\n document: ReportAiTokenUsageDocument,\n variables: {\n input: { sessionId, messageId, model, inputTokens, outputTokens },\n },\n workspaceId,\n authContext,\n });\n}\n\nexport async function getAssistantArtifacts(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantArtifact[]> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/artifacts`,\n 'GET',\n undefined,\n authContext\n );\n\n return ((body.artifacts as AssistantArtifact[] | undefined) ?? []).filter(\n (artifact) => !artifact.name.startsWith('.')\n );\n}\n\nexport async function downloadAssistantArtifact(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n artifactPath: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<DownloadedAssistantArtifact> {\n const encodedPath = encodeURIComponent(artifactPath);\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/artifacts/download?path=${encodedPath}`,\n 'GET',\n undefined,\n authContext\n );\n\n const artifact = body.artifact as Record<string, unknown> | undefined;\n if (!artifact) {\n throw new Error('Artifact download returned an empty response');\n }\n\n return {\n filename: artifact.filename as string,\n mimeType: artifact.mimeType as string,\n contentBase64: artifact.contentBase64 as string,\n };\n}\n\n// ── Heartbeat ───────────────────────────────────────────────────────\n\nexport async function sendHeartbeat(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/heartbeat`,\n 'POST',\n undefined,\n authContext\n );\n}\n\nexport async function extendAssistantSandboxTTL(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n additionalSeconds = 600,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await executeSandboxGraphQL<{ extendSandboxTTL: { id: string } }>({\n document: ExtendSandboxTtlDocument,\n variables: {\n id: sandboxId,\n additionalSeconds,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n workspaceId,\n authContext,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"assistantApi.js","names":[],"sources":["../../src/services/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API service.\n *\n * All requests go through the wspace-public-gateway (GraphQL Federation):\n * Frontend → wspace-public-gateway → wspace-sandbox-svc → ai-assistant (K8s pod)\n *\n * Uses the host app's Apollo Client which already points to the gateway.\n */\n\nimport { type ApolloClient } from '@apollo/client';\nimport { print, type DocumentNode } from 'graphql';\nimport type { AssistantArtifact, AssistantMode } from '@/types/assistant';\nimport type { PageContext } from '@/utils/pageContext';\nimport {\n CreateSandboxDocument,\n GetSandboxDocument,\n GetActiveSandboxesDocument,\n ProxySandboxRequestDocument,\n ExtendSandboxTtlDocument,\n CheckAiCreditsDocument,\n ReportAiTokenUsageDocument,\n} from '@/generated/wspace-operations';\n\n// ── Image registry ─────────────────────────────────────────────────\n\nconst HARBOR_REGISTRY = 'burdenoffregistry.azurecr.io';\nconst DEFAULT_IMAGE_TAG = import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha';\nconst GENERAL_IMAGE_TAG = import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-proxy-auth-v1';\nconst VIBE_PLUGINS_IMAGE_TAG =\n import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-proxy-auth-v1';\nconst API_CALLS_IMAGE_TAG =\n import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-delegated-auth-v11';\nconst ASSISTANT_API_AUTH_STRATEGY = 'sandbox-app-client-headers-v1';\n\nfunction getImageTagForMode(mode: AssistantMode): string {\n switch (mode) {\n case 'general':\n return GENERAL_IMAGE_TAG;\n case 'vibe-plugins':\n return VIBE_PLUGINS_IMAGE_TAG;\n // The combined default mode runs in the api-calls image (it can make calls).\n case 'assistant':\n case 'api-calls':\n return API_CALLS_IMAGE_TAG;\n }\n}\n\nfunction getImageForMode(mode: AssistantMode): string {\n // `assistant` reuses the api-calls image — one image, mode selected at runtime\n // via AI_ASSISTANT_MODE. So the image segment is api-calls for both.\n const imageMode = mode === 'assistant' ? 'api-calls' : mode;\n return `${HARBOR_REGISTRY}/wspace/ai-assistant-${imageMode}:${getImageTagForMode(mode)}`;\n}\n\nfunction getResourcesForMode(mode: AssistantMode): {\n cpuRequest: string;\n cpuLimit: string;\n memoryRequest: string;\n memoryLimit: string;\n} {\n switch (mode) {\n case 'general':\n return {\n cpuRequest: '100m',\n cpuLimit: '500m',\n memoryRequest: '256Mi',\n memoryLimit: '1Gi',\n };\n case 'vibe-plugins':\n return {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n };\n case 'assistant':\n case 'api-calls':\n return {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n };\n }\n}\n\nfunction resolveSandboxAssistantMode(sandbox: {\n metadata: Record<string, unknown> | null;\n name?: string | null;\n}): AssistantMode | null {\n const metadataMode = sandbox.metadata?.assistantMode;\n if (\n metadataMode === 'assistant' ||\n metadataMode === 'general' ||\n metadataMode === 'api-calls' ||\n metadataMode === 'vibe-plugins'\n ) {\n return metadataMode;\n }\n\n const name = sandbox.name ?? '';\n if (name.startsWith('ai-assistant-vibe-plugins-')) return 'vibe-plugins';\n if (name.startsWith('ai-assistant-api-calls-')) return 'api-calls';\n if (name.startsWith('ai-assistant-general-')) return 'general';\n if (name.startsWith('ai-assistant-assistant-')) return 'assistant';\n return null;\n}\n\n// ── GraphQL operations (colocated .graphql → generated DocumentNodes) ──\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface AssistantPromptResult {\n response: string;\n tokens?: { input: number; output: number };\n artifacts?: AssistantArtifact[];\n}\n\nexport interface AssistantRawMessagePart {\n type?: string;\n text?: string;\n tool?: string;\n state?: {\n status?: string;\n input?: unknown;\n output?: unknown;\n raw?: unknown;\n };\n}\n\nexport interface AssistantRawMessage {\n info?: {\n id?: string;\n role?: string;\n providerID?: string;\n modelID?: string;\n tokens?: { input: number; output: number };\n time?: {\n created?: number;\n completed?: number;\n };\n };\n parts?: AssistantRawMessagePart[];\n}\n\nexport interface AssistantSandboxAuthContext {\n accessToken?: string | null;\n workspaceToken?: string | null;\n userId?: string | null;\n organizationId?: string | null;\n}\n\nexport interface AssistantSessionInfo {\n id: string;\n mode: string;\n}\n\ninterface GraphQLResponse<TData> {\n data?: TData;\n errors?: Array<{ message: string }>;\n}\n\nexport interface DownloadedAssistantArtifact {\n filename: string;\n mimeType: string;\n contentBase64: string;\n}\n\ninterface ProxyResponse {\n proxySandboxRequest: {\n status: number;\n body: Record<string, unknown>;\n };\n}\n\nfunction getGatewayRequestContext(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): { headers?: Record<string, string> } | undefined {\n const headers: Record<string, string> = {};\n\n if (authContext?.accessToken) {\n headers.authorization = `Bearer ${authContext.accessToken}`;\n }\n\n if (authContext?.workspaceToken) {\n headers['x-workspace-authorization'] = `Bearer ${authContext.workspaceToken}`;\n }\n\n if (workspaceId) {\n headers['x-workspace-id'] = workspaceId;\n }\n\n if (authContext?.userId) {\n headers['x-actor-id'] = authContext.userId;\n headers['x-actor-type'] = 'user';\n }\n\n if (authContext?.organizationId) {\n headers['x-org-id'] = authContext.organizationId;\n }\n\n return Object.keys(headers).length > 0 ? { headers } : undefined;\n}\n\nfunction buildSandboxContextInput(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Record<string, unknown> {\n return {\n workspaceId,\n ...(authContext?.organizationId\n ? {\n custom: {\n organizationId: authContext.organizationId,\n },\n }\n : {}),\n };\n}\n\nasync function executeSandboxGraphQL<TData>(params: {\n document: DocumentNode;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const response = await fetch('/workspaces/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(getGatewayRequestContext(params.workspaceId, params.authContext)?.headers ?? {}),\n },\n body: JSON.stringify({\n query: print(params.document),\n variables: params.variables,\n }),\n });\n\n const result = (await response.json().catch(() => null)) as GraphQLResponse<TData> | null;\n\n if (!response.ok) {\n // Preserve the HTTP status and parsed body so quota detection works.\n // The wspace public gateway returns 429 + errors[].extensions.code =\n // QUOTA_EXHAUSTED for @usage limits (createSandbox, checkAiCredits, …);\n // a bare `HTTP 429` Error would be invisible to isQuotaExhaustedError.\n const messages = result?.errors?.map((error: { message: string }) => error.message).join(', ');\n const httpError = new Error(messages || `Sandbox GraphQL HTTP ${response.status}`) as Error & {\n statusCode?: number;\n result?: unknown;\n };\n httpError.statusCode = response.status;\n httpError.result = result ?? undefined;\n throw httpError;\n }\n\n if (!result) {\n throw new Error('Sandbox GraphQL returned empty response');\n }\n\n if (result.errors?.length) {\n throw new Error(result.errors.map((error: { message: string }) => error.message).join(', '));\n }\n\n if (!result.data) {\n throw new Error('Sandbox GraphQL returned no data');\n }\n\n return result.data;\n}\n\n// ── Find existing sandbox for a mode ───────────────────────────────\n\nexport async function findExistingSandbox(\n _apollo: ApolloClient,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<string | null> {\n const data = await executeSandboxGraphQL<{\n getActiveSandboxes: Array<{\n id: string;\n name: string;\n status: string;\n metadata: Record<string, unknown> | null;\n template: string;\n createdBy: string;\n }>;\n }>({\n document: GetActiveSandboxesDocument,\n variables: { context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const sandboxes = data.getActiveSandboxes ?? [];\n\n return (\n sandboxes.find(\n (s) =>\n (s.status === 'RUNNING' || s.status === 'PROVISIONING' || s.status === 'PENDING') &&\n s.template === 'AI_BUILDER' &&\n resolveSandboxAssistantMode(s) === mode &&\n (!authContext?.userId || s.createdBy === authContext.userId) &&\n s.metadata?.authStrategy === ASSISTANT_API_AUTH_STRATEGY &&\n s.metadata?.imageTag === getImageTagForMode(mode)\n )?.id ?? null\n );\n}\n\n// ── Sandbox lifecycle ──────────────────────────────────────────────\n\nexport async function createAssistantSandbox(\n _apollo: ApolloClient,\n mode: AssistantMode,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<string> {\n const resources = getResourcesForMode(mode);\n const env = [\n { name: 'AI_ASSISTANT_MODE', value: mode },\n { name: 'HOSTNAME', value: '0.0.0.0' },\n { name: 'WORKSPACE_ID', value: workspaceId },\n // Product the user is in — drives the agent's dynamic per-product docs fetch\n // (the docs repo is `<product>-docs`). Not a secret. This package is the\n // VibeControls MFE, so the product is vibecontrols; other products' MFEs\n // pass their own id.\n { name: 'AI_ASSISTANT_PRODUCT', value: 'vibecontrols' },\n ];\n\n const data = await executeSandboxGraphQL<{ createSandbox: { id: string } }>({\n document: CreateSandboxDocument,\n variables: {\n input: {\n name: `ai-assistant-${mode}-${Date.now()}`,\n description: `AI Assistant sandbox (${mode} mode)`,\n template: 'AI_BUILDER',\n mode: 'DEPLOYMENT',\n containers: [\n {\n name: 'ai-assistant',\n image: getImageForMode(mode),\n ports: [{ containerPort: 80, name: 'http' }],\n env,\n },\n ],\n resources: {\n cpuRequest: resources.cpuRequest,\n cpuLimit: resources.cpuLimit,\n memoryRequest: resources.memoryRequest,\n memoryLimit: resources.memoryLimit,\n },\n ttlSeconds: 600,\n metadata: {\n assistantMode: mode,\n assistantOwnerUserId: authContext?.userId ?? null,\n assistantOrganizationId: authContext?.organizationId ?? null,\n authStrategy: ASSISTANT_API_AUTH_STRATEGY,\n imageTag: getImageTagForMode(mode),\n },\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n if (!data?.createSandbox?.id) {\n throw new Error('createSandbox returned empty response');\n }\n return data.createSandbox.id;\n}\n\nexport async function waitForSandboxReady(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 300_000,\n onProgress?: (message: string) => void\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < maxWaitMs) {\n const data = await executeSandboxGraphQL<{\n getSandbox: { status?: string; errorMessage?: string } | null;\n }>({\n document: GetSandboxDocument,\n variables: { id: sandboxId, context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const status = data?.getSandbox?.status;\n const errorMessage = data?.getSandbox?.errorMessage as string | undefined;\n if (status === 'RUNNING') return;\n if (status === 'FAILED' || status === 'STOPPED' || status === 'EXPIRED') {\n throw new Error(\n errorMessage\n ? `Sandbox ${status.toLowerCase()}: ${errorMessage}`\n : `Sandbox ${status.toLowerCase()}`\n );\n }\n const elapsedSeconds = Math.max(1, Math.round((Date.now() - start) / 1000));\n onProgress?.(`Starting assistant environment... (${elapsedSeconds}s)`);\n await new Promise((r) => setTimeout(r, 2000));\n }\n throw new Error('Sandbox startup timed out');\n}\n\n// ── Proxy calls to ai-assistant (via gateway → sandbox-svc) ───────\n\nasync function proxyToSandbox(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n path: string,\n method: string,\n body?: unknown,\n authContext?: AssistantSandboxAuthContext\n): Promise<Record<string, unknown>> {\n const result = await executeSandboxGraphQL<ProxyResponse>({\n document: ProxySandboxRequestDocument,\n variables: {\n input: {\n sandboxId,\n path,\n method,\n body,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n const proxyResult = result.proxySandboxRequest;\n if (!proxyResult || proxyResult.status >= 400) {\n const errBody = proxyResult?.body as Record<string, unknown> | undefined;\n throw new Error(\n (errBody?.error as string) ?? `Proxy error: ${proxyResult?.status ?? 'unknown'}`\n );\n }\n\n return proxyResult.body;\n}\n\nexport async function waitForAssistantServiceReady(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 90_000,\n onProgress?: (message: string) => void\n): Promise<void> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < maxWaitMs) {\n try {\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n '/health',\n 'GET',\n undefined,\n authContext\n );\n return;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (\n !/proxy error: 502|proxy error: 503|proxy error: 404|service not available/i.test(message)\n ) {\n throw error;\n }\n }\n\n const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));\n onProgress?.(`Connecting to assistant... (${elapsedSeconds}s)`);\n\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n throw new Error('Assistant service did not become healthy before timeout');\n}\n\n// ── Session management ─────────────────────────────────────────────\n\nexport async function createAssistantSession(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<{ sessionId: string }> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n '/sessions',\n 'POST',\n { mode },\n authContext\n );\n const session = body.session as Record<string, unknown>;\n return { sessionId: session.id as string };\n}\n\nexport async function getAssistantSession(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantSessionInfo> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}`,\n 'GET',\n undefined,\n authContext\n );\n const session = body.session as Record<string, unknown> | undefined;\n\n return {\n id: (session?.id as string | undefined) ?? sessionId,\n mode: (body.mode as string | undefined) ?? 'unknown',\n };\n}\n\n// ── Send prompt ────────────────────────────────────────────────────\n\nexport async function sendAssistantPrompt(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantPromptResult> {\n const requestBody = {\n mode,\n prompt,\n pageContext: pageContext\n ? {\n activeUrl: pageContext.activeUrl,\n currentPage: pageContext.currentPage,\n platform: pageContext.platform,\n version: pageContext.version,\n viewportSize: pageContext.viewportSize,\n }\n : undefined,\n };\n\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt`,\n 'POST',\n requestBody,\n authContext\n );\n\n const message = body.message as Record<string, unknown> | undefined;\n const tokens = message?.tokens as { input: number; output: number } | undefined;\n\n return { response: body.text as string, tokens };\n}\n\nexport async function sendAssistantPromptAsync(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n mode: AssistantMode,\n pageContext?: PageContext,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n const requestBody = {\n mode,\n prompt,\n pageContext: pageContext ?? undefined,\n };\n\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt-async`,\n 'POST',\n requestBody,\n authContext\n );\n}\n\nexport async function getAssistantMessages(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext,\n limit = 50\n): Promise<AssistantRawMessage[]> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/messages?limit=${limit}`,\n 'GET',\n undefined,\n authContext\n );\n\n return (body.messages as AssistantRawMessage[] | undefined) ?? [];\n}\n\n// ── AI credits (per-token metering) ────────────────────────────────\n\n/**\n * Pre-flight AI-credit gate. Resolves to `true` when the caller still has\n * AI-credit budget; throws a quota error (HTTP 429 / QUOTA_EXHAUSTED, from\n * the wspace gateway's @usage check) when the monthly token budget is\n * depleted. Detect the rejection with `isQuotaExhaustedError()`.\n */\nexport async function checkAssistantAiCredits(\n _apollo: ApolloClient,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<boolean> {\n const data = await executeSandboxGraphQL<{ checkAiCredits: boolean }>({\n document: CheckAiCreditsDocument,\n variables: {},\n workspaceId,\n authContext,\n });\n return data.checkAiCredits;\n}\n\n/**\n * Post-turn charge. Reports the turn's raw input/output token counts + the\n * model so the svc computes COST-WEIGHTED credits server-side and charges them\n * against `vibecontrols.ai.credits` (the amount is never sent from the client).\n * `messageId` makes the charge idempotent (retries don't double-charge).\n * No-ops when there are no tokens, or when the model is unknown (the charge\n * requires it). Best-effort — callers must not block the displayed answer on it.\n */\nexport async function reportAssistantAiTokenUsage(\n _apollo: ApolloClient,\n workspaceId: string,\n sessionId: string,\n messageId: string,\n model: string | undefined,\n tokens: { input: number; output: number },\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n const inputTokens = Math.max(0, Math.round(tokens.input ?? 0));\n const outputTokens = Math.max(0, Math.round(tokens.output ?? 0));\n if (inputTokens + outputTokens === 0) return;\n // The svc requires a model to price the charge; skip rather than send a turn\n // that can't be priced (the meter under-counts that turn — acceptable).\n if (!model) return;\n\n await executeSandboxGraphQL<{ reportAiTokenUsage: boolean }>({\n document: ReportAiTokenUsageDocument,\n variables: {\n input: { sessionId, messageId, model, inputTokens, outputTokens },\n },\n workspaceId,\n authContext,\n });\n}\n\nexport async function getAssistantArtifacts(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantArtifact[]> {\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/artifacts`,\n 'GET',\n undefined,\n authContext\n );\n\n return ((body.artifacts as AssistantArtifact[] | undefined) ?? []).filter(\n (artifact) => !artifact.name.startsWith('.')\n );\n}\n\nexport async function downloadAssistantArtifact(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n artifactPath: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<DownloadedAssistantArtifact> {\n const encodedPath = encodeURIComponent(artifactPath);\n const body = await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/artifacts/download?path=${encodedPath}`,\n 'GET',\n undefined,\n authContext\n );\n\n const artifact = body.artifact as Record<string, unknown> | undefined;\n if (!artifact) {\n throw new Error('Artifact download returned an empty response');\n }\n\n return {\n filename: artifact.filename as string,\n mimeType: artifact.mimeType as string,\n contentBase64: artifact.contentBase64 as string,\n };\n}\n\n// ── Heartbeat ───────────────────────────────────────────────────────\n\nexport async function sendHeartbeat(\n apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await proxyToSandbox(\n apollo,\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/heartbeat`,\n 'POST',\n undefined,\n authContext\n );\n}\n\nexport async function extendAssistantSandboxTTL(\n _apollo: ApolloClient,\n sandboxId: string,\n workspaceId: string,\n additionalSeconds = 600,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await executeSandboxGraphQL<{ extendSandboxTTL: { id: string } }>({\n document: ExtendSandboxTtlDocument,\n variables: {\n id: sandboxId,\n additionalSeconds,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n workspaceId,\n authContext,\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ import { createAssistantSandbox as e, createAssistantSession as t, extendAssistantSandboxTTL as n, findExistingSandbox as r, getAssistantMessages as i, sendAssistantPromptAsync as a, waitForAssistantServiceReady as o, waitForSandboxReady as s } from "./assistantApi.js";
2
+ import { gatherPageContext as c } from "../utils/pageContext.js";
3
+ import { useCallback as l, useMemo as u, useRef as d } from "react";
4
+ import { useGatewayClient as f } from "@burdenoff/fe-libs/shared/graphql";
5
+ import { useAuthToken as p } from "@burdenoff/fe-libs/shared/providers/shell";
6
+ //#region src/services/createSandboxAssistantTransport.ts
7
+ var m = "assistant", h = 18e4, g = 1500, _ = 3e4;
8
+ function v(e) {
9
+ try {
10
+ let t = e === "workspaceId" ? "burdenoff-active-context-workspace" : "burdenoff-active-context-organization", n = localStorage.getItem(t);
11
+ if (n) return n;
12
+ let r = sessionStorage.getItem("bf-active-profile");
13
+ if (!r) return "";
14
+ let i = localStorage.getItem(`bf-p-${r}-context`);
15
+ return i ? JSON.parse(i)[e] ?? "" : "";
16
+ } catch {
17
+ return "";
18
+ }
19
+ }
20
+ function y() {
21
+ return new URLSearchParams(window.location.search).get("workspace") ?? v("workspaceId");
22
+ }
23
+ function b() {
24
+ return new URLSearchParams(window.location.search).get("org") ?? v("organizationId");
25
+ }
26
+ function x(e) {
27
+ return e.info?.time?.created ?? 0;
28
+ }
29
+ function S(e) {
30
+ return (e ?? []).filter((e) => e.type === "text" && typeof e.text == "string").map((e) => e.text?.trim() ?? "").filter(Boolean).join("\n");
31
+ }
32
+ function C(e) {
33
+ return (e ?? []).filter((e) => e.type === "tool" && e.tool).map((e) => {
34
+ let t = e.state?.status ?? "running", n = e.tool ?? "tool";
35
+ return t === "completed" ? `Completed: ${n}` : t === "failed" ? `Failed: ${n}` : `Running: ${n}`;
36
+ });
37
+ }
38
+ function w(e, t) {
39
+ let n = e.filter((e) => e.info?.role === "assistant" && x(e) >= t).sort((e, t) => x(e) - x(t));
40
+ if (n.length === 0) return {
41
+ content: "Thinking…",
42
+ done: !1
43
+ };
44
+ let r = n[n.length - 1], i = S(r.parts), a = C(r.parts);
45
+ return {
46
+ content: [i, ...a].filter(Boolean).join("\n\n") || "Thinking…",
47
+ done: !!r.info?.time?.completed && (!!i || a.length > 0)
48
+ };
49
+ }
50
+ function T(e) {
51
+ return e.replace(/(Authorization\s*:\s*Bearer\s+)[^\s\n]+/gi, "$1[REDACTED]").replace(/(X-Workspace-Authorization\s*:\s*Bearer\s+)[^\s\n]+/gi, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+\b/g, "[REDACTED_JWT]").replace(/\bsk-ant-[A-Za-z0-9-]+\b/g, "[REDACTED_API_KEY]");
52
+ }
53
+ function E(e) {
54
+ let t = e instanceof Error ? e.message.toLowerCase() : String(e).toLowerCase();
55
+ return t.includes("rate limit exceeded") || t.includes("unauthorized") || t.includes("k8s api error 401") ? !1 : [
56
+ "sandbox not found",
57
+ "sandbox is not running",
58
+ "sandbox service not available yet",
59
+ "sandbox failed",
60
+ "sandbox startup timed out",
61
+ "assistant service did not become healthy",
62
+ "proxy error: 404",
63
+ "proxy error: 502",
64
+ "proxy error: 503"
65
+ ].some((e) => t.includes(e));
66
+ }
67
+ function D() {
68
+ let v = f("workspace"), { getAccessToken: x, getWorkspaceToken: S, userId: C } = p(), D = d(null), O = d(null), k = l(async (n, i) => {
69
+ let a = D.current;
70
+ if (!a) {
71
+ if (a = await r(v, n, m, i), !a) {
72
+ if (!x()) throw Error("API Calls mode requires an authenticated session. Please sign in again.");
73
+ a = await e(v, m, n, i);
74
+ }
75
+ await s(v, a, n, i), await o(v, a, n, i), D.current = a;
76
+ }
77
+ let c = O.current;
78
+ return c || (c = (await t(v, a, n, m, i)).sessionId, O.current = c), {
79
+ sandboxId: a,
80
+ sessionId: c
81
+ };
82
+ }, [v, x]), A = l(async ({ prompt: e, onProgress: t, signal: r }) => {
83
+ let o = y();
84
+ if (!o) throw Error("The assistant needs an active workspace. Open a workspace and try again.");
85
+ let s = {
86
+ accessToken: x(),
87
+ workspaceToken: S(),
88
+ userId: C,
89
+ organizationId: b()
90
+ }, l = async (u) => {
91
+ try {
92
+ let { sandboxId: l, sessionId: u } = await k(o, s), d = Date.now();
93
+ await a(v, l, o, u, e, m, c(), s);
94
+ let f = Date.now() + h, p = Date.now(), y = w([], d);
95
+ for (; Date.now() < f;) {
96
+ if (r.aborted) throw Error("Cancelled");
97
+ if (y = w(await i(v, l, o, u, s, 50), d), t(T(y.content)), y.done) break;
98
+ Date.now() - p > _ && (await n(v, l, o, 600, s).catch(() => void 0), p = Date.now()), await new Promise((e) => setTimeout(e, g));
99
+ }
100
+ if (!y.done) throw Error("The assistant timed out while responding. Please try again.");
101
+ return { text: T(y.content) };
102
+ } catch (e) {
103
+ if (u === 1 && E(e)) return D.current = null, O.current = null, l(2);
104
+ throw e;
105
+ }
106
+ };
107
+ return l(1);
108
+ }, [
109
+ v,
110
+ k,
111
+ x,
112
+ S,
113
+ C
114
+ ]);
115
+ return u(() => ({ sendPrompt: A }), [A]);
116
+ }
117
+ //#endregion
118
+ export { D as useSandboxAssistantTransport };
119
+
120
+ //# sourceMappingURL=createSandboxAssistantTransport.js.map