@burdenoff/microfe-bigconsole 2026.713.3 → 2026.713.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/assistant/assistantApi.js +11 -4
- package/dist/bigconsole/assistant/assistantApi.js.map +1 -1
- package/dist/bigconsole/assistant/createSandboxAssistantTransport.js +209 -130
- package/dist/bigconsole/assistant/createSandboxAssistantTransport.js.map +1 -1
- package/package.json +1 -1
|
@@ -231,7 +231,14 @@ async function D(e, t, n, r, i = 9e4) {
|
|
|
231
231
|
async function O(e, t, n, r) {
|
|
232
232
|
return { sessionId: (await E(e, t, "/sessions", "POST", { mode: n }, r)).session.id };
|
|
233
233
|
}
|
|
234
|
-
async function k(e, t, n
|
|
234
|
+
async function k(e, t, n) {
|
|
235
|
+
let r = (await E(e, t, "/sessions", "GET", void 0, n)).sessions;
|
|
236
|
+
return Array.isArray(r) ? r : [];
|
|
237
|
+
}
|
|
238
|
+
async function A(e, t, n, r) {
|
|
239
|
+
await E(e, t, `/sessions/${n}`, "DELETE", void 0, r);
|
|
240
|
+
}
|
|
241
|
+
async function j(e, t, n, r, i, a, o) {
|
|
235
242
|
let s = {
|
|
236
243
|
mode: i,
|
|
237
244
|
prompt: r,
|
|
@@ -239,10 +246,10 @@ async function k(e, t, n, r, i, a, o) {
|
|
|
239
246
|
};
|
|
240
247
|
await E(e, t, `/sessions/${n}/prompt-async`, "POST", s, o);
|
|
241
248
|
}
|
|
242
|
-
async function
|
|
249
|
+
async function M(e, t, n, r, i = 50) {
|
|
243
250
|
return (await E(e, t, `/sessions/${n}/messages?limit=${i}`, "GET", void 0, r)).messages ?? [];
|
|
244
251
|
}
|
|
245
|
-
async function
|
|
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,
|
|
265
|
+
export { w as createAssistantSandbox, O as createAssistantSession, A as deleteAssistantSession, N as extendAssistantSandboxTTL, C as findExistingSandbox, M as getAssistantMessages, k as listAssistantSessions, j as sendAssistantPromptAsync, D as waitForAssistantServiceReady, T 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, 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// ── 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';\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,22 +1,22 @@
|
|
|
1
|
-
import { createAssistantSandbox as e, createAssistantSession as t,
|
|
2
|
-
import { gatherPageContext as
|
|
3
|
-
import { useCallback as
|
|
4
|
-
import { useAuthToken as
|
|
1
|
+
import { createAssistantSandbox as e, createAssistantSession as t, deleteAssistantSession as n, extendAssistantSandboxTTL as r, findExistingSandbox as i, getAssistantMessages as a, listAssistantSessions as o, sendAssistantPromptAsync as s, waitForAssistantServiceReady as c, waitForSandboxReady as l } from "./assistantApi.js";
|
|
2
|
+
import { gatherPageContext as u } from "./pageContext.js";
|
|
3
|
+
import { useCallback as d, useMemo as f, useRef as p } from "react";
|
|
4
|
+
import { useAuthToken as m } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
5
5
|
//#region src/bigconsole/assistant/createSandboxAssistantTransport.ts
|
|
6
|
-
var
|
|
7
|
-
function
|
|
6
|
+
var h = "api-calls", g = 42e4, _ = 1500, v = 3e4, y = 200, b = 15, x = 8, S = "bc-assistant-sandbox-id", C = "bc-assistant-session-id";
|
|
7
|
+
function w(e) {
|
|
8
8
|
try {
|
|
9
9
|
return window.sessionStorage.getItem(e);
|
|
10
10
|
} catch {
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function T(e, t) {
|
|
15
15
|
try {
|
|
16
16
|
t ? window.sessionStorage.setItem(e, t) : window.sessionStorage.removeItem(e);
|
|
17
17
|
} catch {}
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function E(e) {
|
|
20
20
|
try {
|
|
21
21
|
let t = e === "workspaceId" ? "burdenoff-active-context-workspace" : "burdenoff-active-context-organization", n = localStorage.getItem(t);
|
|
22
22
|
if (n) return n;
|
|
@@ -28,16 +28,16 @@ function S(e) {
|
|
|
28
28
|
return "";
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
function
|
|
32
|
-
return new URLSearchParams(window.location.search).get("workspace") ??
|
|
31
|
+
function D(e) {
|
|
32
|
+
return new URLSearchParams(window.location.search).get("workspace") ?? E("workspaceId") ?? e ?? "";
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
return new URLSearchParams(window.location.search).get("org") ??
|
|
34
|
+
function O() {
|
|
35
|
+
return new URLSearchParams(window.location.search).get("org") ?? E("organizationId");
|
|
36
36
|
}
|
|
37
|
-
function
|
|
37
|
+
function k(e) {
|
|
38
38
|
return e.info?.role ?? e.role;
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function A(e) {
|
|
41
41
|
let t = e.info?.time?.created;
|
|
42
42
|
if (t !== void 0) return t;
|
|
43
43
|
let n = e.createdAt;
|
|
@@ -48,7 +48,7 @@ function E(e) {
|
|
|
48
48
|
}
|
|
49
49
|
return n;
|
|
50
50
|
}
|
|
51
|
-
function
|
|
51
|
+
function j(e) {
|
|
52
52
|
let t = e.info?.time?.completed;
|
|
53
53
|
if (t !== void 0) return t;
|
|
54
54
|
let n = e.completedAt;
|
|
@@ -60,10 +60,10 @@ function D(e) {
|
|
|
60
60
|
return n;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
function
|
|
63
|
+
function M(e) {
|
|
64
64
|
return (e.parts ?? [] ?? []).filter((e) => e.type === "text" && typeof e.text == "string").map((e) => e.text?.trim() ?? "").filter(Boolean).join("\n") || (typeof e.content == "string" ? e.content.trim() : "");
|
|
65
65
|
}
|
|
66
|
-
var
|
|
66
|
+
var N = {
|
|
67
67
|
bash: "Running a command",
|
|
68
68
|
webfetch: "Fetching a page",
|
|
69
69
|
"file.read": "Reading files",
|
|
@@ -74,83 +74,96 @@ var k = {
|
|
|
74
74
|
todowrite: "Planning the steps",
|
|
75
75
|
todoread: "Reviewing the plan"
|
|
76
76
|
};
|
|
77
|
-
function
|
|
77
|
+
function P(e) {
|
|
78
78
|
return typeof e == "object" && e ? e : void 0;
|
|
79
79
|
}
|
|
80
|
-
function
|
|
81
|
-
let t = e.tool ?? "tool", n =
|
|
80
|
+
function F(e) {
|
|
81
|
+
let t = e.tool ?? "tool", n = P(e.state?.input), r = n?.description;
|
|
82
82
|
if (typeof r == "string" && r.trim()) return r.trim();
|
|
83
83
|
let i = n?.todos;
|
|
84
84
|
if (Array.isArray(i)) {
|
|
85
|
-
let e =
|
|
85
|
+
let e = P(i.find((e) => P(e)?.status === "in_progress") ?? i[0])?.content;
|
|
86
86
|
if (typeof e == "string" && e.trim()) return e.trim();
|
|
87
87
|
}
|
|
88
|
-
return
|
|
88
|
+
return N[t] ?? `Running ${t}`;
|
|
89
89
|
}
|
|
90
|
-
function
|
|
90
|
+
function I(e) {
|
|
91
91
|
return (e.parts ?? []).filter((e) => e.type === "tool" && e.tool).map((e) => {
|
|
92
|
-
let t = e.state?.status ?? "running", n =
|
|
92
|
+
let t = e.state?.status ?? "running", n = F(e);
|
|
93
93
|
return t === "completed" ? `✓ ${n}` : t === "failed" ? `⚠ ${n}` : `⏳ ${n}…`;
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
function
|
|
97
|
-
let i = e.filter((e) =>
|
|
96
|
+
function L(e, t, n, r) {
|
|
97
|
+
let i = e.filter((e) => k(e) === "assistant" && A(e) >= t).sort((e, t) => A(e) - A(t));
|
|
98
98
|
i.length === 0 && e.length > 0 && console.log("[BigConsole-Assistant] buildProgress: no relevant messages", {
|
|
99
99
|
totalMessages: e.length,
|
|
100
100
|
sinceMs: t,
|
|
101
|
-
messageRoles: e.map((e) =>
|
|
102
|
-
messageTimestamps: e.map((e) =>
|
|
101
|
+
messageRoles: e.map((e) => k(e)),
|
|
102
|
+
messageTimestamps: e.map((e) => A(e))
|
|
103
103
|
});
|
|
104
104
|
let a, o;
|
|
105
|
-
if (i.length === 0) a = "
|
|
105
|
+
if (i.length === 0) a = "", o = n === a && r !== void 0 && Date.now() - r >= 15e3;
|
|
106
106
|
else {
|
|
107
|
-
let e = i[i.length - 1], t =
|
|
108
|
-
a = [t, ...s].
|
|
109
|
-
let c = !!
|
|
110
|
-
o = c ||
|
|
107
|
+
let e = i[i.length - 1], t = i.map(M).filter(Boolean), s = I(e);
|
|
108
|
+
a = [...t, ...s].join("\n\n");
|
|
109
|
+
let c = !!j(e) && a.length > 0, l = (e.parts ?? []).some((e) => e.type === "tool" && e.state?.status !== "completed" && e.state?.status !== "failed"), u = !c && !l && n === a && r !== void 0 && Date.now() - r >= 45e3;
|
|
110
|
+
o = c || u;
|
|
111
111
|
}
|
|
112
112
|
return {
|
|
113
113
|
content: a,
|
|
114
114
|
done: o
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
-
function
|
|
117
|
+
function R(e) {
|
|
118
118
|
return e.info?.id ?? e.id;
|
|
119
119
|
}
|
|
120
|
-
var
|
|
121
|
-
function
|
|
120
|
+
var z = ["[Context file:", "User's current context:"];
|
|
121
|
+
function B(e) {
|
|
122
122
|
let t = e.trimStart();
|
|
123
|
-
return
|
|
123
|
+
return z.some((e) => t.startsWith(e));
|
|
124
124
|
}
|
|
125
|
-
function
|
|
126
|
-
let t = [...e].sort((e, t) =>
|
|
125
|
+
function V(e) {
|
|
126
|
+
let t = [...e].sort((e, t) => A(e) - A(t)), n = [], r = null, i = [], a = () => {
|
|
127
127
|
let e = r;
|
|
128
128
|
e && (n.push({
|
|
129
129
|
id: e.id,
|
|
130
130
|
role: "user",
|
|
131
131
|
content: e.content
|
|
132
|
-
}), i && n.push({
|
|
132
|
+
}), i.length > 0 && n.push({
|
|
133
133
|
id: `${e.id}:reply`,
|
|
134
134
|
role: "assistant",
|
|
135
|
-
content:
|
|
136
|
-
}), r = null, i =
|
|
135
|
+
content: G(i.join("\n\n"))
|
|
136
|
+
}), r = null, i = []);
|
|
137
137
|
};
|
|
138
138
|
for (let e of t) {
|
|
139
|
-
let t =
|
|
139
|
+
let t = k(e), o = M(e);
|
|
140
140
|
if (t === "user") {
|
|
141
|
-
if (!o ||
|
|
141
|
+
if (!o || B(o)) continue;
|
|
142
142
|
a(), r = {
|
|
143
|
-
id:
|
|
143
|
+
id: R(e) ?? `user-${String(n.length)}`,
|
|
144
144
|
content: o
|
|
145
145
|
};
|
|
146
|
-
} else t === "assistant" && r && o && (
|
|
146
|
+
} else t === "assistant" && r && o && i.push(o);
|
|
147
147
|
}
|
|
148
148
|
return a(), n;
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
var H = /^AI Assistant\s*[—-]/i, U = 60;
|
|
151
|
+
async function W(e, t, n, r) {
|
|
152
|
+
let i = n.title?.trim();
|
|
153
|
+
if (i && !H.test(i)) return i;
|
|
154
|
+
try {
|
|
155
|
+
let i = [...await a(e, t, n.id, r, x)].sort((e, t) => A(e) - A(t)).filter((e) => k(e) === "user").map(M).find((e) => e && !B(e));
|
|
156
|
+
if (!i) return "New chat";
|
|
157
|
+
let o = i.replace(/\s+/g, " ").trim();
|
|
158
|
+
return o.length > U ? `${o.slice(0, U - 1)}…` : o;
|
|
159
|
+
} catch {
|
|
160
|
+
return i || "Untitled chat";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function G(e) {
|
|
151
164
|
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]");
|
|
152
165
|
}
|
|
153
|
-
function
|
|
166
|
+
function K(e) {
|
|
154
167
|
let t = e instanceof Error ? e.message.toLowerCase() : String(e).toLowerCase();
|
|
155
168
|
return t.includes("rate limit exceeded") || t.includes("unauthorized") || t.includes("k8s api error 401") ? !1 : [
|
|
156
169
|
"sandbox not found",
|
|
@@ -171,154 +184,220 @@ function z(e) {
|
|
|
171
184
|
"unexpected error"
|
|
172
185
|
].some((e) => t.includes(e));
|
|
173
186
|
}
|
|
174
|
-
function
|
|
175
|
-
let { getAccessToken:
|
|
176
|
-
|
|
177
|
-
}, []),
|
|
178
|
-
|
|
179
|
-
}, []), B =
|
|
180
|
-
let a =
|
|
187
|
+
function q() {
|
|
188
|
+
let { getAccessToken: x, getWorkspaceToken: E, userId: j, workspaceId: M } = m(), [N, P] = f(() => [w(S), w(C)], []), F = p(N), I = p(P), R = d((e) => {
|
|
189
|
+
F.current = e, T(S, e);
|
|
190
|
+
}, []), z = d((e) => {
|
|
191
|
+
I.current = e, T(C, e);
|
|
192
|
+
}, []), B = d(async (n, r) => {
|
|
193
|
+
let a = F.current;
|
|
181
194
|
if (console.log("[BigConsole-Assistant] ensureRuntime start", {
|
|
182
195
|
sandboxId: a,
|
|
183
196
|
workspaceId: n
|
|
184
197
|
}), !a) {
|
|
185
|
-
if (!
|
|
186
|
-
if (console.log("[BigConsole-Assistant] findExistingSandbox called"), a = await
|
|
187
|
-
console.log("[BigConsole-Assistant] waitForSandboxReady called (reused)", { sandboxId: a }), await
|
|
198
|
+
if (!x()) throw Error("The assistant requires an authenticated session. Please sign in again.");
|
|
199
|
+
if (console.log("[BigConsole-Assistant] findExistingSandbox called"), a = await i(n, h, r), console.log("[BigConsole-Assistant] findExistingSandbox result", { sandboxId: a }), a) try {
|
|
200
|
+
console.log("[BigConsole-Assistant] waitForSandboxReady called (reused)", { sandboxId: a }), await l(a, n, r), console.log("[BigConsole-Assistant] waitForSandboxReady done (reused)", { sandboxId: a }), console.log("[BigConsole-Assistant] waitForAssistantServiceReady called (reused)", { sandboxId: a }), await c(a, n, h, r), console.log("[BigConsole-Assistant] waitForAssistantServiceReady done (reused)", { sandboxId: a });
|
|
188
201
|
} catch (e) {
|
|
189
202
|
console.warn("[BigConsole-Assistant] existing sandbox unusable, falling back to fresh sandbox", {
|
|
190
203
|
sandboxId: a,
|
|
191
204
|
error: e instanceof Error ? e.message : String(e)
|
|
192
205
|
}), a = null;
|
|
193
206
|
}
|
|
194
|
-
a || (console.log("[BigConsole-Assistant] createAssistantSandbox called"), a = await e(
|
|
207
|
+
a || (console.log("[BigConsole-Assistant] createAssistantSandbox called"), a = await e(h, n, r), console.log("[BigConsole-Assistant] createAssistantSandbox result", { sandboxId: a }), console.log("[BigConsole-Assistant] waitForSandboxReady called (fresh)", { sandboxId: a }), await l(a, n, r), console.log("[BigConsole-Assistant] waitForSandboxReady done (fresh)", { sandboxId: a }), console.log("[BigConsole-Assistant] waitForAssistantServiceReady called (fresh)", { sandboxId: a }), await c(a, n, h, r), console.log("[BigConsole-Assistant] waitForAssistantServiceReady done (fresh)", { sandboxId: a })), R(a);
|
|
195
208
|
}
|
|
196
|
-
let
|
|
209
|
+
let o = I.current;
|
|
197
210
|
return console.log("[BigConsole-Assistant] session check", {
|
|
198
|
-
sessionId:
|
|
211
|
+
sessionId: o,
|
|
199
212
|
sandboxId: a
|
|
200
|
-
}),
|
|
213
|
+
}), o || (console.log("[BigConsole-Assistant] createAssistantSession called", {
|
|
201
214
|
sandboxId: a,
|
|
202
215
|
workspaceId: n
|
|
203
|
-
}),
|
|
216
|
+
}), o = (await t(a, n, h, r)).sessionId, z(o)), {
|
|
204
217
|
sandboxId: a,
|
|
205
|
-
sessionId:
|
|
218
|
+
sessionId: o
|
|
206
219
|
};
|
|
207
|
-
}, [
|
|
208
|
-
let
|
|
220
|
+
}, [x]), H = d(async ({ prompt: e, onProgress: t, signal: n }) => {
|
|
221
|
+
let i = D(M);
|
|
209
222
|
if (console.log("[BigConsole-Assistant] sendPrompt called", {
|
|
210
223
|
promptLength: e.length,
|
|
211
|
-
workspaceId:
|
|
212
|
-
hasCtxWorkspaceId: !!
|
|
224
|
+
workspaceId: i,
|
|
225
|
+
hasCtxWorkspaceId: !!M,
|
|
213
226
|
authContextKeys: {
|
|
214
|
-
hasAccessToken: !!
|
|
215
|
-
hasWorkspaceToken: !!
|
|
216
|
-
hasUserId: !!
|
|
227
|
+
hasAccessToken: !!x(),
|
|
228
|
+
hasWorkspaceToken: !!E(),
|
|
229
|
+
hasUserId: !!j
|
|
217
230
|
},
|
|
218
231
|
locationSearch: window.location.search
|
|
219
|
-
}), !
|
|
220
|
-
let
|
|
221
|
-
accessToken:
|
|
222
|
-
workspaceToken:
|
|
223
|
-
userId:
|
|
224
|
-
organizationId:
|
|
232
|
+
}), !i) throw Error("The assistant needs an active workspace. Open a workspace and try again.");
|
|
233
|
+
let o = {
|
|
234
|
+
accessToken: x(),
|
|
235
|
+
workspaceToken: E(),
|
|
236
|
+
userId: j,
|
|
237
|
+
organizationId: O()
|
|
225
238
|
};
|
|
226
239
|
console.log("[BigConsole-Assistant] authContext prepared", {
|
|
227
|
-
hasAccessToken: !!
|
|
228
|
-
hasWorkspaceToken: !!
|
|
229
|
-
hasUserId: !!
|
|
230
|
-
hasOrgId: !!
|
|
240
|
+
hasAccessToken: !!o.accessToken,
|
|
241
|
+
hasWorkspaceToken: !!o.workspaceToken,
|
|
242
|
+
hasUserId: !!o.userId,
|
|
243
|
+
hasOrgId: !!o.organizationId
|
|
231
244
|
});
|
|
232
|
-
let
|
|
245
|
+
let c = async (l) => {
|
|
233
246
|
try {
|
|
234
|
-
console.log("[BigConsole-Assistant] run attempt",
|
|
235
|
-
let { sandboxId:
|
|
247
|
+
console.log("[BigConsole-Assistant] run attempt", l);
|
|
248
|
+
let { sandboxId: c, sessionId: d } = await B(i, o);
|
|
236
249
|
console.log("[BigConsole-Assistant] ensureRuntime resolved", {
|
|
237
|
-
sandboxId:
|
|
250
|
+
sandboxId: c,
|
|
238
251
|
sessionId: d
|
|
239
252
|
});
|
|
240
253
|
let f = Date.now();
|
|
241
254
|
console.log("[BigConsole-Assistant] calling sendAssistantPromptAsync", {
|
|
242
|
-
sandboxId:
|
|
243
|
-
workspaceId:
|
|
255
|
+
sandboxId: c,
|
|
256
|
+
workspaceId: i,
|
|
244
257
|
sessionId: d,
|
|
245
258
|
promptLength: e.length
|
|
246
|
-
}), await
|
|
247
|
-
let
|
|
248
|
-
for (; Date.now() <
|
|
249
|
-
if (
|
|
250
|
-
let e = await
|
|
259
|
+
}), await s(c, i, d, e, h, u(), o);
|
|
260
|
+
let p = Date.now() + g, m = Date.now(), y = "", b = Date.now(), x = L([], f);
|
|
261
|
+
for (; Date.now() < p;) {
|
|
262
|
+
if (n.aborted) throw Error("Cancelled");
|
|
263
|
+
let e = await a(c, i, d, o, 50);
|
|
251
264
|
if (console.log("[BigConsole-Assistant] poll", {
|
|
252
265
|
elapsedMs: Date.now() - f,
|
|
253
266
|
messageCount: e.length,
|
|
254
|
-
firstFewRoles: e.slice(0, 3).map((e) =>
|
|
255
|
-
firstFewTimestamps: e.slice(0, 3).map((e) =>
|
|
256
|
-
}), x =
|
|
267
|
+
firstFewRoles: e.slice(0, 3).map((e) => k(e)),
|
|
268
|
+
firstFewTimestamps: e.slice(0, 3).map((e) => A(e))
|
|
269
|
+
}), x = L(e, f, y, b), console.log("[BigConsole-Assistant] progress", {
|
|
257
270
|
contentPreview: x.content.slice(0, 100),
|
|
258
271
|
done: x.done,
|
|
259
272
|
lastContentChangeAt: Date.now() - b
|
|
260
|
-
}), x.content !== y && (y = x.content, b = Date.now()), t(
|
|
273
|
+
}), x.content !== y && (y = x.content, b = Date.now()), t(G(x.content)), x.done) {
|
|
261
274
|
console.log("[BigConsole-Assistant] progress.done=true, breaking poll loop");
|
|
262
275
|
break;
|
|
263
276
|
}
|
|
264
|
-
Date.now() -
|
|
277
|
+
Date.now() - m > v && (await r(c, i, 600, o).catch(() => void 0), m = Date.now()), await new Promise((e) => setTimeout(e, _));
|
|
265
278
|
}
|
|
266
279
|
if (!x.done) throw Error("I stopped waiting for a reply, but I may still be working — anything I already created will be there. Check your dashboards and data sinks before asking again, so you do not end up with duplicates.");
|
|
267
|
-
return { text:
|
|
280
|
+
return { text: G(x.content) };
|
|
268
281
|
} catch (e) {
|
|
269
282
|
if (console.error("[BigConsole-Assistant] run error", {
|
|
270
|
-
attempt:
|
|
283
|
+
attempt: l,
|
|
271
284
|
error: e instanceof Error ? e.message : String(e),
|
|
272
285
|
stack: e instanceof Error ? e.stack : void 0,
|
|
273
|
-
sandboxId:
|
|
274
|
-
sessionId:
|
|
275
|
-
}),
|
|
286
|
+
sandboxId: F.current,
|
|
287
|
+
sessionId: I.current
|
|
288
|
+
}), l === 1 && K(e)) return R(null), z(null), c(2);
|
|
276
289
|
throw e;
|
|
277
290
|
}
|
|
278
291
|
};
|
|
279
|
-
return
|
|
292
|
+
return c(1);
|
|
280
293
|
}, [
|
|
281
|
-
|
|
294
|
+
M,
|
|
282
295
|
B,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
]),
|
|
289
|
-
let e =
|
|
296
|
+
x,
|
|
297
|
+
E,
|
|
298
|
+
j,
|
|
299
|
+
R,
|
|
300
|
+
z
|
|
301
|
+
]), U = d(async () => {
|
|
302
|
+
let e = F.current, t = I.current;
|
|
290
303
|
if (!e || !t) return [];
|
|
291
|
-
let n =
|
|
292
|
-
if (!n || !
|
|
304
|
+
let n = D(M);
|
|
305
|
+
if (!n || !x()) return [];
|
|
293
306
|
let r = {
|
|
294
|
-
accessToken:
|
|
295
|
-
workspaceToken:
|
|
296
|
-
userId:
|
|
297
|
-
organizationId:
|
|
307
|
+
accessToken: x(),
|
|
308
|
+
workspaceToken: E(),
|
|
309
|
+
userId: j,
|
|
310
|
+
organizationId: O()
|
|
298
311
|
};
|
|
299
312
|
try {
|
|
300
|
-
return
|
|
313
|
+
return V(await a(e, n, t, r, y));
|
|
301
314
|
} catch (n) {
|
|
302
315
|
return console.warn("[BigConsole-Assistant] could not restore history; starting fresh", {
|
|
303
316
|
sandboxId: e,
|
|
304
317
|
sessionId: t,
|
|
305
318
|
error: n instanceof Error ? n.message : String(n)
|
|
306
|
-
}),
|
|
319
|
+
}), R(null), z(null), [];
|
|
320
|
+
}
|
|
321
|
+
}, [
|
|
322
|
+
M,
|
|
323
|
+
x,
|
|
324
|
+
E,
|
|
325
|
+
j,
|
|
326
|
+
R,
|
|
327
|
+
z
|
|
328
|
+
]), q = d(() => ({
|
|
329
|
+
accessToken: x(),
|
|
330
|
+
workspaceToken: E(),
|
|
331
|
+
userId: j,
|
|
332
|
+
organizationId: O()
|
|
333
|
+
}), [
|
|
334
|
+
x,
|
|
335
|
+
E,
|
|
336
|
+
j
|
|
337
|
+
]), J = d(async () => {
|
|
338
|
+
let e = F.current, t = D(M);
|
|
339
|
+
if (!e || !t) return [];
|
|
340
|
+
try {
|
|
341
|
+
let n = await o(e, t, q()), r = q(), i = n.map((e) => ({
|
|
342
|
+
session: e,
|
|
343
|
+
updatedAt: e.time?.updated ?? e.time?.created
|
|
344
|
+
})).sort((e, t) => (t.updatedAt ?? 0) - (e.updatedAt ?? 0)).slice(0, b);
|
|
345
|
+
return await Promise.all(i.map(async ({ session: n, updatedAt: i }) => ({
|
|
346
|
+
id: n.id,
|
|
347
|
+
title: await W(e, t, n, r),
|
|
348
|
+
updatedAt: i,
|
|
349
|
+
active: n.id === I.current
|
|
350
|
+
})));
|
|
351
|
+
} catch {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
}, [M, q]), Y = d(async () => {
|
|
355
|
+
let e = F.current, n = D(M);
|
|
356
|
+
if (!e || !n) {
|
|
357
|
+
z(null);
|
|
358
|
+
return;
|
|
307
359
|
}
|
|
360
|
+
let { sessionId: r } = await t(e, n, h, q());
|
|
361
|
+
z(r);
|
|
308
362
|
}, [
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
F,
|
|
314
|
-
|
|
363
|
+
M,
|
|
364
|
+
q,
|
|
365
|
+
z
|
|
366
|
+
]), X = d(async () => {
|
|
367
|
+
let e = F.current, t = I.current, r = D(M);
|
|
368
|
+
e && t && r && await n(e, r, t, q()).catch(() => void 0), z(null), await Y();
|
|
369
|
+
}, [
|
|
370
|
+
M,
|
|
371
|
+
q,
|
|
372
|
+
z,
|
|
373
|
+
Y
|
|
374
|
+
]), Z = d(async (e) => {
|
|
375
|
+
let t = F.current, n = D(M);
|
|
376
|
+
if (!t || !n) return [];
|
|
377
|
+
let r = await a(t, n, e, q(), y);
|
|
378
|
+
return z(e), V(r);
|
|
379
|
+
}, [
|
|
380
|
+
M,
|
|
381
|
+
q,
|
|
382
|
+
z
|
|
383
|
+
]);
|
|
384
|
+
return f(() => ({
|
|
385
|
+
sendPrompt: H,
|
|
386
|
+
loadHistory: U,
|
|
387
|
+
listSessions: J,
|
|
388
|
+
newSession: Y,
|
|
389
|
+
clearSession: X,
|
|
390
|
+
selectSession: Z
|
|
391
|
+
}), [
|
|
392
|
+
H,
|
|
393
|
+
U,
|
|
394
|
+
J,
|
|
395
|
+
Y,
|
|
396
|
+
X,
|
|
397
|
+
Z
|
|
315
398
|
]);
|
|
316
|
-
return u(() => ({
|
|
317
|
-
sendPrompt: V,
|
|
318
|
-
loadHistory: H
|
|
319
|
-
}), [V, H]);
|
|
320
399
|
}
|
|
321
400
|
//#endregion
|
|
322
|
-
export {
|
|
401
|
+
export { q as useSandboxAssistantTransport };
|
|
323
402
|
|
|
324
403
|
//# sourceMappingURL=createSandboxAssistantTransport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../../src/bigconsole/assistant/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * BigConsole adapter for the shared fe-libs AssistantWidget.\n *\n * The floating widget (fe-libs, Layer 1) is backend-agnostic — it calls an\n * injected `AssistantTransport`. This hook builds a transport that drives the\n * sandbox AI assistant (combined `assistant` mode = docs Q&A + api-calls): it\n * provisions/reuses a sandbox, opens a session, dispatches the prompt async,\n * then polls for the streamed answer. The agent introspects the GraphQL schema\n * and performs API calls on the user's behalf using their workspace token,\n * contextual to the current screen (via `gatherPageContext`).\n *\n * This is a faithful port of microfe-vibecontrols'\n * `services/createSandboxAssistantTransport.ts`; the only product-specific\n * difference lives in `assistantApi.createAssistantSandbox`\n * (`AI_ASSISTANT_PRODUCT=bigconsole`).\n */\n\nimport { useCallback, useMemo, useRef } from 'react';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantMessages,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n} from './assistantApi';\nimport { gatherPageContext } from './pageContext';\nimport type { AssistantMode, AssistantRawMessage, AssistantRawMessagePart, AssistantSandboxAuthContext } from './types';\n\n/**\n * Locally-defined mirror of the fe-libs `AssistantTransport` contract.\n *\n * Intentionally NOT imported from `@burdenoff/fe-libs`: microfe's tsconfig maps\n * `@burdenoff/fe-libs/*` to fe-libs *source*, so vite-plugin-dts would rewrite a\n * cross-package type used in this hook's public signature to a broken\n * source-relative path in the emitted `.d.ts`. Structural typing makes this\n * shape assignable to fe-libs' `AssistantTransport` at the call site\n * (bigconsole-app's AppShell), which is where compatibility is enforced.\n */\ninterface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\n/** Mirror of fe-libs' `AssistantWidgetMessage` (see note above on why). */\ninterface AssistantHistoryMessage {\n id: string;\n role: 'user' | 'assistant';\n content: string;\n pending?: boolean;\n error?: boolean;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n loadHistory: () => Promise<AssistantHistoryMessage[]>;\n}\n\n// BigConsole still boots the manually-tagged ACA image\n// `alpha-delegated-auth-v11` for the assistant sandbox. The historical\n// platform notes show that this image line reliably supports `api-calls`, while\n// the combined `assistant` mode depends on newer image contracts that are not\n// yet guaranteed on this tag. Use `api-calls` here so the assistant can execute\n// workspace GraphQL operations end-to-end right now. Once the underlying image\n// line is rebuilt and verified for combined mode, this can be switched back.\nconst MODE: AssistantMode = 'api-calls';\n// How long the UI will follow a single turn.\n//\n// This was 180s, which was SHORTER THAN THE WORK. A full \"create a school\n// attendance dashboard\" build — datasink → dashboard → parser → widget, each a\n// separate gateway call preceded by a model round-trip — measured 229s in prod.\n// So the agent finished, the dashboard genuinely existed, and the user was still\n// shown \"the assistant timed out\". That is worse than cosmetic: people retry and\n// end up with duplicate dashboards.\n//\n// 7 minutes covers the observed worst case with headroom. It costs nothing on\n// fast turns (we stop the moment the turn reports done), and the backend keeps\n// pace — the sandbox TTL is extended every TTL_EXTEND_INTERVAL_MS.\nconst STREAM_BUDGET_MS = 420_000;\nconst POLL_INTERVAL_MS = 1500;\nconst TTL_EXTEND_INTERVAL_MS = 30_000;\n// Raw agent messages per restore. The agent emits one message per internal\n// step, so a handful of turns is already dozens of messages — this is a cap on\n// the RAW fetch, not on the number of restored turns.\nconst HISTORY_MESSAGE_LIMIT = 200;\n\nconst SANDBOX_ID_KEY = 'bc-assistant-sandbox-id';\nconst SESSION_ID_KEY = 'bc-assistant-session-id';\n\nfunction readStoredId(key: string): string | null {\n try {\n return window.sessionStorage.getItem(key);\n } catch {\n // sessionStorage unavailable (private mode) — degrade to a fresh session.\n return null;\n }\n}\n\nfunction writeStoredId(key: string, id: string | null): void {\n try {\n if (id) window.sessionStorage.setItem(key, id);\n else window.sessionStorage.removeItem(key);\n } catch {\n // Non-fatal: we simply lose cross-reload continuity.\n }\n}\n\n// ── Context helpers (mirror vibecontrols' resolution) ────────────────\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId' ? 'burdenoff-active-context-workspace' : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as { workspaceId?: string; organizationId?: string };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(fallback: string | null): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId') ?? fallback ?? '';\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\n// ── Message-progress helpers (pure; mirror vibecontrols) ─────────────\n\nfunction getMessageRole(message: AssistantRawMessage): string | undefined {\n // Check nested format first, then flat format\n return message.info?.role ?? message.role;\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n // Check nested format first (epoch ms), then flat format (ISO string or epoch ms)\n const nested = message.info?.time?.created;\n if (nested !== undefined) return nested;\n const flat = message.createdAt;\n if (flat === undefined) return 0;\n // If it's a string (ISO), parse it; otherwise treat as epoch ms\n if (typeof flat === 'string') {\n const parsed = Date.parse(flat);\n return isNaN(parsed) ? 0 : parsed;\n }\n return flat;\n}\n\nfunction getMessageCompleted(message: AssistantRawMessage): number | undefined {\n // Check nested format first, then flat format\n const nested = message.info?.time?.completed;\n if (nested !== undefined) return nested;\n const flat = message.completedAt;\n if (flat === undefined) return undefined;\n if (typeof flat === 'string') {\n const parsed = Date.parse(flat);\n return isNaN(parsed) ? undefined : parsed;\n }\n return flat;\n}\n\nfunction getAssistantText(message: AssistantRawMessage): string {\n // Check parts format first (nested), then flat content\n const parts = message.parts ?? [];\n const textFromParts = (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n if (textFromParts) return textFromParts;\n // Fallback to flat content field\n return typeof message.content === 'string' ? message.content.trim() : '';\n}\n\n// Friendly, human-readable labels for the agent's tools so the progress line\n// reads like \"Searching the schema…\" instead of \"Running: bash\". The agent sets\n// a `description` on every bash call (e.g. \"Search for sales-related types in\n// workspace schema\") and a todo list on todowrite — surface those directly.\nconst TOOL_LABELS: Record<string, string> = {\n bash: 'Running a command',\n webfetch: 'Fetching a page',\n 'file.read': 'Reading files',\n 'file.write': 'Writing files',\n 'file.edit': 'Editing files',\n 'file.find.text': 'Searching the code',\n 'file.find.file': 'Looking for files',\n todowrite: 'Planning the steps',\n todoread: 'Reviewing the plan',\n};\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : undefined;\n}\n\n/** Best-effort human summary of what a single tool part is doing right now. */\nfunction describeToolPart(part: AssistantRawMessagePart): string {\n const tool = part.tool ?? 'tool';\n const input = asRecord(part.state?.input);\n\n // bash carries a plain-English `description` of the step — the best signal.\n const description = input?.description;\n if (typeof description === 'string' && description.trim()) return description.trim();\n\n // todowrite carries the todo list — surface the item being worked on.\n const todos = input?.todos;\n if (Array.isArray(todos)) {\n const active = todos.find((todo) => asRecord(todo)?.status === 'in_progress') ?? todos[0];\n const content = asRecord(active)?.content;\n if (typeof content === 'string' && content.trim()) return content.trim();\n }\n\n return TOOL_LABELS[tool] ?? `Running ${tool}`;\n}\n\nfunction getToolProgress(message: AssistantRawMessage): string[] {\n const parts = message.parts ?? [];\n return parts\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const label = describeToolPart(part);\n if (status === 'completed') return `✓ ${label}`;\n if (status === 'failed') return `⚠ ${label}`;\n return `⏳ ${label}…`;\n });\n}\n\nfunction buildProgress(\n messages: AssistantRawMessage[],\n sinceMs: number,\n previousContent?: string,\n previousContentAtMs?: number\n): { content: string; done: boolean } {\n const relevant = messages\n .filter((message) => getMessageRole(message) === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs)\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n // Debug: log message filtering when no relevant messages found\n if (relevant.length === 0 && messages.length > 0) {\n console.log('[BigConsole-Assistant] buildProgress: no relevant messages', {\n totalMessages: messages.length,\n sinceMs,\n messageRoles: messages.map((m) => getMessageRole(m)),\n messageTimestamps: messages.map((m) => getRawMessageCreatedAt(m)),\n });\n }\n\n let content: string;\n let done: boolean;\n\n if (relevant.length === 0) {\n content = 'Thinking…';\n // Staleness fallback: if no messages have appeared after 15+ seconds of polling,\n // treat the response as complete even when messages get filtered out\n // (e.g., due to timestamp skew or role mismatch).\n done =\n previousContent === content && previousContentAtMs !== undefined && Date.now() - previousContentAtMs >= 15_000;\n } else {\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest);\n const toolProgress = getToolProgress(latest);\n content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking…';\n\n const officiallyDone = Boolean(getMessageCompleted(latest)) && (Boolean(text) || toolProgress.length > 0);\n\n // Staleness fallback: if content hasn't changed in 15 s, treat the build as\n // complete even when the assistant forgot to set `completed`.\n const staleDone =\n !officiallyDone &&\n previousContent === content &&\n previousContentAtMs !== undefined &&\n Date.now() - previousContentAtMs >= 15_000;\n\n done = officiallyDone || staleDone;\n }\n\n return { content, done };\n}\n\nfunction getRawMessageId(message: AssistantRawMessage): string | undefined {\n return message.info?.id ?? message.id;\n}\n\n/**\n * The host injects synthetic \"user\" messages into the session (the auth-context\n * file, and a `User's current context:` block describing the open screen). They\n * are plumbing, not something the human typed, so they must never be replayed\n * into the visible transcript.\n */\nconst INJECTED_USER_PREFIXES = ['[Context file:', \"User's current context:\"];\n\nfunction isInjectedContextMessage(text: string): boolean {\n const head = text.trimStart();\n return INJECTED_USER_PREFIXES.some((prefix) => head.startsWith(prefix));\n}\n\n/**\n * Collapse a raw agent session into the user-visible conversation.\n *\n * The agent emits a message per internal step — narration (\"Let me grep the\n * schema…\"), tool calls, step markers — so replaying every assistant message\n * would dump its scratchpad into the chat. For each user turn we keep only the\n * LAST assistant message that carried text, which is its final answer. That is\n * exactly what the widget rendered live, so a restored conversation is\n * indistinguishable from the one the user was looking at before the reload.\n *\n * Turn boundaries are derived from ordering rather than `parentID` so this also\n * works for backends that return the flat message shape.\n */\nfunction mapSessionToHistory(raw: AssistantRawMessage[]): AssistantHistoryMessage[] {\n const ordered = [...raw].sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n const history: AssistantHistoryMessage[] = [];\n let pendingUser: { id: string; content: string } | null = null;\n let finalAnswer = '';\n\n const flushTurn = (): void => {\n const turn = pendingUser;\n if (!turn) return;\n history.push({ id: turn.id, role: 'user', content: turn.content });\n if (finalAnswer) {\n history.push({ id: `${turn.id}:reply`, role: 'assistant', content: sanitize(finalAnswer) });\n }\n pendingUser = null;\n finalAnswer = '';\n };\n\n for (const message of ordered) {\n const role = getMessageRole(message);\n const text = getAssistantText(message);\n\n if (role === 'user') {\n if (!text || isInjectedContextMessage(text)) continue;\n flushTurn();\n pendingUser = {\n id: getRawMessageId(message) ?? `user-${String(history.length)}`,\n content: text,\n };\n } else if (role === 'assistant' && pendingUser && text) {\n // Overwrite as the turn progresses — we end up holding the final answer.\n finalAnswer = text;\n }\n }\n flushTurn();\n\n return history;\n}\n\nfunction sanitize(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nfunction isRecoverable(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'unable to connect',\n 'image pull',\n 'container failed',\n 'bootstrap failed',\n // Transient network errors from the browser fetch — gateway CORS preflight\n // failures, mid-stream resets, and Cloudflare 524s all surface as\n // \"Failed to fetch\" via TypeError. They're worth retrying on a clean\n // runtime since the underlying sandbox state is unaffected.\n 'failed to fetch',\n 'cf-proxy timeout',\n // Subgraph returned 500 with a generic message — gateway returns this as\n // a GraphQL error rather than an HTTP error. The actual underlying cause\n // (e.g., transient Prisma timeout) is recoverable, but a fresh sandbox\n // may be needed.\n 'unexpected error',\n ].some((fragment) => message.includes(fragment));\n}\n\n/**\n * Returns a memoized `AssistantTransport` wired to the BigConsole sandbox\n * assistant agent. The sandbox + session are cached in refs so follow-up turns\n * reuse the warm environment for the lifetime of the host shell.\n */\nexport function useSandboxAssistantTransport(): AssistantTransport {\n const { getAccessToken, getWorkspaceToken, userId, workspaceId: ctxWorkspaceId } = useAuthToken();\n\n // Rehydrate the sandbox + session ids persisted by the previous page\n // lifecycle. These were being WRITTEN to sessionStorage but never read back,\n // so every reload silently opened a brand-new agent session: the chat looked\n // empty AND the agent genuinely lost the conversation (it could no longer\n // resolve \"that datasink\" / \"the dashboard you just made\").\n //\n // Restoring both together is what makes history real rather than cosmetic —\n // the transcript we replay into the UI is the same session the agent will\n // keep reasoning over. A stale/expired sandbox is not a problem: `sendPrompt`\n // already treats that as recoverable, drops the refs, and retries clean.\n const [initialSandboxId, initialSessionId] = useMemo(\n () => [readStoredId(SANDBOX_ID_KEY), readStoredId(SESSION_ID_KEY)] as const,\n []\n );\n\n const sandboxIdRef = useRef<string | null>(initialSandboxId);\n const sessionIdRef = useRef<string | null>(initialSessionId);\n\n const persistSandboxId = useCallback((id: string | null) => {\n sandboxIdRef.current = id;\n writeStoredId(SANDBOX_ID_KEY, id);\n }, []);\n\n const persistSessionId = useCallback((id: string | null) => {\n sessionIdRef.current = id;\n writeStoredId(SESSION_ID_KEY, id);\n }, []);\n\n const ensureRuntime = useCallback(\n async (\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let sandboxId = sandboxIdRef.current;\n console.log('[BigConsole-Assistant] ensureRuntime start', { sandboxId, workspaceId });\n if (!sandboxId) {\n if (!getAccessToken()) {\n throw new Error('The assistant requires an authenticated session. Please sign in again.');\n }\n console.log('[BigConsole-Assistant] findExistingSandbox called');\n sandboxId = await findExistingSandbox(workspaceId, MODE, authContext);\n console.log('[BigConsole-Assistant] findExistingSandbox result', { sandboxId });\n\n if (sandboxId) {\n try {\n console.log('[BigConsole-Assistant] waitForSandboxReady called (reused)', { sandboxId });\n await waitForSandboxReady(sandboxId, workspaceId, authContext);\n console.log('[BigConsole-Assistant] waitForSandboxReady done (reused)', { sandboxId });\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady called (reused)', { sandboxId });\n await waitForAssistantServiceReady(sandboxId, workspaceId, MODE, authContext);\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady done (reused)', { sandboxId });\n } catch (error) {\n console.warn('[BigConsole-Assistant] 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 console.log('[BigConsole-Assistant] createAssistantSandbox called');\n sandboxId = await createAssistantSandbox(MODE, workspaceId, authContext);\n console.log('[BigConsole-Assistant] createAssistantSandbox result', { sandboxId });\n console.log('[BigConsole-Assistant] waitForSandboxReady called (fresh)', { sandboxId });\n await waitForSandboxReady(sandboxId, workspaceId, authContext);\n console.log('[BigConsole-Assistant] waitForSandboxReady done (fresh)', { sandboxId });\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady called (fresh)', { sandboxId });\n await waitForAssistantServiceReady(sandboxId, workspaceId, MODE, authContext);\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady done (fresh)', { sandboxId });\n }\n\n persistSandboxId(sandboxId);\n }\n\n let sessionId = sessionIdRef.current;\n console.log('[BigConsole-Assistant] session check', { sessionId, sandboxId });\n if (!sessionId) {\n console.log('[BigConsole-Assistant] createAssistantSession called', { sandboxId, workspaceId });\n const result = await createAssistantSession(sandboxId, workspaceId, MODE, authContext);\n sessionId = result.sessionId;\n persistSessionId(sessionId);\n }\n\n return { sandboxId, sessionId };\n },\n [getAccessToken]\n );\n\n const sendPrompt = useCallback(\n async ({ prompt, onProgress, signal }: AssistantSendArgs): Promise<{ text: string }> => {\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n console.log('[BigConsole-Assistant] sendPrompt called', {\n promptLength: prompt.length,\n workspaceId,\n hasCtxWorkspaceId: !!ctxWorkspaceId,\n authContextKeys: {\n hasAccessToken: !!getAccessToken(),\n hasWorkspaceToken: !!getWorkspaceToken(),\n hasUserId: !!userId,\n },\n locationSearch: window.location.search,\n });\n if (!workspaceId) {\n throw new Error('The assistant needs an active workspace. Open a workspace and try again.');\n }\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n console.log('[BigConsole-Assistant] authContext prepared', {\n hasAccessToken: !!authContext.accessToken,\n hasWorkspaceToken: !!authContext.workspaceToken,\n hasUserId: !!authContext.userId,\n hasOrgId: !!authContext.organizationId,\n });\n\n const run = async (attempt: 1 | 2): Promise<{ text: string }> => {\n try {\n console.log('[BigConsole-Assistant] run attempt', attempt);\n const { sandboxId, sessionId } = await ensureRuntime(workspaceId, authContext);\n console.log('[BigConsole-Assistant] ensureRuntime resolved', { sandboxId, sessionId });\n\n const startedAt = Date.now();\n console.log('[BigConsole-Assistant] calling sendAssistantPromptAsync', {\n sandboxId,\n workspaceId,\n sessionId,\n promptLength: prompt.length,\n });\n await sendAssistantPromptAsync(\n sandboxId,\n workspaceId,\n sessionId,\n prompt,\n MODE,\n gatherPageContext(),\n authContext\n );\n\n const timeoutAt = Date.now() + STREAM_BUDGET_MS;\n let lastTtlExtensionAt = Date.now();\n let lastContent = '';\n let lastContentChangeAt = Date.now();\n let progress = buildProgress([], startedAt);\n\n while (Date.now() < timeoutAt) {\n if (signal.aborted) throw new Error('Cancelled');\n\n const messages = await getAssistantMessages(sandboxId, workspaceId, sessionId, authContext, 50);\n console.log('[BigConsole-Assistant] poll', {\n elapsedMs: Date.now() - startedAt,\n messageCount: messages.length,\n firstFewRoles: messages.slice(0, 3).map((m) => getMessageRole(m)),\n firstFewTimestamps: messages.slice(0, 3).map((m) => getRawMessageCreatedAt(m)),\n });\n progress = buildProgress(messages, startedAt, lastContent, lastContentChangeAt);\n console.log('[BigConsole-Assistant] progress', {\n contentPreview: progress.content.slice(0, 100),\n done: progress.done,\n lastContentChangeAt: Date.now() - lastContentChangeAt,\n });\n if (progress.content !== lastContent) {\n lastContent = progress.content;\n lastContentChangeAt = Date.now();\n }\n onProgress(sanitize(progress.content));\n if (progress.done) {\n console.log('[BigConsole-Assistant] progress.done=true, breaking poll loop');\n break;\n }\n\n if (Date.now() - lastTtlExtensionAt > TTL_EXTEND_INTERVAL_MS) {\n await extendAssistantSandboxTTL(sandboxId, workspaceId, 600, authContext).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n if (!progress.done) {\n // Do NOT say \"please try again\". We stopped watching; the agent did\n // not stop working, and anything it already created is real. Telling\n // people to retry is how you get duplicate dashboards.\n throw new Error(\n 'I stopped waiting for a reply, but I may still be working — anything I already created will be there. Check your dashboards and data sinks before asking again, so you do not end up with duplicates.'\n );\n }\n\n return { text: sanitize(progress.content) };\n } catch (error) {\n console.error('[BigConsole-Assistant] run error', {\n attempt,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n sandboxId: sandboxIdRef.current,\n sessionId: sessionIdRef.current,\n });\n // A stale/expired sandbox or session is recoverable — drop the warm\n // refs and retry once from a clean runtime.\n if (attempt === 1 && isRecoverable(error)) {\n persistSandboxId(null);\n persistSessionId(null);\n return run(2);\n }\n throw error;\n }\n };\n\n return run(1);\n },\n [ctxWorkspaceId, ensureRuntime, getAccessToken, getWorkspaceToken, userId, persistSandboxId, persistSessionId]\n );\n\n /**\n * Rebuild the visible conversation from the agent's own session.\n *\n * The sandbox is the source of truth for the transcript, so we replay from it\n * rather than mirroring messages into localStorage: the UI can then never\n * show a history the agent doesn't actually have. Called by the widget the\n * first time it opens; a no-op on a first-ever visit.\n */\n const loadHistory = useCallback(async (): Promise<AssistantHistoryMessage[]> => {\n const sandboxId = sandboxIdRef.current;\n const sessionId = sessionIdRef.current;\n if (!sandboxId || !sessionId) return [];\n\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!workspaceId || !getAccessToken()) return [];\n\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n try {\n const raw = await getAssistantMessages(sandboxId, workspaceId, sessionId, authContext, HISTORY_MESSAGE_LIMIT);\n return mapSessionToHistory(raw);\n } catch (error) {\n // The session is gone — sandbox TTL expired, or the workspace was reset.\n // Drop the stale ids so the next prompt provisions a clean runtime instead\n // of repeatedly failing against a dead session.\n console.warn('[BigConsole-Assistant] could not restore history; starting fresh', {\n sandboxId,\n sessionId,\n error: error instanceof Error ? error.message : String(error),\n });\n persistSandboxId(null);\n persistSessionId(null);\n return [];\n }\n }, [ctxWorkspaceId, getAccessToken, getWorkspaceToken, userId, persistSandboxId, persistSessionId]);\n\n return useMemo<AssistantTransport>(() => ({ sendPrompt, loadHistory }), [sendPrompt, loadHistory]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../../src/bigconsole/assistant/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * BigConsole adapter for the shared fe-libs AssistantWidget.\n *\n * The floating widget (fe-libs, Layer 1) is backend-agnostic — it calls an\n * injected `AssistantTransport`. This hook builds a transport that drives the\n * sandbox AI assistant (combined `assistant` mode = docs Q&A + api-calls): it\n * provisions/reuses a sandbox, opens a session, dispatches the prompt async,\n * then polls for the streamed answer. The agent introspects the GraphQL schema\n * and performs API calls on the user's behalf using their workspace token,\n * contextual to the current screen (via `gatherPageContext`).\n *\n * This is a faithful port of microfe-vibecontrols'\n * `services/createSandboxAssistantTransport.ts`; the only product-specific\n * difference lives in `assistantApi.createAssistantSandbox`\n * (`AI_ASSISTANT_PRODUCT=bigconsole`).\n */\n\nimport { useCallback, useMemo, useRef } from 'react';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n createAssistantSandbox,\n createAssistantSession,\n deleteAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantMessages,\n listAssistantSessions,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n} from './assistantApi';\nimport { gatherPageContext } from './pageContext';\nimport type {\n AssistantMode,\n AssistantRawMessage,\n AssistantRawMessagePart,\n AssistantRawSession,\n AssistantSandboxAuthContext,\n} from './types';\n\n/**\n * Locally-defined mirror of the fe-libs `AssistantTransport` contract.\n *\n * Intentionally NOT imported from `@burdenoff/fe-libs`: microfe's tsconfig maps\n * `@burdenoff/fe-libs/*` to fe-libs *source*, so vite-plugin-dts would rewrite a\n * cross-package type used in this hook's public signature to a broken\n * source-relative path in the emitted `.d.ts`. Structural typing makes this\n * shape assignable to fe-libs' `AssistantTransport` at the call site\n * (bigconsole-app's AppShell), which is where compatibility is enforced.\n */\ninterface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\n/** Mirror of fe-libs' `AssistantWidgetMessage` (see note above on why). */\ninterface AssistantHistoryMessage {\n id: string;\n role: 'user' | 'assistant';\n content: string;\n pending?: boolean;\n error?: boolean;\n}\n\n/** Mirror of fe-libs' `AssistantSessionSummary` (see note above on why). */\ninterface AssistantSessionSummaryLocal {\n id: string;\n title: string;\n updatedAt?: number;\n active?: boolean;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n loadHistory: () => Promise<AssistantHistoryMessage[]>;\n listSessions: () => Promise<AssistantSessionSummaryLocal[]>;\n newSession: () => Promise<void>;\n clearSession: () => Promise<void>;\n selectSession: (sessionId: string) => Promise<AssistantHistoryMessage[]>;\n}\n\n// BigConsole still boots the manually-tagged ACA image\n// `alpha-delegated-auth-v11` for the assistant sandbox. The historical\n// platform notes show that this image line reliably supports `api-calls`, while\n// the combined `assistant` mode depends on newer image contracts that are not\n// yet guaranteed on this tag. Use `api-calls` here so the assistant can execute\n// workspace GraphQL operations end-to-end right now. Once the underlying image\n// line is rebuilt and verified for combined mode, this can be switched back.\nconst MODE: AssistantMode = 'api-calls';\n// How long the UI will follow a single turn.\n//\n// This was 180s, which was SHORTER THAN THE WORK. A full \"create a school\n// attendance dashboard\" build — datasink → dashboard → parser → widget, each a\n// separate gateway call preceded by a model round-trip — measured 229s in prod.\n// So the agent finished, the dashboard genuinely existed, and the user was still\n// shown \"the assistant timed out\". That is worse than cosmetic: people retry and\n// end up with duplicate dashboards.\n//\n// 7 minutes covers the observed worst case with headroom. It costs nothing on\n// fast turns (we stop the moment the turn reports done), and the backend keeps\n// pace — the sandbox TTL is extended every TTL_EXTEND_INTERVAL_MS.\nconst STREAM_BUDGET_MS = 420_000;\nconst POLL_INTERVAL_MS = 1500;\nconst TTL_EXTEND_INTERVAL_MS = 30_000;\n// Raw agent messages per restore. The agent emits one message per internal\n// step, so a handful of turns is already dozens of messages — this is a cap on\n// the RAW fetch, not on the number of restored turns.\nconst HISTORY_MESSAGE_LIMIT = 200;\n// The History panel fetches one extra round-trip per chat to title it, so cap\n// how many chats we title at once. Older chats simply are not listed.\nconst SESSION_LIST_LIMIT = 15;\n// Only the first few messages are needed to find the first user prompt.\nconst SESSION_TITLE_PROBE_LIMIT = 8;\n\nconst SANDBOX_ID_KEY = 'bc-assistant-sandbox-id';\nconst SESSION_ID_KEY = 'bc-assistant-session-id';\n\nfunction readStoredId(key: string): string | null {\n try {\n return window.sessionStorage.getItem(key);\n } catch {\n // sessionStorage unavailable (private mode) — degrade to a fresh session.\n return null;\n }\n}\n\nfunction writeStoredId(key: string, id: string | null): void {\n try {\n if (id) window.sessionStorage.setItem(key, id);\n else window.sessionStorage.removeItem(key);\n } catch {\n // Non-fatal: we simply lose cross-reload continuity.\n }\n}\n\n// ── Context helpers (mirror vibecontrols' resolution) ────────────────\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId' ? 'burdenoff-active-context-workspace' : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as { workspaceId?: string; organizationId?: string };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(fallback: string | null): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId') ?? fallback ?? '';\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\n// ── Message-progress helpers (pure; mirror vibecontrols) ─────────────\n\nfunction getMessageRole(message: AssistantRawMessage): string | undefined {\n // Check nested format first, then flat format\n return message.info?.role ?? message.role;\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n // Check nested format first (epoch ms), then flat format (ISO string or epoch ms)\n const nested = message.info?.time?.created;\n if (nested !== undefined) return nested;\n const flat = message.createdAt;\n if (flat === undefined) return 0;\n // If it's a string (ISO), parse it; otherwise treat as epoch ms\n if (typeof flat === 'string') {\n const parsed = Date.parse(flat);\n return isNaN(parsed) ? 0 : parsed;\n }\n return flat;\n}\n\nfunction getMessageCompleted(message: AssistantRawMessage): number | undefined {\n // Check nested format first, then flat format\n const nested = message.info?.time?.completed;\n if (nested !== undefined) return nested;\n const flat = message.completedAt;\n if (flat === undefined) return undefined;\n if (typeof flat === 'string') {\n const parsed = Date.parse(flat);\n return isNaN(parsed) ? undefined : parsed;\n }\n return flat;\n}\n\nfunction getAssistantText(message: AssistantRawMessage): string {\n // Check parts format first (nested), then flat content\n const parts = message.parts ?? [];\n const textFromParts = (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n if (textFromParts) return textFromParts;\n // Fallback to flat content field\n return typeof message.content === 'string' ? message.content.trim() : '';\n}\n\n// Friendly, human-readable labels for the agent's tools so the progress line\n// reads like \"Searching the schema…\" instead of \"Running: bash\". The agent sets\n// a `description` on every bash call (e.g. \"Search for sales-related types in\n// workspace schema\") and a todo list on todowrite — surface those directly.\nconst TOOL_LABELS: Record<string, string> = {\n bash: 'Running a command',\n webfetch: 'Fetching a page',\n 'file.read': 'Reading files',\n 'file.write': 'Writing files',\n 'file.edit': 'Editing files',\n 'file.find.text': 'Searching the code',\n 'file.find.file': 'Looking for files',\n todowrite: 'Planning the steps',\n todoread: 'Reviewing the plan',\n};\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : undefined;\n}\n\n/** Best-effort human summary of what a single tool part is doing right now. */\nfunction describeToolPart(part: AssistantRawMessagePart): string {\n const tool = part.tool ?? 'tool';\n const input = asRecord(part.state?.input);\n\n // bash carries a plain-English `description` of the step — the best signal.\n const description = input?.description;\n if (typeof description === 'string' && description.trim()) return description.trim();\n\n // todowrite carries the todo list — surface the item being worked on.\n const todos = input?.todos;\n if (Array.isArray(todos)) {\n const active = todos.find((todo) => asRecord(todo)?.status === 'in_progress') ?? todos[0];\n const content = asRecord(active)?.content;\n if (typeof content === 'string' && content.trim()) return content.trim();\n }\n\n return TOOL_LABELS[tool] ?? `Running ${tool}`;\n}\n\nfunction getToolProgress(message: AssistantRawMessage): string[] {\n const parts = message.parts ?? [];\n return parts\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const label = describeToolPart(part);\n if (status === 'completed') return `✓ ${label}`;\n if (status === 'failed') return `⚠ ${label}`;\n return `⏳ ${label}…`;\n });\n}\n\nfunction buildProgress(\n messages: AssistantRawMessage[],\n sinceMs: number,\n previousContent?: string,\n previousContentAtMs?: number\n): { content: string; done: boolean } {\n const relevant = messages\n .filter((message) => getMessageRole(message) === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs)\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n // Debug: log message filtering when no relevant messages found\n if (relevant.length === 0 && messages.length > 0) {\n console.log('[BigConsole-Assistant] buildProgress: no relevant messages', {\n totalMessages: messages.length,\n sinceMs,\n messageRoles: messages.map((m) => getMessageRole(m)),\n messageTimestamps: messages.map((m) => getRawMessageCreatedAt(m)),\n });\n }\n\n let content: string;\n let done: boolean;\n\n if (relevant.length === 0) {\n content = '';\n // Staleness fallback: if no messages have appeared after 15+ seconds of polling,\n // treat the response as complete even when messages get filtered out\n // (e.g., due to timestamp skew or role mismatch).\n done =\n previousContent === content && previousContentAtMs !== undefined && Date.now() - previousContentAtMs >= 15_000;\n } else {\n const latest = relevant[relevant.length - 1]!;\n\n // ACCUMULATE the run, don't just show its last line.\n //\n // The agent emits a message per step, and it now narrates each one and prints\n // a link the moment a create lands (\"✅ Data sink created — [Open …](/…)\").\n // Showing only the newest message threw all of that away a second later: the\n // user saw a lone \"Thinking…\" and none of the links they were promised. Join\n // the whole run instead, so the panel reads as a live account of what is\n // happening and every link stays on screen.\n const narration = relevant.map(getAssistantText).filter(Boolean);\n const toolProgress = getToolProgress(latest);\n content = [...narration, ...toolProgress].join('\\n\\n');\n\n const officiallyDone = Boolean(getMessageCompleted(latest)) && content.length > 0;\n\n // A tool that is still running is proof the turn is alive, so never let the\n // staleness fallback fire underneath it. A single gateway call can sit on the\n // same \"⏳ Creating the data sink…\" line for far longer than the old 15s\n // window, which would have declared the turn finished mid-build.\n const hasRunningTool = (latest.parts ?? []).some(\n (part) => part.type === 'tool' && part.state?.status !== 'completed' && part.state?.status !== 'failed'\n );\n\n const staleDone =\n !officiallyDone &&\n !hasRunningTool &&\n previousContent === content &&\n previousContentAtMs !== undefined &&\n Date.now() - previousContentAtMs >= 45_000;\n\n done = officiallyDone || staleDone;\n }\n\n return { content, done };\n}\n\nfunction getRawMessageId(message: AssistantRawMessage): string | undefined {\n return message.info?.id ?? message.id;\n}\n\n/**\n * The host injects synthetic \"user\" messages into the session (the auth-context\n * file, and a `User's current context:` block describing the open screen). They\n * are plumbing, not something the human typed, so they must never be replayed\n * into the visible transcript.\n */\nconst INJECTED_USER_PREFIXES = ['[Context file:', \"User's current context:\"];\n\nfunction isInjectedContextMessage(text: string): boolean {\n const head = text.trimStart();\n return INJECTED_USER_PREFIXES.some((prefix) => head.startsWith(prefix));\n}\n\n/**\n * Collapse a raw agent session into the user-visible conversation.\n *\n * For each user turn we join every assistant message that carried text — the\n * same accumulation `buildProgress` renders live. That is deliberate: a restored\n * conversation must look like the one the user was actually looking at, and it\n * keeps the per-step \"✅ … [Open the data sink](/…)\" links, which are the whole\n * point of the run. Keeping only the final answer would silently drop them.\n *\n * Tool/step parts carry no text and so fall out on their own — the agent's\n * scratchpad never reaches the chat.\n *\n * Turn boundaries are derived from ordering rather than `parentID` so this also\n * works for backends that return the flat message shape.\n */\nfunction mapSessionToHistory(raw: AssistantRawMessage[]): AssistantHistoryMessage[] {\n const ordered = [...raw].sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n const history: AssistantHistoryMessage[] = [];\n let pendingUser: { id: string; content: string } | null = null;\n let reply: string[] = [];\n\n const flushTurn = (): void => {\n const turn = pendingUser;\n if (!turn) return;\n history.push({ id: turn.id, role: 'user', content: turn.content });\n if (reply.length > 0) {\n history.push({\n id: `${turn.id}:reply`,\n role: 'assistant',\n content: sanitize(reply.join('\\n\\n')),\n });\n }\n pendingUser = null;\n reply = [];\n };\n\n for (const message of ordered) {\n const role = getMessageRole(message);\n const text = getAssistantText(message);\n\n if (role === 'user') {\n if (!text || isInjectedContextMessage(text)) continue;\n flushTurn();\n pendingUser = {\n id: getRawMessageId(message) ?? `user-${String(history.length)}`,\n content: text,\n };\n } else if (role === 'assistant' && pendingUser && text) {\n // Accumulate every narrated step — mirrors what buildProgress showed live,\n // and preserves the per-step links.\n reply.push(text);\n }\n }\n flushTurn();\n\n return history;\n}\n\n/** The sandbox's placeholder title, which carries no information. */\nconst DEFAULT_SESSION_TITLE = /^AI Assistant\\s*[—-]/i;\nconst SESSION_TITLE_MAX = 60;\n\n/**\n * A human-meaningful label for a past chat: the first thing the user actually\n * typed in it.\n *\n * Falls back to the backend's own title when it has one worth showing (opencode\n * may auto-title a session later), and to \"New chat\" for a session with no\n * prompt in it yet.\n */\nasync function deriveSessionTitle(\n sandboxId: string,\n workspaceId: string,\n session: AssistantRawSession,\n authContext: AssistantSandboxAuthContext\n): Promise<string> {\n const backendTitle = session.title?.trim();\n if (backendTitle && !DEFAULT_SESSION_TITLE.test(backendTitle)) return backendTitle;\n\n try {\n const raw = await getAssistantMessages(sandboxId, workspaceId, session.id, authContext, SESSION_TITLE_PROBE_LIMIT);\n const firstPrompt = [...raw]\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right))\n .filter((message) => getMessageRole(message) === 'user')\n .map(getAssistantText)\n .find((text) => text && !isInjectedContextMessage(text));\n\n if (!firstPrompt) return 'New chat';\n const flat = firstPrompt.replace(/\\s+/g, ' ').trim();\n return flat.length > SESSION_TITLE_MAX ? `${flat.slice(0, SESSION_TITLE_MAX - 1)}…` : flat;\n } catch {\n return backendTitle || 'Untitled chat';\n }\n}\n\nfunction sanitize(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nfunction isRecoverable(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'unable to connect',\n 'image pull',\n 'container failed',\n 'bootstrap failed',\n // Transient network errors from the browser fetch — gateway CORS preflight\n // failures, mid-stream resets, and Cloudflare 524s all surface as\n // \"Failed to fetch\" via TypeError. They're worth retrying on a clean\n // runtime since the underlying sandbox state is unaffected.\n 'failed to fetch',\n 'cf-proxy timeout',\n // Subgraph returned 500 with a generic message — gateway returns this as\n // a GraphQL error rather than an HTTP error. The actual underlying cause\n // (e.g., transient Prisma timeout) is recoverable, but a fresh sandbox\n // may be needed.\n 'unexpected error',\n ].some((fragment) => message.includes(fragment));\n}\n\n/**\n * Returns a memoized `AssistantTransport` wired to the BigConsole sandbox\n * assistant agent. The sandbox + session are cached in refs so follow-up turns\n * reuse the warm environment for the lifetime of the host shell.\n */\nexport function useSandboxAssistantTransport(): AssistantTransport {\n const { getAccessToken, getWorkspaceToken, userId, workspaceId: ctxWorkspaceId } = useAuthToken();\n\n // Rehydrate the sandbox + session ids persisted by the previous page\n // lifecycle. These were being WRITTEN to sessionStorage but never read back,\n // so every reload silently opened a brand-new agent session: the chat looked\n // empty AND the agent genuinely lost the conversation (it could no longer\n // resolve \"that datasink\" / \"the dashboard you just made\").\n //\n // Restoring both together is what makes history real rather than cosmetic —\n // the transcript we replay into the UI is the same session the agent will\n // keep reasoning over. A stale/expired sandbox is not a problem: `sendPrompt`\n // already treats that as recoverable, drops the refs, and retries clean.\n const [initialSandboxId, initialSessionId] = useMemo(\n () => [readStoredId(SANDBOX_ID_KEY), readStoredId(SESSION_ID_KEY)] as const,\n []\n );\n\n const sandboxIdRef = useRef<string | null>(initialSandboxId);\n const sessionIdRef = useRef<string | null>(initialSessionId);\n\n const persistSandboxId = useCallback((id: string | null) => {\n sandboxIdRef.current = id;\n writeStoredId(SANDBOX_ID_KEY, id);\n }, []);\n\n const persistSessionId = useCallback((id: string | null) => {\n sessionIdRef.current = id;\n writeStoredId(SESSION_ID_KEY, id);\n }, []);\n\n const ensureRuntime = useCallback(\n async (\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let sandboxId = sandboxIdRef.current;\n console.log('[BigConsole-Assistant] ensureRuntime start', { sandboxId, workspaceId });\n if (!sandboxId) {\n if (!getAccessToken()) {\n throw new Error('The assistant requires an authenticated session. Please sign in again.');\n }\n console.log('[BigConsole-Assistant] findExistingSandbox called');\n sandboxId = await findExistingSandbox(workspaceId, MODE, authContext);\n console.log('[BigConsole-Assistant] findExistingSandbox result', { sandboxId });\n\n if (sandboxId) {\n try {\n console.log('[BigConsole-Assistant] waitForSandboxReady called (reused)', { sandboxId });\n await waitForSandboxReady(sandboxId, workspaceId, authContext);\n console.log('[BigConsole-Assistant] waitForSandboxReady done (reused)', { sandboxId });\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady called (reused)', { sandboxId });\n await waitForAssistantServiceReady(sandboxId, workspaceId, MODE, authContext);\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady done (reused)', { sandboxId });\n } catch (error) {\n console.warn('[BigConsole-Assistant] 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 console.log('[BigConsole-Assistant] createAssistantSandbox called');\n sandboxId = await createAssistantSandbox(MODE, workspaceId, authContext);\n console.log('[BigConsole-Assistant] createAssistantSandbox result', { sandboxId });\n console.log('[BigConsole-Assistant] waitForSandboxReady called (fresh)', { sandboxId });\n await waitForSandboxReady(sandboxId, workspaceId, authContext);\n console.log('[BigConsole-Assistant] waitForSandboxReady done (fresh)', { sandboxId });\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady called (fresh)', { sandboxId });\n await waitForAssistantServiceReady(sandboxId, workspaceId, MODE, authContext);\n console.log('[BigConsole-Assistant] waitForAssistantServiceReady done (fresh)', { sandboxId });\n }\n\n persistSandboxId(sandboxId);\n }\n\n let sessionId = sessionIdRef.current;\n console.log('[BigConsole-Assistant] session check', { sessionId, sandboxId });\n if (!sessionId) {\n console.log('[BigConsole-Assistant] createAssistantSession called', { sandboxId, workspaceId });\n const result = await createAssistantSession(sandboxId, workspaceId, MODE, authContext);\n sessionId = result.sessionId;\n persistSessionId(sessionId);\n }\n\n return { sandboxId, sessionId };\n },\n [getAccessToken]\n );\n\n const sendPrompt = useCallback(\n async ({ prompt, onProgress, signal }: AssistantSendArgs): Promise<{ text: string }> => {\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n console.log('[BigConsole-Assistant] sendPrompt called', {\n promptLength: prompt.length,\n workspaceId,\n hasCtxWorkspaceId: !!ctxWorkspaceId,\n authContextKeys: {\n hasAccessToken: !!getAccessToken(),\n hasWorkspaceToken: !!getWorkspaceToken(),\n hasUserId: !!userId,\n },\n locationSearch: window.location.search,\n });\n if (!workspaceId) {\n throw new Error('The assistant needs an active workspace. Open a workspace and try again.');\n }\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n console.log('[BigConsole-Assistant] authContext prepared', {\n hasAccessToken: !!authContext.accessToken,\n hasWorkspaceToken: !!authContext.workspaceToken,\n hasUserId: !!authContext.userId,\n hasOrgId: !!authContext.organizationId,\n });\n\n const run = async (attempt: 1 | 2): Promise<{ text: string }> => {\n try {\n console.log('[BigConsole-Assistant] run attempt', attempt);\n const { sandboxId, sessionId } = await ensureRuntime(workspaceId, authContext);\n console.log('[BigConsole-Assistant] ensureRuntime resolved', { sandboxId, sessionId });\n\n const startedAt = Date.now();\n console.log('[BigConsole-Assistant] calling sendAssistantPromptAsync', {\n sandboxId,\n workspaceId,\n sessionId,\n promptLength: prompt.length,\n });\n await sendAssistantPromptAsync(\n sandboxId,\n workspaceId,\n sessionId,\n prompt,\n MODE,\n gatherPageContext(),\n authContext\n );\n\n const timeoutAt = Date.now() + STREAM_BUDGET_MS;\n let lastTtlExtensionAt = Date.now();\n let lastContent = '';\n let lastContentChangeAt = Date.now();\n let progress = buildProgress([], startedAt);\n\n while (Date.now() < timeoutAt) {\n if (signal.aborted) throw new Error('Cancelled');\n\n const messages = await getAssistantMessages(sandboxId, workspaceId, sessionId, authContext, 50);\n console.log('[BigConsole-Assistant] poll', {\n elapsedMs: Date.now() - startedAt,\n messageCount: messages.length,\n firstFewRoles: messages.slice(0, 3).map((m) => getMessageRole(m)),\n firstFewTimestamps: messages.slice(0, 3).map((m) => getRawMessageCreatedAt(m)),\n });\n progress = buildProgress(messages, startedAt, lastContent, lastContentChangeAt);\n console.log('[BigConsole-Assistant] progress', {\n contentPreview: progress.content.slice(0, 100),\n done: progress.done,\n lastContentChangeAt: Date.now() - lastContentChangeAt,\n });\n if (progress.content !== lastContent) {\n lastContent = progress.content;\n lastContentChangeAt = Date.now();\n }\n onProgress(sanitize(progress.content));\n if (progress.done) {\n console.log('[BigConsole-Assistant] progress.done=true, breaking poll loop');\n break;\n }\n\n if (Date.now() - lastTtlExtensionAt > TTL_EXTEND_INTERVAL_MS) {\n await extendAssistantSandboxTTL(sandboxId, workspaceId, 600, authContext).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n if (!progress.done) {\n // Do NOT say \"please try again\". We stopped watching; the agent did\n // not stop working, and anything it already created is real. Telling\n // people to retry is how you get duplicate dashboards.\n throw new Error(\n 'I stopped waiting for a reply, but I may still be working — anything I already created will be there. Check your dashboards and data sinks before asking again, so you do not end up with duplicates.'\n );\n }\n\n return { text: sanitize(progress.content) };\n } catch (error) {\n console.error('[BigConsole-Assistant] run error', {\n attempt,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n sandboxId: sandboxIdRef.current,\n sessionId: sessionIdRef.current,\n });\n // A stale/expired sandbox or session is recoverable — drop the warm\n // refs and retry once from a clean runtime.\n if (attempt === 1 && isRecoverable(error)) {\n persistSandboxId(null);\n persistSessionId(null);\n return run(2);\n }\n throw error;\n }\n };\n\n return run(1);\n },\n [ctxWorkspaceId, ensureRuntime, getAccessToken, getWorkspaceToken, userId, persistSandboxId, persistSessionId]\n );\n\n /**\n * Rebuild the visible conversation from the agent's own session.\n *\n * The sandbox is the source of truth for the transcript, so we replay from it\n * rather than mirroring messages into localStorage: the UI can then never\n * show a history the agent doesn't actually have. Called by the widget the\n * first time it opens; a no-op on a first-ever visit.\n */\n const loadHistory = useCallback(async (): Promise<AssistantHistoryMessage[]> => {\n const sandboxId = sandboxIdRef.current;\n const sessionId = sessionIdRef.current;\n if (!sandboxId || !sessionId) return [];\n\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!workspaceId || !getAccessToken()) return [];\n\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n try {\n const raw = await getAssistantMessages(sandboxId, workspaceId, sessionId, authContext, HISTORY_MESSAGE_LIMIT);\n return mapSessionToHistory(raw);\n } catch (error) {\n // The session is gone — sandbox TTL expired, or the workspace was reset.\n // Drop the stale ids so the next prompt provisions a clean runtime instead\n // of repeatedly failing against a dead session.\n console.warn('[BigConsole-Assistant] could not restore history; starting fresh', {\n sandboxId,\n sessionId,\n error: error instanceof Error ? error.message : String(error),\n });\n persistSandboxId(null);\n persistSessionId(null);\n return [];\n }\n }, [ctxWorkspaceId, getAccessToken, getWorkspaceToken, userId, persistSandboxId, persistSessionId]);\n\n // ── Session management (powers the New chat / Clear chat / History rail) ──\n //\n // The sandbox owns the conversations, so every one of these is a thin call\n // onto it rather than client-side bookkeeping — the rail can never show a\n // chat the agent has actually forgotten.\n\n const authFor = useCallback(\n (): AssistantSandboxAuthContext => ({\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n }),\n [getAccessToken, getWorkspaceToken, userId]\n );\n\n const listSessions = useCallback(async (): Promise<AssistantSessionSummaryLocal[]> => {\n const sandboxId = sandboxIdRef.current;\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!sandboxId || !workspaceId) return [];\n\n try {\n const raw = await listAssistantSessions(sandboxId, workspaceId, authFor());\n const auth = authFor();\n\n const recent = raw\n .map((session) => ({\n session,\n updatedAt: session.time?.updated ?? session.time?.created,\n }))\n .sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))\n .slice(0, SESSION_LIST_LIMIT);\n\n // Title each chat from its own first prompt.\n //\n // The sandbox names every session `AI Assistant — <mode>`, so a History\n // list built from those titles is a column of identical rows — you cannot\n // tell which chat is which, which defeats the point of having it. The\n // first thing the user actually typed is the only title that means\n // anything, so fetch it. Bounded by SESSION_LIST_LIMIT and only ever run\n // when the panel is on screen; a failure degrades to the placeholder\n // rather than emptying the list.\n return await Promise.all(\n recent.map(async ({ session, updatedAt }) => ({\n id: session.id,\n title: await deriveSessionTitle(sandboxId, workspaceId, session, auth),\n updatedAt,\n active: session.id === sessionIdRef.current,\n }))\n );\n } catch {\n return [];\n }\n }, [ctxWorkspaceId, authFor]);\n\n const newSession = useCallback(async (): Promise<void> => {\n const sandboxId = sandboxIdRef.current;\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n\n // No sandbox yet → nothing to detach from; the next prompt provisions one.\n if (!sandboxId || !workspaceId) {\n persistSessionId(null);\n return;\n }\n\n const { sessionId } = await createAssistantSession(sandboxId, workspaceId, MODE, authFor());\n persistSessionId(sessionId);\n }, [ctxWorkspaceId, authFor, persistSessionId]);\n\n const clearSession = useCallback(async (): Promise<void> => {\n const sandboxId = sandboxIdRef.current;\n const sessionId = sessionIdRef.current;\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n\n if (sandboxId && sessionId && workspaceId) {\n // Best-effort: if the delete fails the session is still detached below, so\n // the user gets the empty chat they asked for either way.\n await deleteAssistantSession(sandboxId, workspaceId, sessionId, authFor()).catch(() => undefined);\n }\n persistSessionId(null);\n await newSession();\n }, [ctxWorkspaceId, authFor, persistSessionId, newSession]);\n\n const selectSession = useCallback(\n async (sessionId: string): Promise<AssistantHistoryMessage[]> => {\n const sandboxId = sandboxIdRef.current;\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!sandboxId || !workspaceId) return [];\n\n const raw = await getAssistantMessages(sandboxId, workspaceId, sessionId, authFor(), HISTORY_MESSAGE_LIMIT);\n // Only adopt the session once we know we can actually read it.\n persistSessionId(sessionId);\n return mapSessionToHistory(raw);\n },\n [ctxWorkspaceId, authFor, persistSessionId]\n );\n\n return useMemo<AssistantTransport>(\n () => ({ sendPrompt, loadHistory, listSessions, newSession, clearSession, selectSession }),\n [sendPrompt, loadHistory, listSessions, newSession, clearSession, selectSession]\n );\n}\n"],"mappings":"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"}
|