@burdenoff/microfe-bigconsole 2026.912.4 → 2026.915.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,19 @@
1
- import { CreateSandboxDocument as e, ExtendSandboxTtlDocument as t, GetActiveSandboxesDocument as n, GetSandboxDocument as r, ProxySandboxRequestDocument as i } from "../../generated/wspace-operations.js";
2
- import { Kind as a, print as o } from "graphql";
3
- import { graphqlFetch as s } from "@burdenoff/fe-libs/shared/graphql";
1
+ import { CreateSandboxDocument as e, ExtendSandboxTtlDocument as t, GetActiveSandboxesDocument as n, GetReusableAssistantSandboxDocument as r, GetSandboxDocument as i, ProxySandboxRequestDocument as a } from "../../generated/wspace-operations.js";
2
+ import { Kind as o, print as s } from "graphql";
3
+ import { graphqlFetch as c } from "@burdenoff/fe-libs/shared/graphql";
4
+ import { ASSISTANT_API_AUTH_STRATEGY as l, getImageForMode as u, getImageTagForMode as d, resolveSandboxAssistantMode as f } from "@burdenoff/fe-libs/shared/assistant/images";
4
5
  //#region src/bigconsole/assistant/assistantApi.ts
5
- var c = "burdenoffregistry.azurecr.io";
6
- function l() {
6
+ var p = "bigconsole";
7
+ function m() {
7
8
  return typeof window > "u" ? !1 : window.location.hostname.toLowerCase() === "app.bigconsole.com";
8
9
  }
9
- var u = l() ? "prod" : "alpha-proxy-auth-v1", d = l() ? "prod" : "alpha-proxy-auth-v1", f = l() ? "prod" : "alpha-delegated-auth-v11", p = "sandbox-app-client-headers-v1";
10
- function m(e) {
11
- switch (e) {
12
- case "general": return u;
13
- case "vibe-plugins": return d;
14
- case "assistant":
15
- case "api-calls": return f;
16
- }
10
+ function h(e, t = m()) {
11
+ return d(e, t);
17
12
  }
18
- function h(e) {
19
- return `${c}/wspace/ai-assistant-${e === "assistant" ? "api-calls" : e}:${m(e)}`;
13
+ function g(e, t = m()) {
14
+ return u(e, t);
20
15
  }
21
- function g(e) {
16
+ function _(e) {
22
17
  switch (e) {
23
18
  case "general": return {
24
19
  cpuRequest: "100m",
@@ -36,14 +31,8 @@ function g(e) {
36
31
  };
37
32
  }
38
33
  }
39
- function _(e) {
40
- let t = e.metadata?.assistantMode;
41
- if (t === "assistant" || t === "general" || t === "api-calls" || t === "vibe-plugins") return t;
42
- let n = e.name ?? "";
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;
44
- }
45
34
  function v(e) {
46
- return e.definitions.find((e) => e.kind === a.OPERATION_DEFINITION)?.name?.value;
35
+ return e.definitions.find((e) => e.kind === o.OPERATION_DEFINITION)?.name?.value;
47
36
  }
48
37
  function y() {
49
38
  try {
@@ -77,9 +66,9 @@ async function x(e) {
77
66
  throw t;
78
67
  }
79
68
  async function S(e) {
80
- let t = v(e.document), n = await s({
69
+ let t = v(e.document), n = await c({
81
70
  gateway: "workspace",
82
- query: o(e.document),
71
+ query: s(e.document),
83
72
  variables: e.variables,
84
73
  operationName: t,
85
74
  authToken: e.authContext?.accessToken ?? void 0,
@@ -97,16 +86,32 @@ async function S(e) {
97
86
  if (!n.data) throw Error("Sandbox GraphQL returned no data");
98
87
  return n.data;
99
88
  }
100
- async function C(e, t, r) {
89
+ function C(e, t, n) {
90
+ return (e.status === "RUNNING" || e.status === "PROVISIONING" || e.status === "PENDING") && e.template === "AI_BUILDER" && f(e) === t && !!n && e.createdBy === n && e.metadata?.authStrategy === l && e.metadata?.assistantProduct === "bigconsole" && e.metadata?.confirmWrites !== !0;
91
+ }
92
+ async function w(e, t, n, i) {
93
+ if (!i?.userId) return !1;
94
+ let a = (await x({
95
+ document: r,
96
+ variables: {
97
+ id: e,
98
+ context: b(t, i)
99
+ },
100
+ workspaceId: t,
101
+ authContext: i
102
+ })).getSandbox;
103
+ return !!a && a.id === e && C(a, n, i.userId);
104
+ }
105
+ async function T(e, t, r) {
101
106
  return ((await x({
102
107
  document: n,
103
108
  variables: { context: b(e, r) },
104
109
  workspaceId: e,
105
110
  authContext: r
106
- })).getActiveSandboxes ?? []).find((e) => (e.status === "RUNNING" || e.status === "PROVISIONING" || e.status === "PENDING") && e.template === "AI_BUILDER" && _(e) === t && (!r?.userId || e.createdBy === r.userId) && e.metadata?.authStrategy === p && e.metadata?.imageTag === m(t))?.id ?? null;
111
+ })).getActiveSandboxes ?? []).find((e) => C(e, t, r?.userId))?.id ?? null;
107
112
  }
108
- async function w(t, n, r) {
109
- let i = g(t), a = [
113
+ async function E(t, n, r) {
114
+ let i = _(t), a = [
110
115
  {
111
116
  name: "AI_ASSISTANT_MODE",
112
117
  value: t
@@ -121,9 +126,9 @@ async function w(t, n, r) {
121
126
  },
122
127
  {
123
128
  name: "AI_ASSISTANT_PRODUCT",
124
- value: "bigconsole"
129
+ value: p
125
130
  }
126
- ], o = await x({
131
+ ], o = m(), s = await x({
127
132
  document: e,
128
133
  variables: { input: {
129
134
  name: `ai-assistant-${t}-${Date.now()}`,
@@ -132,7 +137,7 @@ async function w(t, n, r) {
132
137
  mode: "DEPLOYMENT",
133
138
  containers: [{
134
139
  name: "ai-assistant",
135
- image: h(t),
140
+ image: g(t, o),
136
141
  ports: [{
137
142
  containerPort: 8080,
138
143
  name: "http"
@@ -148,52 +153,54 @@ async function w(t, n, r) {
148
153
  ttlSeconds: 600,
149
154
  metadata: {
150
155
  assistantMode: t,
156
+ assistantProduct: p,
151
157
  assistantOwnerUserId: r?.userId ?? null,
152
158
  assistantOrganizationId: r?.organizationId ?? null,
153
- authStrategy: p,
154
- imageTag: m(t)
159
+ authStrategy: l,
160
+ imageTag: h(t, o),
161
+ confirmWrites: !1
155
162
  },
156
163
  context: b(n, r)
157
164
  } },
158
165
  workspaceId: n,
159
166
  authContext: r
160
167
  });
161
- if (!o?.createSandbox?.id) throw Error("createSandbox returned empty response");
162
- return o.createSandbox.id;
168
+ if (!s?.createSandbox?.id) throw Error("createSandbox returned empty response");
169
+ return s.createSandbox.id;
163
170
  }
164
- async function T(e, t, n, i = 3e5) {
171
+ async function D(e, t, n, r = 3e5) {
165
172
  let a = Date.now();
166
- for (; Date.now() - a < i;) {
167
- let i = await x({
168
- document: r,
173
+ for (; Date.now() - a < r;) {
174
+ let r = await x({
175
+ document: i,
169
176
  variables: {
170
177
  id: e,
171
178
  context: b(t, n)
172
179
  },
173
180
  workspaceId: t,
174
181
  authContext: n
175
- }), a = i?.getSandbox?.status, o = i?.getSandbox?.errorMessage;
182
+ }), a = r?.getSandbox?.status, o = r?.getSandbox?.errorMessage;
176
183
  if (a === "RUNNING") return;
177
184
  if (a === "FAILED" || a === "STOPPED" || a === "EXPIRED") throw Error(o ? `Sandbox ${a.toLowerCase()}: ${o}` : `Sandbox ${a.toLowerCase()}`);
178
185
  await new Promise((e) => setTimeout(e, 2e3));
179
186
  }
180
187
  throw Error("Sandbox startup timed out");
181
188
  }
182
- async function E(e, t, n, r, a, o) {
189
+ async function O(e, t, n, r, i, o) {
183
190
  console.log("[BigConsole-Assistant] proxyToSandbox REQUEST", {
184
191
  sandboxId: e,
185
192
  workspaceId: t,
186
193
  path: n,
187
194
  method: r,
188
- hasBody: !!a
195
+ hasBody: !!i
189
196
  });
190
197
  let s = (await x({
191
- document: i,
198
+ document: a,
192
199
  variables: { input: {
193
200
  sandboxId: e,
194
201
  path: n,
195
202
  method: r,
196
- body: a,
203
+ body: i,
197
204
  context: b(t, o)
198
205
  } },
199
206
  workspaceId: t,
@@ -214,11 +221,11 @@ async function E(e, t, n, r, a, o) {
214
221
  }
215
222
  return s.body;
216
223
  }
217
- async function D(e, t, n, r, i = 9e4) {
224
+ async function k(e, t, n, r, i = 9e4) {
218
225
  let a = Date.now();
219
226
  for (; Date.now() - a < i;) {
220
227
  try {
221
- await E(e, t, "/sessions", "POST", { mode: n }, r);
228
+ await O(e, t, "/sessions", "POST", { mode: n }, r);
222
229
  return;
223
230
  } catch (e) {
224
231
  let t = e instanceof Error ? e.message : String(e);
@@ -228,21 +235,21 @@ async function D(e, t, n, r, i = 9e4) {
228
235
  }
229
236
  throw Error("Assistant service did not become ready before timeout");
230
237
  }
231
- async function O(e, t, n, r) {
232
- return { sessionId: (await E(e, t, "/sessions", "POST", { mode: n }, r)).session.id };
238
+ async function A(e, t, n, r) {
239
+ return { sessionId: (await O(e, t, "/sessions", "POST", { mode: n }, r)).session.id };
233
240
  }
234
- async function k(e, t, n, r, i, a, o) {
241
+ async function j(e, t, n, r, i, a, o) {
235
242
  let s = {
236
243
  mode: i,
237
244
  prompt: r,
238
245
  pageContext: a ?? void 0
239
246
  };
240
- await E(e, t, `/sessions/${n}/prompt-async`, "POST", s, o);
247
+ await O(e, t, `/sessions/${n}/prompt-async`, "POST", s, o);
241
248
  }
242
- async function A(e, t, n, r, i = 50) {
243
- return (await E(e, t, `/sessions/${n}/messages?limit=${i}`, "GET", void 0, r)).messages ?? [];
249
+ async function M(e, t, n, r, i = 50) {
250
+ return (await O(e, t, `/sessions/${n}/messages?limit=${i}`, "GET", void 0, r)).messages ?? [];
244
251
  }
245
- async function j(e, n, r = 600, i) {
252
+ async function N(e, n, r = 600, i) {
246
253
  await x({
247
254
  document: t,
248
255
  variables: {
@@ -255,6 +262,6 @@ async function j(e, n, r = 600, i) {
255
262
  });
256
263
  }
257
264
  //#endregion
258
- export { w as createAssistantSandbox, O as createAssistantSession, j as extendAssistantSandboxTTL, C as findExistingSandbox, A as getAssistantMessages, k as sendAssistantPromptAsync, D as waitForAssistantServiceReady, T as waitForSandboxReady };
265
+ export { E as createAssistantSandbox, A as createAssistantSession, N as extendAssistantSandboxTTL, T as findExistingSandbox, M as getAssistantMessages, w as isCachedAssistantSandboxReusable, j as sendAssistantPromptAsync, k as waitForAssistantServiceReady, D as waitForSandboxReady };
259
266
 
260
267
  //# sourceMappingURL=assistantApi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assistantApi.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API service for BigConsole.\n *\n * All requests go through the wspace-public-gateway (GraphQL Federation):\n * Frontend → wspace-public-gateway → wspace-sandbox-svc → ai-assistant (sandbox)\n *\n * This is a faithful port of microfe-vibecontrols' `services/assistantApi.ts`,\n * adapted for BigConsole: the only product-specific difference is\n * `AI_ASSISTANT_PRODUCT=bigconsole` in the sandbox container env (drives the\n * agent's per-product docs fetch + system prompt).\n */\n\nimport { print, type DocumentNode, Kind, type OperationDefinitionNode } from 'graphql';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type { AssistantMode, AssistantRawMessage, AssistantRawSession, AssistantSandboxAuthContext } from './types';\nimport type { PageContext } from './pageContext';\nimport {\n CreateSandboxDocument,\n GetSandboxDocument,\n GetActiveSandboxesDocument,\n ProxySandboxRequestDocument,\n ExtendSandboxTtlDocument,\n} from '../../generated/wspace-operations';\n\n// ── Image registry ─────────────────────────────────────────────────\n\nconst ACR_REGISTRY = 'burdenoffregistry.azurecr.io';\nfunction isProdRuntime(): boolean {\n if (typeof window === 'undefined') {\n return import.meta.env.VITE_DEPLOY_ENV === 'prod' || import.meta.env.VITE_APP_ENV === 'prod';\n }\n const host = window.location.hostname.toLowerCase();\n return host === 'app.bigconsole.com';\n}\n\nconst GENERAL_IMAGE_TAG = isProdRuntime() ? 'prod' : 'alpha-proxy-auth-v1';\nconst VIBE_PLUGINS_IMAGE_TAG = isProdRuntime() ? 'prod' : 'alpha-proxy-auth-v1';\nconst API_CALLS_IMAGE_TAG = isProdRuntime() ? '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 `${ACR_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 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// ── Types ───────────────────────────────────────────────────────────\n\ninterface ProxyResponse {\n proxySandboxRequest: {\n status: number;\n body: Record<string, unknown>;\n };\n}\n\nfunction getOperationName(document: DocumentNode): string | undefined {\n const operation = document.definitions.find(\n (definition): definition is OperationDefinitionNode => definition.kind === Kind.OPERATION_DEFINITION\n );\n return operation?.name?.value;\n}\n\nfunction buildAssistantRequestId(): string {\n try {\n return `bc-assistant-${crypto.randomUUID()}`;\n } catch {\n return `bc-assistant-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n }\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 // Retry transient failures (network drops, CORS preflight rejections,\n // gateway 524s, etc.) with bounded exponential backoff. The browser\n // surfaces these as \"Failed to fetch\" via fetch() throwing TypeError\n // before the runtime can inspect the response — so the only signal we\n // have is the throw itself. Bounded retries mask the gateway's\n // occasional CORS preflight races without papering over hard failures\n // (unauth, validation) which the inner block below raises as typed\n // errors that bubble up to the assistant's existing `isRecoverable`\n // retry path.\n const maxRetries = 3;\n let lastError: unknown;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await executeSandboxGraphQLOnce<TData>(params);\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n // Only retry on transient network-level failures. Let domain errors\n // (auth, validation, server 5xx with a body) bubble immediately so\n // the outer `isRecoverable` logic can decide whether to drop the\n // sandbox and try a clean runtime instead.\n const isTransient =\n message.includes('failed to fetch') ||\n message.includes('networkerror') ||\n message.includes('network request failed') ||\n message.includes('aborted') ||\n message.includes('load failed');\n if (!isTransient || attempt === maxRetries - 1) {\n throw error;\n }\n // 250ms, 500ms, 1000ms — bounded so we don't blow the 90s gateway\n // execution timeout on a stuck connection.\n const backoff = 250 * Math.pow(2, attempt);\n console.warn('[BigConsole-Assistant] executeSandboxGraphQL transient failure, retrying', {\n attempt: attempt + 1,\n maxRetries,\n backoffMs: backoff,\n error: error instanceof Error ? error.message : String(error),\n });\n await new Promise((resolve) => setTimeout(resolve, backoff));\n }\n }\n // Unreachable — the loop either returns or throws.\n throw lastError;\n}\n\nasync function executeSandboxGraphQLOnce<TData>(params: {\n document: DocumentNode;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const operationName = getOperationName(params.document);\n const result = await graphqlFetch<TData>({\n gateway: 'workspace',\n query: print(params.document),\n variables: params.variables,\n operationName,\n authToken: params.authContext?.accessToken ?? undefined,\n // Critical: always opt into workspace-token auto-minting here instead of\n // passing through a cached token string from the shell. If we pass a stale\n // string, graphqlFetch validates it for the current workspace/project and\n // nulls it out, but it WILL NOT mint a replacement unless the caller used\n // `workspaceToken: true`. That exact behavior caused proxySandboxRequest\n // calls to fail with `Dual-token authentication failed` even though the\n // browser had a login session and the workspace context was known.\n workspaceToken: true,\n workspaceId: params.workspaceId,\n organizationId: params.authContext?.organizationId ?? true,\n suppressGlobalErrorEvent: true,\n // Give assistant calls a unique queue key so they never batch with the\n // dashboard's burst of background widget requests. This avoids mixing\n // long-lived assistant polls with unrelated queries in the shared\n // graphqlFetch batching queue.\n extraHeaders: {\n 'x-request-id': buildAssistantRequestId(),\n },\n });\n\n if (!result) {\n throw new Error('Sandbox GraphQL returned empty response');\n }\n\n if (result.errors?.length) {\n console.error('[BigConsole-Assistant] executeSandboxGraphQL errors', {\n operationName,\n errors: result.errors,\n });\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 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 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`) and personalizes its system prompt.\n // This package is the BigConsole MFE, so the product is bigconsole.\n { name: 'AI_ASSISTANT_PRODUCT', value: 'bigconsole' },\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 // The agent (start-assistant.sh) binds :8080; the sandbox svc proxies\n // to this declared containerPort, so it MUST be 8080.\n ports: [{ containerPort: 8080, 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 sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 300_000\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 ? `Sandbox ${status.toLowerCase()}: ${errorMessage}` : `Sandbox ${status.toLowerCase()}`\n );\n }\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 sandboxId: string,\n workspaceId: string,\n path: string,\n method: string,\n body?: unknown,\n authContext?: AssistantSandboxAuthContext\n): Promise<Record<string, unknown>> {\n console.log('[BigConsole-Assistant] proxyToSandbox REQUEST', {\n sandboxId,\n workspaceId,\n path,\n method,\n hasBody: !!body,\n });\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 console.log('[BigConsole-Assistant] proxyToSandbox RESPONSE', { sandboxId, path, status: proxyResult?.status });\n if (!proxyResult || proxyResult.status >= 400) {\n const errBody = proxyResult?.body as Record<string, unknown> | undefined;\n console.error('[BigConsole-Assistant] proxyToSandbox ERROR', {\n sandboxId,\n path,\n status: proxyResult?.status,\n errBody,\n });\n throw new Error((errBody?.error as string) ?? `Proxy error: ${proxyResult?.status ?? 'unknown'}`);\n }\n\n return proxyResult.body;\n}\n\nexport async function waitForAssistantServiceReady(\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 90_000\n): Promise<void> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < maxWaitMs) {\n try {\n // Use the documented assistant API contract itself as the readiness\n // signal. The assistant image definitively supports POST /sessions,\n // whereas /health is an implementation assumption and has proven to hang\n // behind the Cloudflare sandbox proxy.\n await proxyToSandbox(sandboxId, workspaceId, '/sessions', 'POST', { mode }, authContext);\n return;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (!/proxy error: 502|proxy error: 503|proxy error: 404|service not available|timeout/i.test(message)) {\n throw error;\n }\n }\n\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n throw new Error('Assistant service did not become ready before timeout');\n}\n\n// ── Session management ─────────────────────────────────────────────\n\nexport async function createAssistantSession(\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<{ sessionId: string }> {\n const body = await proxyToSandbox(sandboxId, workspaceId, '/sessions', 'POST', { mode }, authContext);\n const session = body.session as Record<string, unknown>;\n return { sessionId: session.id as string };\n}\n\n/**\n * All conversations held by this sandbox, newest first.\n *\n * The sandbox is the source of truth for the transcript, so the History list is\n * built from it rather than from anything we cache client-side — what the user\n * can reopen is exactly what the agent still has.\n */\nexport async function listAssistantSessions(\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantRawSession[]> {\n const body = await proxyToSandbox(sandboxId, workspaceId, '/sessions', 'GET', undefined, authContext);\n const sessions = body.sessions;\n return Array.isArray(sessions) ? (sessions as AssistantRawSession[]) : [];\n}\n\nexport async function deleteAssistantSession(\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await proxyToSandbox(sandboxId, workspaceId, `/sessions/${sessionId}`, 'DELETE', undefined, authContext);\n}\n\n// ── Send prompt ────────────────────────────────────────────────────\n\nexport async function sendAssistantPromptAsync(\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(sandboxId, workspaceId, `/sessions/${sessionId}/prompt-async`, 'POST', requestBody, authContext);\n}\n\nexport async function getAssistantMessages(\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext,\n limit = 50\n): Promise<AssistantRawMessage[]> {\n const body = await proxyToSandbox(\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// ── TTL extension (keep the sandbox warm across the turn) ──────────\n\nexport async function extendAssistantSandboxTTL(\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/bigconsole/assistant/assistantApi.ts"],"sourcesContent":["/**\n * AI Assistant API service for BigConsole.\n *\n * All requests go through the wspace-public-gateway (GraphQL Federation):\n * Frontend → wspace-public-gateway → wspace-sandbox-svc → ai-assistant (sandbox)\n *\n * This is a faithful port of microfe-vibecontrols' `services/assistantApi.ts`,\n * adapted for BigConsole: the only product-specific difference is\n * `AI_ASSISTANT_PRODUCT=bigconsole` in the sandbox container env (drives the\n * agent's per-product docs fetch + system prompt).\n */\n\nimport { print, type DocumentNode, Kind, type OperationDefinitionNode } from 'graphql';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n// Deep import: images.js has no dependencies, so this does not pull the shared\n// assistant barrel into the consumer's chunk. fe-libs owns the registry (ECR\n// since the AWS cutover), the image tags, the auth strategy and the mode\n// resolution; this file used to carry byte-for-byte copies of them.\nimport {\n ASSISTANT_API_AUTH_STRATEGY,\n getImageForMode as getSharedImageForMode,\n getImageTagForMode as getSharedImageTagForMode,\n resolveSandboxAssistantMode,\n} from '@burdenoff/fe-libs/shared/assistant/images';\nimport type { AssistantMode, AssistantRawMessage, AssistantRawSession, AssistantSandboxAuthContext } from './types';\nimport type { PageContext } from './pageContext';\nimport {\n CreateSandboxDocument,\n GetSandboxDocument,\n GetReusableAssistantSandboxDocument,\n GetActiveSandboxesDocument,\n ProxySandboxRequestDocument,\n ExtendSandboxTtlDocument,\n} from '../../generated/wspace-operations';\n\n// ── Product, runtime and image ─────────────────────────────────────\n\n/**\n * The product this transport stamps on every assistant sandbox it creates\n * (`metadata.assistantProduct`) — the same key the shared fe-libs assistant\n * stamps for HealthyBowl and VibeControls, and FluidGrids stamps for itself — and\n * the ONLY product whose sandboxes it will reuse (BOFF-7331).\n */\nexport const ASSISTANT_PRODUCT = 'bigconsole';\n\nexport function isProdRuntime(): boolean {\n if (typeof window === 'undefined') {\n return import.meta.env.VITE_DEPLOY_ENV === 'prod' || import.meta.env.VITE_APP_ENV === 'prod';\n }\n const host = window.location.hostname.toLowerCase();\n return host === 'app.bigconsole.com';\n}\n\n/**\n * Image tag recorded on the sandbox. It is metadata only: the sandbox\n * cf-proxy's AiAssistant pin decides which image actually runs (plan decision\n * D9), so the tag is NOT used to pick a sandbox for reuse — see\n * `isReusableAssistantSandbox`.\n */\nexport function getImageTagForMode(mode: AssistantMode, isProd: boolean = isProdRuntime()): string {\n return getSharedImageTagForMode(mode, isProd);\n}\n\n/**\n * `assistant` reuses the api-calls image (mode is selected at runtime via\n * AI_ASSISTANT_MODE). The registry is fe-libs' — the Azure registry this used to\n * hardcode was decommissioned on 2026-08-15 and is not in wspace-sandbox-svc's\n * default ALLOWED_IMAGE_REGISTRIES.\n */\nexport function getImageForMode(mode: AssistantMode, isProd: boolean = isProdRuntime()): string {\n return getSharedImageForMode(mode, isProd);\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 case 'assistant':\n case 'api-calls':\n return {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n };\n }\n}\n\n// ── Types ───────────────────────────────────────────────────────────\n\ninterface ProxyResponse {\n proxySandboxRequest: {\n status: number;\n body: Record<string, unknown>;\n };\n}\n\nfunction getOperationName(document: DocumentNode): string | undefined {\n const operation = document.definitions.find(\n (definition): definition is OperationDefinitionNode => definition.kind === Kind.OPERATION_DEFINITION\n );\n return operation?.name?.value;\n}\n\nfunction buildAssistantRequestId(): string {\n try {\n return `bc-assistant-${crypto.randomUUID()}`;\n } catch {\n return `bc-assistant-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n }\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 // Retry transient failures (network drops, CORS preflight rejections,\n // gateway 524s, etc.) with bounded exponential backoff. The browser\n // surfaces these as \"Failed to fetch\" via fetch() throwing TypeError\n // before the runtime can inspect the response — so the only signal we\n // have is the throw itself. Bounded retries mask the gateway's\n // occasional CORS preflight races without papering over hard failures\n // (unauth, validation) which the inner block below raises as typed\n // errors that bubble up to the assistant's existing `isRecoverable`\n // retry path.\n const maxRetries = 3;\n let lastError: unknown;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await executeSandboxGraphQLOnce<TData>(params);\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n // Only retry on transient network-level failures. Let domain errors\n // (auth, validation, server 5xx with a body) bubble immediately so\n // the outer `isRecoverable` logic can decide whether to drop the\n // sandbox and try a clean runtime instead.\n const isTransient =\n message.includes('failed to fetch') ||\n message.includes('networkerror') ||\n message.includes('network request failed') ||\n message.includes('aborted') ||\n message.includes('load failed');\n if (!isTransient || attempt === maxRetries - 1) {\n throw error;\n }\n // 250ms, 500ms, 1000ms — bounded so we don't blow the 90s gateway\n // execution timeout on a stuck connection.\n const backoff = 250 * Math.pow(2, attempt);\n console.warn('[BigConsole-Assistant] executeSandboxGraphQL transient failure, retrying', {\n attempt: attempt + 1,\n maxRetries,\n backoffMs: backoff,\n error: error instanceof Error ? error.message : String(error),\n });\n await new Promise((resolve) => setTimeout(resolve, backoff));\n }\n }\n // Unreachable — the loop either returns or throws.\n throw lastError;\n}\n\nasync function executeSandboxGraphQLOnce<TData>(params: {\n document: DocumentNode;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const operationName = getOperationName(params.document);\n const result = await graphqlFetch<TData>({\n gateway: 'workspace',\n query: print(params.document),\n variables: params.variables,\n operationName,\n authToken: params.authContext?.accessToken ?? undefined,\n // Critical: always opt into workspace-token auto-minting here instead of\n // passing through a cached token string from the shell. If we pass a stale\n // string, graphqlFetch validates it for the current workspace/project and\n // nulls it out, but it WILL NOT mint a replacement unless the caller used\n // `workspaceToken: true`. That exact behavior caused proxySandboxRequest\n // calls to fail with `Dual-token authentication failed` even though the\n // browser had a login session and the workspace context was known.\n workspaceToken: true,\n workspaceId: params.workspaceId,\n organizationId: params.authContext?.organizationId ?? true,\n suppressGlobalErrorEvent: true,\n // Give assistant calls a unique queue key so they never batch with the\n // dashboard's burst of background widget requests. This avoids mixing\n // long-lived assistant polls with unrelated queries in the shared\n // graphqlFetch batching queue.\n extraHeaders: {\n 'x-request-id': buildAssistantRequestId(),\n },\n });\n\n if (!result) {\n throw new Error('Sandbox GraphQL returned empty response');\n }\n\n if (result.errors?.length) {\n console.error('[BigConsole-Assistant] executeSandboxGraphQL errors', {\n operationName,\n errors: result.errors,\n });\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 interface ActiveAssistantSandbox {\n id: string;\n name: string;\n status: string;\n metadata: Record<string, unknown> | null;\n template: string;\n createdBy: string;\n}\n\n/**\n * May this active sandbox be reused for `mode`?\n *\n * Every product's assistant runs as the same user in the same workspace, so\n * `getActiveSandboxes` returns HealthyBowl's, VibeControls' and FluidGrids'\n * assistant sandboxes too (BOFF-7331). Adopting one of those runs the wrong\n * product guide, and HB/VC `api-calls` sandboxes have CONFIRM_WRITES on, which\n * the legacy widget BigConsole mounts has no Approve UI for. So:\n * - the sandbox must be stamped `assistantProduct: 'bigconsole'`. Unstamped\n * sandboxes — every BigConsole sandbox created before this change — are NOT\n * reused: their product cannot be proven. They lapse on their 600s TTL.\n * - a sandbox recording `confirmWrites: true` is never reused, whoever\n * stamped it: it waits for approvals this widget cannot give.\n * - the image tag is NOT compared (D9). It never decided what runs.\n * - ownership FAILS CLOSED: with no `userId` nothing is reusable. fe-libs'\n * AuthTokenProvider starts with `userId` null, and a super_admin caller is\n * handed every sandbox in the workspace, so skipping the owner check when\n * the id is unknown would adopt another user's BigConsole sandbox. The\n * caller creates a fresh sandbox instead.\n */\nexport function isReusableAssistantSandbox(\n sandbox: ActiveAssistantSandbox,\n mode: AssistantMode,\n userId?: string | null\n): boolean {\n return (\n (sandbox.status === 'RUNNING' || sandbox.status === 'PROVISIONING' || sandbox.status === 'PENDING') &&\n sandbox.template === 'AI_BUILDER' &&\n resolveSandboxAssistantMode(sandbox) === mode &&\n !!userId &&\n sandbox.createdBy === userId &&\n sandbox.metadata?.authStrategy === ASSISTANT_API_AUTH_STRATEGY &&\n sandbox.metadata?.assistantProduct === ASSISTANT_PRODUCT &&\n sandbox.metadata?.confirmWrites !== true\n );\n}\n\n/**\n * May a sandbox id cached from an earlier page lifecycle (sessionStorage) be\n * adopted for `mode`?\n *\n * A cached id carries no proof of what it points at: a tab that adopted another\n * product's, an unstamped or another user's sandbox under an older build would\n * otherwise keep driving it. So the sandbox is fetched and held to exactly the\n * same predicate as a freshly listed one. A sandbox that no longer exists is not\n * reusable. Transport errors propagate; the caller treats them as \"not reusable\".\n */\nexport async function isCachedAssistantSandboxReusable(\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<boolean> {\n // Checked before the call so a caller with no identity yet costs no round-trip.\n if (!authContext?.userId) return false;\n const data = await executeSandboxGraphQL<{ getSandbox: ActiveAssistantSandbox | null }>({\n document: GetReusableAssistantSandboxDocument,\n variables: { id: sandboxId, context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const sandbox = data.getSandbox;\n return !!sandbox && sandbox.id === sandboxId && isReusableAssistantSandbox(sandbox, mode, authContext.userId);\n}\n\nexport async function findExistingSandbox(\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<string | null> {\n const data = await executeSandboxGraphQL<{\n getActiveSandboxes: ActiveAssistantSandbox[];\n }>({\n document: GetActiveSandboxesDocument,\n variables: { context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const sandboxes = data.getActiveSandboxes ?? [];\n\n return sandboxes.find((s) => isReusableAssistantSandbox(s, mode, authContext?.userId))?.id ?? null;\n}\n\n// ── Sandbox lifecycle ──────────────────────────────────────────────\n\nexport async function createAssistantSandbox(\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`) and personalizes its system prompt.\n // This package is the BigConsole MFE, so the product is bigconsole.\n { name: 'AI_ASSISTANT_PRODUCT', value: ASSISTANT_PRODUCT },\n ];\n // Resolved once so the image and its recorded tag can never disagree.\n const isProd = isProdRuntime();\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, isProd),\n // The agent (start-assistant.sh) binds :8080; the sandbox svc proxies\n // to this declared containerPort, so it MUST be 8080.\n ports: [{ containerPort: 8080, 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 // Product stamp: the reuse filter only adopts sandboxes carrying it.\n assistantProduct: ASSISTANT_PRODUCT,\n assistantOwnerUserId: authContext?.userId ?? null,\n assistantOrganizationId: authContext?.organizationId ?? null,\n authStrategy: ASSISTANT_API_AUTH_STRATEGY,\n imageTag: getImageTagForMode(mode, isProd),\n // Recorded explicitly (as fe-libs does): the legacy widget has no\n // Approve UI, so these sandboxes never run with\n // AI_ASSISTANT_CONFIRM_WRITES.\n confirmWrites: false,\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 sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 300_000\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 ? `Sandbox ${status.toLowerCase()}: ${errorMessage}` : `Sandbox ${status.toLowerCase()}`\n );\n }\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 sandboxId: string,\n workspaceId: string,\n path: string,\n method: string,\n body?: unknown,\n authContext?: AssistantSandboxAuthContext\n): Promise<Record<string, unknown>> {\n console.log('[BigConsole-Assistant] proxyToSandbox REQUEST', {\n sandboxId,\n workspaceId,\n path,\n method,\n hasBody: !!body,\n });\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 console.log('[BigConsole-Assistant] proxyToSandbox RESPONSE', { sandboxId, path, status: proxyResult?.status });\n if (!proxyResult || proxyResult.status >= 400) {\n const errBody = proxyResult?.body as Record<string, unknown> | undefined;\n console.error('[BigConsole-Assistant] proxyToSandbox ERROR', {\n sandboxId,\n path,\n status: proxyResult?.status,\n errBody,\n });\n throw new Error((errBody?.error as string) ?? `Proxy error: ${proxyResult?.status ?? 'unknown'}`);\n }\n\n return proxyResult.body;\n}\n\nexport async function waitForAssistantServiceReady(\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 90_000\n): Promise<void> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < maxWaitMs) {\n try {\n // Use the documented assistant API contract itself as the readiness\n // signal. The assistant image definitively supports POST /sessions,\n // whereas /health is an implementation assumption and has proven to hang\n // behind the Cloudflare sandbox proxy.\n await proxyToSandbox(sandboxId, workspaceId, '/sessions', 'POST', { mode }, authContext);\n return;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (!/proxy error: 502|proxy error: 503|proxy error: 404|service not available|timeout/i.test(message)) {\n throw error;\n }\n }\n\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n\n throw new Error('Assistant service did not become ready before timeout');\n}\n\n// ── Session management ─────────────────────────────────────────────\n\nexport async function createAssistantSession(\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n): Promise<{ sessionId: string }> {\n const body = await proxyToSandbox(sandboxId, workspaceId, '/sessions', 'POST', { mode }, authContext);\n const session = body.session as Record<string, unknown>;\n return { sessionId: session.id as string };\n}\n\n/**\n * All conversations held by this sandbox, newest first.\n *\n * The sandbox is the source of truth for the transcript, so the History list is\n * built from it rather than from anything we cache client-side — what the user\n * can reopen is exactly what the agent still has.\n */\nexport async function listAssistantSessions(\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<AssistantRawSession[]> {\n const body = await proxyToSandbox(sandboxId, workspaceId, '/sessions', 'GET', undefined, authContext);\n const sessions = body.sessions;\n return Array.isArray(sessions) ? (sessions as AssistantRawSession[]) : [];\n}\n\nexport async function deleteAssistantSession(\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await proxyToSandbox(sandboxId, workspaceId, `/sessions/${sessionId}`, 'DELETE', undefined, authContext);\n}\n\n// ── Send prompt ────────────────────────────────────────────────────\n\nexport async function sendAssistantPromptAsync(\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(sandboxId, workspaceId, `/sessions/${sessionId}/prompt-async`, 'POST', requestBody, authContext);\n}\n\nexport async function getAssistantMessages(\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext,\n limit = 50\n): Promise<AssistantRawMessage[]> {\n const body = await proxyToSandbox(\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// ── TTL extension (keep the sandbox warm across the turn) ──────────\n\nexport async function extendAssistantSandboxTTL(\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,45 @@
1
+ //#region src/bigconsole/assistant/assistantRuntime.ts
2
+ var e = "bc-assistant-sandbox-id-v2", t = "bc-assistant-session-id-v2", n = ["bc-assistant-sandbox-id", "bc-assistant-session-id"];
3
+ function r(e, t) {
4
+ return JSON.stringify([e, t ?? null]);
5
+ }
6
+ var i = "[BigConsole-Assistant]";
7
+ async function a(e) {
8
+ let { workspaceId: t, mode: n, authContext: a, store: o, api: s } = e, c = o.getSandboxId();
9
+ if (console.log(`${i} ensureRuntime start`, {
10
+ sandboxId: c,
11
+ workspaceId: t
12
+ }), c && o.getVerifiedSandboxKey() !== r(c, a.userId)) {
13
+ let e = !1;
14
+ try {
15
+ e = await s.isCachedAssistantSandboxReusable(c, t, n, a);
16
+ } catch (e) {
17
+ console.warn(`${i} could not re-validate cached sandbox`, {
18
+ sandboxId: c,
19
+ error: e instanceof Error ? e.message : String(e)
20
+ });
21
+ }
22
+ e ? o.setVerifiedSandboxKey(r(c, a.userId)) : (console.warn(`${i} cached sandbox is not reusable, discarding it`, { sandboxId: c }), o.setSandboxId(null), o.setSessionId(null), o.setVerifiedSandboxKey(null), c = null);
23
+ }
24
+ if (!c) {
25
+ if (!e.hasAccessToken()) throw Error("The assistant requires an authenticated session. Please sign in again.");
26
+ if (c = await s.findExistingSandbox(t, n, a), console.log(`${i} findExistingSandbox result`, { sandboxId: c }), c) try {
27
+ await s.waitForSandboxReady(c, t, a), await s.waitForAssistantServiceReady(c, t, n, a);
28
+ } catch (e) {
29
+ console.warn(`${i} existing sandbox unusable, falling back to fresh sandbox`, {
30
+ sandboxId: c,
31
+ error: e instanceof Error ? e.message : String(e)
32
+ }), c = null;
33
+ }
34
+ c || (c = await s.createAssistantSandbox(n, t, a), console.log(`${i} createAssistantSandbox result`, { sandboxId: c }), await s.waitForSandboxReady(c, t, a), await s.waitForAssistantServiceReady(c, t, n, a)), o.setSandboxId(c), o.setVerifiedSandboxKey(r(c, a.userId)), o.setSessionId(null);
35
+ }
36
+ let l = o.getSessionId();
37
+ return l || (l = (await s.createAssistantSession(c, t, n, a)).sessionId, o.setSessionId(l)), {
38
+ sandboxId: c,
39
+ sessionId: l
40
+ };
41
+ }
42
+ //#endregion
43
+ export { e as ASSISTANT_SANDBOX_ID_KEY, t as ASSISTANT_SESSION_ID_KEY, n as LEGACY_ASSISTANT_RUNTIME_KEYS, a as resolveAssistantRuntime };
44
+
45
+ //# sourceMappingURL=assistantRuntime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistantRuntime.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRuntime.ts"],"sourcesContent":["/**\n * Resolves the sandbox + agent session a BigConsole assistant turn runs on.\n *\n * Kept out of the React hook so the adoption rules are testable without a DOM:\n * the hook supplies the storage-backed refs and the sandbox API, this decides\n * which sandbox may be driven.\n *\n * The rule (BOFF-7331): a sandbox is only driven once it has been proven to be\n * this user's BigConsole assistant sandbox in THIS page lifecycle — by being\n * created here, by passing `isReusableAssistantSandbox` in the active list, or,\n * for an id rehydrated from sessionStorage, by being fetched and passing the\n * same predicate. A cached id is never trusted on its own: it may have been\n * adopted by an older build that reused other products' and unstamped sandboxes.\n */\n\nimport type { AssistantMode, AssistantSandboxAuthContext } from './types';\n\n/**\n * sessionStorage keys for the warm sandbox + agent session. Versioned: the\n * unversioned keys were written by builds that adopted foreign and unstamped\n * sandboxes, so they are ignored (and cleared) rather than read. Re-validation\n * below is what actually guards adoption; the rename just means a pre-rollout\n * cache never even gets that far.\n */\nexport const ASSISTANT_SANDBOX_ID_KEY = 'bc-assistant-sandbox-id-v2';\nexport const ASSISTANT_SESSION_ID_KEY = 'bc-assistant-session-id-v2';\nexport const LEGACY_ASSISTANT_RUNTIME_KEYS: readonly string[] = ['bc-assistant-sandbox-id', 'bc-assistant-session-id'];\n\n/** The sandbox operations runtime resolution needs (the `assistantApi` functions). */\nexport interface AssistantRuntimeApi {\n isCachedAssistantSandboxReusable: (\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n ) => Promise<boolean>;\n findExistingSandbox: (\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n ) => Promise<string | null>;\n createAssistantSandbox: (\n mode: AssistantMode,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n ) => Promise<string>;\n waitForSandboxReady: (\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n ) => Promise<void>;\n waitForAssistantServiceReady: (\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n ) => Promise<void>;\n createAssistantSession: (\n sandboxId: string,\n workspaceId: string,\n mode: AssistantMode,\n authContext?: AssistantSandboxAuthContext\n ) => Promise<{ sessionId: string }>;\n}\n\n/** The hook's refs. Setters persist to sessionStorage; `verified*` is memory only. */\nexport interface AssistantRuntimeStore {\n getSandboxId: () => string | null;\n setSandboxId: (id: string | null) => void;\n getSessionId: () => string | null;\n setSessionId: (id: string | null) => void;\n getVerifiedSandboxKey: () => string | null;\n setVerifiedSandboxKey: (key: string | null) => void;\n}\n\n/**\n * Identity of a proof that `sandboxId` may be driven by `userId`. Keyed on the\n * user too, so an identity change in the same tab re-validates. A missing user\n * id is part of the key rather than a reason to skip the key: a sandbox created\n * in this lifecycle before the id loaded must not be re-validated (it would fail\n * the fail-closed owner check) and replaced on every turn.\n */\nexport function verifiedSandboxKey(sandboxId: string, userId: string | null | undefined): string {\n return JSON.stringify([sandboxId, userId ?? null]);\n}\n\nconst LOG_PREFIX = '[BigConsole-Assistant]';\n\nexport async function resolveAssistantRuntime(params: {\n workspaceId: string;\n mode: AssistantMode;\n authContext: AssistantSandboxAuthContext;\n store: AssistantRuntimeStore;\n api: AssistantRuntimeApi;\n hasAccessToken: () => boolean;\n}): Promise<{ sandboxId: string; sessionId: string }> {\n const { workspaceId, mode, authContext, store, api } = params;\n\n let sandboxId = store.getSandboxId();\n console.log(`${LOG_PREFIX} ensureRuntime start`, { sandboxId, workspaceId });\n\n if (sandboxId && store.getVerifiedSandboxKey() !== verifiedSandboxKey(sandboxId, authContext.userId)) {\n let reusable = false;\n try {\n reusable = await api.isCachedAssistantSandboxReusable(sandboxId, workspaceId, mode, authContext);\n } catch (error) {\n // Fail closed: an unverifiable sandbox is not driven.\n console.warn(`${LOG_PREFIX} could not re-validate cached sandbox`, {\n sandboxId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (reusable) {\n store.setVerifiedSandboxKey(verifiedSandboxKey(sandboxId, authContext.userId));\n } else {\n console.warn(`${LOG_PREFIX} cached sandbox is not reusable, discarding it`, { sandboxId });\n // The session lives inside that sandbox, so it goes with it.\n store.setSandboxId(null);\n store.setSessionId(null);\n store.setVerifiedSandboxKey(null);\n sandboxId = null;\n }\n }\n\n if (!sandboxId) {\n if (!params.hasAccessToken()) {\n throw new Error('The assistant requires an authenticated session. Please sign in again.');\n }\n sandboxId = await api.findExistingSandbox(workspaceId, mode, authContext);\n console.log(`${LOG_PREFIX} findExistingSandbox result`, { sandboxId });\n\n if (sandboxId) {\n try {\n await api.waitForSandboxReady(sandboxId, workspaceId, authContext);\n await api.waitForAssistantServiceReady(sandboxId, workspaceId, mode, authContext);\n } catch (error) {\n console.warn(`${LOG_PREFIX} existing sandbox unusable, falling back to fresh sandbox`, {\n sandboxId,\n error: error instanceof Error ? error.message : String(error),\n });\n sandboxId = null;\n }\n }\n\n if (!sandboxId) {\n sandboxId = await api.createAssistantSandbox(mode, workspaceId, authContext);\n console.log(`${LOG_PREFIX} createAssistantSandbox result`, { sandboxId });\n await api.waitForSandboxReady(sandboxId, workspaceId, authContext);\n await api.waitForAssistantServiceReady(sandboxId, workspaceId, mode, authContext);\n }\n\n store.setSandboxId(sandboxId);\n store.setVerifiedSandboxKey(verifiedSandboxKey(sandboxId, authContext.userId));\n // Any session id held now belonged to a sandbox we no longer have.\n store.setSessionId(null);\n }\n\n let sessionId = store.getSessionId();\n if (!sessionId) {\n const result = await api.createAssistantSession(sandboxId, workspaceId, mode, authContext);\n sessionId = result.sessionId;\n store.setSessionId(sessionId);\n }\n\n return { sandboxId, sessionId };\n}\n"],"mappings":";AAwBA,IAAa,IAA2B,8BAC3B,IAA2B,8BAC3B,IAAmD,CAAC,2BAA2B,0BAA0B;AAwDtH,SAAgB,EAAmB,GAAmB,GAA2C;AAC/F,QAAO,KAAK,UAAU,CAAC,GAAW,KAAU,KAAK,CAAC;;AAGpD,IAAM,IAAa;AAEnB,eAAsB,EAAwB,GAOQ;CACpD,IAAM,EAAE,gBAAa,SAAM,gBAAa,UAAO,WAAQ,GAEnD,IAAY,EAAM,cAAc;AAGpC,KAFA,QAAQ,IAAI,GAAG,EAAW,uBAAuB;EAAE;EAAW;EAAa,CAAC,EAExE,KAAa,EAAM,uBAAuB,KAAK,EAAmB,GAAW,EAAY,OAAO,EAAE;EACpG,IAAI,IAAW;AACf,MAAI;AACF,OAAW,MAAM,EAAI,iCAAiC,GAAW,GAAa,GAAM,EAAY;WACzF,GAAO;AAEd,WAAQ,KAAK,GAAG,EAAW,wCAAwC;IACjE;IACA,OAAO,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;IAC9D,CAAC;;AAEJ,EAAI,IACF,EAAM,sBAAsB,EAAmB,GAAW,EAAY,OAAO,CAAC,IAE9E,QAAQ,KAAK,GAAG,EAAW,iDAAiD,EAAE,cAAW,CAAC,EAE1F,EAAM,aAAa,KAAK,EACxB,EAAM,aAAa,KAAK,EACxB,EAAM,sBAAsB,KAAK,EACjC,IAAY;;AAIhB,KAAI,CAAC,GAAW;AACd,MAAI,CAAC,EAAO,gBAAgB,CAC1B,OAAU,MAAM,yEAAyE;AAK3F,MAHA,IAAY,MAAM,EAAI,oBAAoB,GAAa,GAAM,EAAY,EACzE,QAAQ,IAAI,GAAG,EAAW,8BAA8B,EAAE,cAAW,CAAC,EAElE,EACF,KAAI;AAEF,GADA,MAAM,EAAI,oBAAoB,GAAW,GAAa,EAAY,EAClE,MAAM,EAAI,6BAA6B,GAAW,GAAa,GAAM,EAAY;WAC1E,GAAO;AAKd,GAJA,QAAQ,KAAK,GAAG,EAAW,4DAA4D;IACrF;IACA,OAAO,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;IAC9D,CAAC,EACF,IAAY;;AAchB,EAVK,MACH,IAAY,MAAM,EAAI,uBAAuB,GAAM,GAAa,EAAY,EAC5E,QAAQ,IAAI,GAAG,EAAW,iCAAiC,EAAE,cAAW,CAAC,EACzE,MAAM,EAAI,oBAAoB,GAAW,GAAa,EAAY,EAClE,MAAM,EAAI,6BAA6B,GAAW,GAAa,GAAM,EAAY,GAGnF,EAAM,aAAa,EAAU,EAC7B,EAAM,sBAAsB,EAAmB,GAAW,EAAY,OAAO,CAAC,EAE9E,EAAM,aAAa,KAAK;;CAG1B,IAAI,IAAY,EAAM,cAAc;AAOpC,QANK,MAEH,KADe,MAAM,EAAI,uBAAuB,GAAW,GAAa,GAAM,EAAY,EACvE,WACnB,EAAM,aAAa,EAAU,GAGxB;EAAE;EAAW;EAAW"}