@burdenoff/microfe-bigconsole 2026.713.4 → 2026.713.6

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.
@@ -3,20 +3,20 @@ import { gatherPageContext as u } from "./pageContext.js";
3
3
  import { useCallback as d, useMemo as f, useRef as p } from "react";
4
4
  import { useAuthToken as m } from "@burdenoff/fe-libs/shared/providers/shell";
5
5
  //#region src/bigconsole/assistant/createSandboxAssistantTransport.ts
6
- var h = "api-calls", g = 42e4, _ = 1500, v = 3e4, y = 200, b = "bc-assistant-sandbox-id", x = "bc-assistant-session-id";
7
- function S(e) {
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 C(e, t) {
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 w(e) {
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 w(e) {
28
28
  return "";
29
29
  }
30
30
  }
31
- function T(e) {
32
- return new URLSearchParams(window.location.search).get("workspace") ?? w("workspaceId") ?? e ?? "";
31
+ function D(e) {
32
+ return new URLSearchParams(window.location.search).get("workspace") ?? E("workspaceId") ?? e ?? "";
33
33
  }
34
- function E() {
35
- return new URLSearchParams(window.location.search).get("org") ?? w("organizationId");
34
+ function O() {
35
+ return new URLSearchParams(window.location.search).get("org") ?? E("organizationId");
36
36
  }
37
- function D(e) {
37
+ function k(e) {
38
38
  return e.info?.role ?? e.role;
39
39
  }
40
- function O(e) {
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 O(e) {
48
48
  }
49
49
  return n;
50
50
  }
51
- function k(e) {
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 k(e) {
60
60
  return n;
61
61
  }
62
62
  }
63
- function A(e) {
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 j = {
66
+ var N = {
67
67
  bash: "Running a command",
68
68
  webfetch: "Fetching a page",
69
69
  "file.read": "Reading files",
@@ -74,39 +74,39 @@ var j = {
74
74
  todowrite: "Planning the steps",
75
75
  todoread: "Reviewing the plan"
76
76
  };
77
- function M(e) {
77
+ function P(e) {
78
78
  return typeof e == "object" && e ? e : void 0;
79
79
  }
80
- function N(e) {
81
- let t = e.tool ?? "tool", n = M(e.state?.input), r = n?.description;
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 = M(i.find((e) => M(e)?.status === "in_progress") ?? i[0])?.content;
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 j[t] ?? `Running ${t}`;
88
+ return N[t] ?? `Running ${t}`;
89
89
  }
90
- function P(e) {
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 = N(e);
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 F(e, t, n, r) {
97
- let i = e.filter((e) => D(e) === "assistant" && O(e) >= t).sort((e, t) => O(e) - O(t));
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) => D(e)),
102
- messageTimestamps: e.map((e) => O(e))
101
+ messageRoles: e.map((e) => k(e)),
102
+ messageTimestamps: e.map((e) => A(e))
103
103
  });
104
104
  let a, o;
105
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 = i.map(A).filter(Boolean), s = P(e);
107
+ let e = i[i.length - 1], t = i.map(M).filter(Boolean), s = I(e);
108
108
  a = [...t, ...s].join("\n\n");
109
- let c = !!k(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;
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
110
  o = c || u;
111
111
  }
112
112
  return {
@@ -114,16 +114,16 @@ function F(e, t, n, r) {
114
114
  done: o
115
115
  };
116
116
  }
117
- function I(e) {
117
+ function R(e) {
118
118
  return e.info?.id ?? e.id;
119
119
  }
120
- var L = ["[Context file:", "User's current context:"];
121
- function R(e) {
120
+ var z = ["[Context file:", "User's current context:"];
121
+ function B(e) {
122
122
  let t = e.trimStart();
123
- return L.some((e) => t.startsWith(e));
123
+ return z.some((e) => t.startsWith(e));
124
124
  }
125
- function z(e) {
126
- let t = [...e].sort((e, t) => O(e) - O(t)), n = [], r = null, i = [], a = () => {
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,
@@ -132,25 +132,38 @@ function z(e) {
132
132
  }), i.length > 0 && n.push({
133
133
  id: `${e.id}:reply`,
134
134
  role: "assistant",
135
- content: B(i.join("\n\n"))
135
+ content: G(i.join("\n\n"))
136
136
  }), r = null, i = []);
137
137
  };
138
138
  for (let e of t) {
139
- let t = D(e), o = A(e);
139
+ let t = k(e), o = M(e);
140
140
  if (t === "user") {
141
- if (!o || R(o)) continue;
141
+ if (!o || B(o)) continue;
142
142
  a(), r = {
143
- id: I(e) ?? `user-${String(n.length)}`,
143
+ id: R(e) ?? `user-${String(n.length)}`,
144
144
  content: o
145
145
  };
146
146
  } else t === "assistant" && r && o && i.push(o);
147
147
  }
148
148
  return a(), n;
149
149
  }
150
- function B(e) {
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 V(e) {
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,18 +184,18 @@ function V(e) {
171
184
  "unexpected error"
172
185
  ].some((e) => t.includes(e));
173
186
  }
174
- function H() {
175
- let { getAccessToken: w, getWorkspaceToken: k, userId: A, workspaceId: j } = m(), [M, N] = f(() => [S(b), S(x)], []), P = p(M), I = p(N), L = d((e) => {
176
- P.current = e, C(b, e);
177
- }, []), R = d((e) => {
178
- I.current = e, C(x, e);
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 = p(/* @__PURE__ */ new Map()), z = d((e) => {
189
+ F.current = e, T(S, e);
190
+ }, []), B = d((e) => {
191
+ I.current = e, T(C, e);
179
192
  }, []), H = d(async (n, r) => {
180
- let a = P.current;
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 (!w()) throw Error("The assistant requires an authenticated session. Please sign in again.");
198
+ if (!x()) throw Error("The assistant requires an authenticated session. Please sign in again.");
186
199
  if (console.log("[BigConsole-Assistant] findExistingSandbox called"), a = await i(n, h, r), console.log("[BigConsole-Assistant] findExistingSandbox result", { sandboxId: a }), a) try {
187
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) {
@@ -191,7 +204,7 @@ function H() {
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(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 })), L(a);
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 })), z(a);
195
208
  }
196
209
  let o = I.current;
197
210
  return console.log("[BigConsole-Assistant] session check", {
@@ -200,28 +213,28 @@ function H() {
200
213
  }), o || (console.log("[BigConsole-Assistant] createAssistantSession called", {
201
214
  sandboxId: a,
202
215
  workspaceId: n
203
- }), o = (await t(a, n, h, r)).sessionId, R(o)), {
216
+ }), o = (await t(a, n, h, r)).sessionId, B(o)), {
204
217
  sandboxId: a,
205
218
  sessionId: o
206
219
  };
207
- }, [w]), U = d(async ({ prompt: e, onProgress: t, signal: n }) => {
208
- let i = T(j);
220
+ }, [x]), U = 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
224
  workspaceId: i,
212
- hasCtxWorkspaceId: !!j,
225
+ hasCtxWorkspaceId: !!M,
213
226
  authContextKeys: {
214
- hasAccessToken: !!w(),
215
- hasWorkspaceToken: !!k(),
216
- hasUserId: !!A
227
+ hasAccessToken: !!x(),
228
+ hasWorkspaceToken: !!E(),
229
+ hasUserId: !!j
217
230
  },
218
231
  locationSearch: window.location.search
219
232
  }), !i) throw Error("The assistant needs an active workspace. Open a workspace and try again.");
220
233
  let o = {
221
- accessToken: w(),
222
- workspaceToken: k(),
223
- userId: A,
224
- organizationId: E()
234
+ accessToken: x(),
235
+ workspaceToken: E(),
236
+ userId: j,
237
+ organizationId: O()
225
238
  };
226
239
  console.log("[BigConsole-Assistant] authContext prepared", {
227
240
  hasAccessToken: !!o.accessToken,
@@ -244,143 +257,159 @@ function H() {
244
257
  sessionId: d,
245
258
  promptLength: e.length
246
259
  }), await s(c, i, d, e, h, u(), o);
247
- let p = Date.now() + g, m = Date.now(), y = "", b = Date.now(), x = F([], f);
260
+ let p = Date.now() + g, m = Date.now(), y = "", b = Date.now(), x = L([], f);
248
261
  for (; Date.now() < p;) {
249
262
  if (n.aborted) throw Error("Cancelled");
250
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) => D(e)),
255
- firstFewTimestamps: e.slice(0, 3).map((e) => O(e))
256
- }), x = F(e, f, y, b), console.log("[BigConsole-Assistant] progress", {
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(B(x.content)), x.done) {
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
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: B(x.content) };
280
+ return { text: G(x.content) };
268
281
  } catch (e) {
269
282
  if (console.error("[BigConsole-Assistant] run error", {
270
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: P.current,
286
+ sandboxId: F.current,
274
287
  sessionId: I.current
275
- }), l === 1 && V(e)) return L(null), R(null), c(2);
288
+ }), l === 1 && K(e)) return z(null), B(null), c(2);
276
289
  throw e;
277
290
  }
278
291
  };
279
292
  return c(1);
280
293
  }, [
281
- j,
294
+ M,
282
295
  H,
283
- w,
284
- k,
285
- A,
286
- L,
287
- R
288
- ]), W = d(async () => {
289
- let e = P.current, t = I.current;
290
- if (!e || !t) return [];
291
- let n = T(j);
292
- if (!n || !w()) return [];
293
- let r = {
294
- accessToken: w(),
295
- workspaceToken: k(),
296
- userId: A,
297
- organizationId: E()
298
- };
296
+ x,
297
+ E,
298
+ j,
299
+ z,
300
+ B
301
+ ]), q = d(async (e, t) => {
302
+ if (F.current) return F.current;
303
+ try {
304
+ let n = await i(e, h, t);
305
+ return n && z(n), n;
306
+ } catch {
307
+ return null;
308
+ }
309
+ }, [z]), J = d(async () => {
310
+ let e = D(M);
311
+ if (!e || !x()) return [];
312
+ let t = {
313
+ accessToken: x(),
314
+ workspaceToken: E(),
315
+ userId: j,
316
+ organizationId: O()
317
+ }, n = await q(e, t);
318
+ if (!n) return [];
319
+ let r = I.current;
320
+ if (!r) try {
321
+ let i = [...await o(n, e, t)].sort((e, t) => (t.time?.updated ?? t.time?.created ?? 0) - (e.time?.updated ?? e.time?.created ?? 0))[0];
322
+ if (!i) return [];
323
+ r = i.id, B(r);
324
+ } catch {
325
+ return [];
326
+ }
299
327
  try {
300
- return z(await a(e, n, t, r, y));
301
- } catch (n) {
328
+ return V(await a(n, e, r, t, y));
329
+ } catch (e) {
302
330
  return console.warn("[BigConsole-Assistant] could not restore history; starting fresh", {
303
- sandboxId: e,
304
- sessionId: t,
305
- error: n instanceof Error ? n.message : String(n)
306
- }), L(null), R(null), [];
331
+ sandboxId: n,
332
+ sessionId: r,
333
+ error: e instanceof Error ? e.message : String(e)
334
+ }), z(null), B(null), [];
307
335
  }
308
336
  }, [
337
+ M,
338
+ x,
339
+ E,
309
340
  j,
310
- w,
311
- k,
312
- A,
313
- L,
314
- R
315
- ]), G = d(() => ({
316
- accessToken: w(),
317
- workspaceToken: k(),
318
- userId: A,
319
- organizationId: E()
341
+ z,
342
+ B,
343
+ q
344
+ ]), Y = d(() => ({
345
+ accessToken: x(),
346
+ workspaceToken: E(),
347
+ userId: j,
348
+ organizationId: O()
320
349
  }), [
321
- w,
322
- k,
323
- A
324
- ]), K = d(async () => {
325
- let e = P.current, t = T(j);
326
- if (!e || !t) return [];
350
+ x,
351
+ E,
352
+ j
353
+ ]), X = d(async () => {
354
+ let e = D(M);
355
+ if (!e) return [];
356
+ let t = Y(), n = await q(e, t);
357
+ if (!n) return [];
327
358
  try {
328
- return (await o(e, t, G())).map((e) => ({
329
- id: e.id,
330
- title: e.title?.trim() || "Untitled chat",
331
- updatedAt: e.time?.updated ?? e.time?.created,
332
- active: e.id === I.current
333
- })).sort((e, t) => (t.updatedAt ?? 0) - (e.updatedAt ?? 0));
359
+ let r = (await o(n, e, t)).map((e) => ({
360
+ session: e,
361
+ updatedAt: e.time?.updated ?? e.time?.created
362
+ })).sort((e, t) => (t.updatedAt ?? 0) - (e.updatedAt ?? 0)).slice(0, b);
363
+ return await Promise.all(r.map(async ({ session: r, updatedAt: i }) => {
364
+ let a = R.current.get(r.id), o = a ?? await W(n, e, r, t);
365
+ return !a && o !== "New chat" && R.current.set(r.id, o), {
366
+ id: r.id,
367
+ title: o,
368
+ updatedAt: i,
369
+ active: r.id === I.current
370
+ };
371
+ }));
334
372
  } catch {
335
373
  return [];
336
374
  }
337
- }, [j, G]), q = d(async () => {
338
- let e = P.current, n = T(j);
339
- if (!e || !n) {
340
- R(null);
341
- return;
342
- }
343
- let { sessionId: r } = await t(e, n, h, G());
344
- R(r);
345
375
  }, [
346
- j,
347
- G,
348
- R
349
- ]), J = d(async () => {
350
- let e = P.current, t = I.current, r = T(j);
351
- e && t && r && await n(e, r, t, G()).catch(() => void 0), R(null), await q();
352
- }, [
353
- j,
354
- G,
355
- R,
376
+ M,
377
+ Y,
356
378
  q
357
- ]), Y = d(async (e) => {
358
- let t = P.current, n = T(j);
379
+ ]), Z = d(async () => (B(null), Promise.resolve()), [B]), Q = d(async (e) => {
380
+ let t = F.current, r = D(M);
381
+ !t || !r || (await n(t, r, e, Y()), R.current.delete(e), I.current === e && B(null));
382
+ }, [
383
+ M,
384
+ Y,
385
+ B
386
+ ]), $ = d(async (e) => {
387
+ let t = F.current, n = D(M);
359
388
  if (!t || !n) return [];
360
- let r = await a(t, n, e, G(), y);
361
- return R(e), z(r);
389
+ let r = await a(t, n, e, Y(), y);
390
+ return B(e), V(r);
362
391
  }, [
363
- j,
364
- G,
365
- R
392
+ M,
393
+ Y,
394
+ B
366
395
  ]);
367
396
  return f(() => ({
368
397
  sendPrompt: U,
369
- loadHistory: W,
370
- listSessions: K,
371
- newSession: q,
372
- clearSession: J,
373
- selectSession: Y
398
+ loadHistory: J,
399
+ listSessions: X,
400
+ newSession: Z,
401
+ deleteSession: Q,
402
+ selectSession: $
374
403
  }), [
375
404
  U,
376
- W,
377
- K,
378
- q,
379
405
  J,
380
- Y
406
+ X,
407
+ Z,
408
+ Q,
409
+ $
381
410
  ]);
382
411
  }
383
412
  //#endregion
384
- export { H as useSandboxAssistantTransport };
413
+ export { q as useSandboxAssistantTransport };
385
414
 
386
415
  //# 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 deleteAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantMessages,\n listAssistantSessions,\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\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\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\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 return raw\n .map((session) => ({\n id: session.id,\n title: session.title?.trim() || 'Untitled chat',\n updatedAt: session.time?.updated ?? session.time?.created,\n active: session.id === sessionIdRef.current,\n }))\n .sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));\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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"}
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 deleteSession: (sessionId: string) => 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 // sessionId -> title. A chat's first prompt never changes, so this is safe to\n // memoise for the life of the page, and it keeps re-opening History free.\n const sessionTitlesRef = useRef<Map<string, string>>(new Map());\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 /**\n * Resolve the sandbox for a READ (history / session list) without provisioning\n * one.\n *\n * Logging out clears sessionStorage, so the sandbox and session ids are gone —\n * but the sandbox itself is still alive on the backend, still holding every\n * conversation. Bailing out on a missing id is why History came back empty\n * after a re-login: we had simply forgotten where the chats were, not lost\n * them. Look the sandbox up instead (a cheap `getActiveSandboxes` query,\n * scoped to this user + workspace).\n *\n * Read-only on purpose: it will adopt a RUNNING sandbox, never create or wake\n * one. Opening the panel must not cost a provision.\n */\n const adoptExistingSandbox = useCallback(\n async (workspaceId: string, authContext: AssistantSandboxAuthContext): Promise<string | null> => {\n if (sandboxIdRef.current) return sandboxIdRef.current;\n try {\n const found = await findExistingSandbox(workspaceId, MODE, authContext);\n if (found) persistSandboxId(found);\n return found;\n } catch {\n return null;\n }\n },\n [persistSandboxId]\n );\n\n const loadHistory = useCallback(async (): Promise<AssistantHistoryMessage[]> => {\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!workspaceId || !getAccessToken()) return [];\n\n const auth: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const sandboxId = await adoptExistingSandbox(workspaceId, auth);\n if (!sandboxId) return [];\n\n // After a re-login we know the sandbox but not which conversation the user\n // was in. Reopen the most recent one so they land back where they left off\n // instead of in a blank chat with their history hidden behind a menu.\n let sessionId = sessionIdRef.current;\n if (!sessionId) {\n try {\n const sessions = await listAssistantSessions(sandboxId, workspaceId, auth);\n const newest = [...sessions].sort(\n (left, right) =>\n (right.time?.updated ?? right.time?.created ?? 0) - (left.time?.updated ?? left.time?.created ?? 0)\n )[0];\n if (!newest) return [];\n sessionId = newest.id;\n persistSessionId(sessionId);\n } catch {\n return [];\n }\n }\n\n try {\n const raw = await getAssistantMessages(sandboxId, workspaceId, sessionId, auth, 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 }, [\n ctxWorkspaceId,\n getAccessToken,\n getWorkspaceToken,\n userId,\n persistSandboxId,\n persistSessionId,\n adoptExistingSandbox,\n ]);\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 workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!workspaceId) return [];\n const auth = authFor();\n\n // Same story as loadHistory: after a re-login we've forgotten where the\n // chats are, not lost them. Find the sandbox before giving up.\n const sandboxId = await adoptExistingSandbox(workspaceId, auth);\n if (!sandboxId) return [];\n\n try {\n const raw = await listAssistantSessions(sandboxId, workspaceId, auth);\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.\n //\n // Titles are memoised per session id: a chat's first prompt never changes,\n // so re-opening History (or refreshing it after a turn) costs nothing. That\n // matters — without the cache this was one extra round-trip per chat on\n // every single open, which is what made the panel feel slow.\n return await Promise.all(\n recent.map(async ({ session, updatedAt }) => {\n const cached = sessionTitlesRef.current.get(session.id);\n const title = cached ?? (await deriveSessionTitle(sandboxId, workspaceId, session, auth));\n if (!cached && title !== 'New chat') sessionTitlesRef.current.set(session.id, title);\n return {\n id: session.id,\n title,\n updatedAt,\n active: session.id === sessionIdRef.current,\n };\n })\n );\n } catch {\n return [];\n }\n }, [ctxWorkspaceId, authFor, adoptExistingSandbox]);\n\n /**\n * Start a new chat — instantly.\n *\n * This used to create the session on the sandbox and await the round-trip, and\n * the caller then refreshed the History list (one fetch per chat to title it).\n * Clicking \"New chat\" while History was open therefore paid for all of that\n * before the screen cleared, which is why it felt like it hung.\n *\n * None of that work is necessary. Detaching locally is enough: `ensureRuntime`\n * already creates a session on the next prompt if there isn't one. So this is\n * now pure local state — the chat clears immediately, and the session is\n * created lazily when the user actually says something.\n *\n * It also stops littering the History list with empty \"New chat\" rows for\n * sessions the user opened and never used.\n */\n const newSession = useCallback(async (): Promise<void> => {\n persistSessionId(null);\n return Promise.resolve();\n }, [persistSessionId]);\n\n /**\n * Delete one conversation.\n *\n * Replaces the old \"Clear chat\" button, which could only ever remove whichever\n * chat happened to be open — ambiguous, and useless for tidying up an old one.\n * A delete control on each History row says exactly what it will remove.\n */\n const deleteSession = useCallback(\n async (sessionId: string): Promise<void> => {\n const sandboxId = sandboxIdRef.current;\n const workspaceId = getWorkspaceId(ctxWorkspaceId);\n if (!sandboxId || !workspaceId) return;\n\n await deleteAssistantSession(sandboxId, workspaceId, sessionId, authFor());\n sessionTitlesRef.current.delete(sessionId);\n\n // Deleting the chat you're looking at leaves you in a fresh one.\n if (sessionIdRef.current === sessionId) persistSessionId(null);\n },\n [ctxWorkspaceId, authFor, persistSessionId]\n );\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, deleteSession, selectSession }),\n [sendPrompt, loadHistory, listSessions, newSession, deleteSession, selectSession]\n );\n}\n"],"mappings":";;;;;AAyFA,IAAM,IAAsB,aAatB,IAAmB,MACnB,IAAmB,MACnB,IAAyB,KAIzB,IAAwB,KAGxB,IAAqB,IAErB,IAA4B,GAE5B,IAAiB,2BACjB,IAAiB;AAEvB,SAAS,EAAa,GAA4B;AAChD,KAAI;AACF,SAAO,OAAO,eAAe,QAAQ,EAAI;SACnC;AAEN,SAAO;;;AAIX,SAAS,EAAc,GAAa,GAAyB;AAC3D,KAAI;AACF,EAAI,IAAI,OAAO,eAAe,QAAQ,GAAK,EAAG,GACzC,OAAO,eAAe,WAAW,EAAI;SACpC;;AAOV,SAAS,EAAuB,GAA+C;AAC7E,KAAI;EACF,IAAM,IACJ,MAAQ,gBAAgB,uCAAuC,yCAC3D,IAAqB,aAAa,QAAQ,EAAiB;AACjE,MAAI,EAAoB,QAAO;EAE/B,IAAM,IAAkB,eAAe,QAAQ,oBAAoB;AACnE,MAAI,CAAC,EAAiB,QAAO;EAC7B,IAAM,IAAM,aAAa,QAAQ,QAAQ,EAAgB,UAAU;AAGnE,SAFK,IACW,KAAK,MAAM,EAAI,CAChB,MAAQ,KAFN;SAGX;AACN,SAAO;;;AAIX,SAAS,EAAe,GAAiC;AAEvD,QADe,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,YAAY,IAAI,EAAuB,cAAc,IAAI,KAAY;;AAGzF,SAAS,IAA4B;AAEnC,QADe,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC5C,IAAI,MAAM,IAAI,EAAuB,iBAAiB;;AAKtE,SAAS,EAAe,GAAkD;AAExE,QAAO,EAAQ,MAAM,QAAQ,EAAQ;;AAGvC,SAAS,EAAuB,GAAsC;CAEpE,IAAM,IAAS,EAAQ,MAAM,MAAM;AACnC,KAAI,MAAW,KAAA,EAAW,QAAO;CACjC,IAAM,IAAO,EAAQ;AACrB,KAAI,MAAS,KAAA,EAAW,QAAO;AAE/B,KAAI,OAAO,KAAS,UAAU;EAC5B,IAAM,IAAS,KAAK,MAAM,EAAK;AAC/B,SAAO,MAAM,EAAO,GAAG,IAAI;;AAE7B,QAAO;;AAGT,SAAS,EAAoB,GAAkD;CAE7E,IAAM,IAAS,EAAQ,MAAM,MAAM;AACnC,KAAI,MAAW,KAAA,EAAW,QAAO;CACjC,IAAM,IAAO,EAAQ;AACjB,WAAS,KAAA,GACb;MAAI,OAAO,KAAS,UAAU;GAC5B,IAAM,IAAS,KAAK,MAAM,EAAK;AAC/B,UAAO,MAAM,EAAO,GAAG,KAAA,IAAY;;AAErC,SAAO;;;AAGT,SAAS,EAAiB,GAAsC;AAU9D,SARc,EAAQ,SAAS,EAAE,IACD,EAAE,EAC/B,QAAQ,MAAS,EAAK,SAAS,UAAU,OAAO,EAAK,QAAS,SAAS,CACvE,KAAK,MAAS,EAAK,MAAM,MAAM,IAAI,GAAG,CACtC,OAAO,QAAQ,CACf,KAAK,KAAK,KAGN,OAAO,EAAQ,WAAY,WAAW,EAAQ,QAAQ,MAAM,GAAG;;AAOxE,IAAM,IAAsC;CAC1C,MAAM;CACN,UAAU;CACV,aAAa;CACb,cAAc;CACd,aAAa;CACb,kBAAkB;CAClB,kBAAkB;CAClB,WAAW;CACX,UAAU;CACX;AAED,SAAS,EAAS,GAAqD;AACrE,QAAO,OAAO,KAAU,YAAY,IAAkB,IAAoC,KAAA;;AAI5F,SAAS,EAAiB,GAAuC;CAC/D,IAAM,IAAO,EAAK,QAAQ,QACpB,IAAQ,EAAS,EAAK,OAAO,MAAM,EAGnC,IAAc,GAAO;AAC3B,KAAI,OAAO,KAAgB,YAAY,EAAY,MAAM,CAAE,QAAO,EAAY,MAAM;CAGpF,IAAM,IAAQ,GAAO;AACrB,KAAI,MAAM,QAAQ,EAAM,EAAE;EAExB,IAAM,IAAU,EADD,EAAM,MAAM,MAAS,EAAS,EAAK,EAAE,WAAW,cAAc,IAAI,EAAM,GACvD,EAAE;AAClC,MAAI,OAAO,KAAY,YAAY,EAAQ,MAAM,CAAE,QAAO,EAAQ,MAAM;;AAG1E,QAAO,EAAY,MAAS,WAAW;;AAGzC,SAAS,EAAgB,GAAwC;AAE/D,SADc,EAAQ,SAAS,EAAE,EAE9B,QAAQ,MAAS,EAAK,SAAS,UAAU,EAAK,KAAK,CACnD,KAAK,MAAS;EACb,IAAM,IAAS,EAAK,OAAO,UAAU,WAC/B,IAAQ,EAAiB,EAAK;AAGpC,SAFI,MAAW,cAAoB,KAAK,MACpC,MAAW,WAAiB,KAAK,MAC9B,KAAK,EAAM;GAClB;;AAGN,SAAS,EACP,GACA,GACA,GACA,GACoC;CACpC,IAAM,IAAW,EACd,QAAQ,MAAY,EAAe,EAAQ,KAAK,eAAe,EAAuB,EAAQ,IAAI,EAAQ,CAC1G,MAAM,GAAM,MAAU,EAAuB,EAAK,GAAG,EAAuB,EAAM,CAAC;AAGtF,CAAI,EAAS,WAAW,KAAK,EAAS,SAAS,KAC7C,QAAQ,IAAI,8DAA8D;EACxE,eAAe,EAAS;EACxB;EACA,cAAc,EAAS,KAAK,MAAM,EAAe,EAAE,CAAC;EACpD,mBAAmB,EAAS,KAAK,MAAM,EAAuB,EAAE,CAAC;EAClE,CAAC;CAGJ,IAAI,GACA;AAEJ,KAAI,EAAS,WAAW,EAKtB,CAJA,IAAU,IAIV,IACE,MAAoB,KAAW,MAAwB,KAAA,KAAa,KAAK,KAAK,GAAG,KAAuB;MACrG;EACL,IAAM,IAAS,EAAS,EAAS,SAAS,IAUpC,IAAY,EAAS,IAAI,EAAiB,CAAC,OAAO,QAAQ,EAC1D,IAAe,EAAgB,EAAO;AAC5C,MAAU,CAAC,GAAG,GAAW,GAAG,EAAa,CAAC,KAAK,OAAO;EAEtD,IAAM,IAAiB,EAAQ,EAAoB,EAAO,IAAK,EAAQ,SAAS,GAM1E,KAAkB,EAAO,SAAS,EAAE,EAAE,MACzC,MAAS,EAAK,SAAS,UAAU,EAAK,OAAO,WAAW,eAAe,EAAK,OAAO,WAAW,SAChG,EAEK,IACJ,CAAC,KACD,CAAC,KACD,MAAoB,KACpB,MAAwB,KAAA,KACxB,KAAK,KAAK,GAAG,KAAuB;AAEtC,MAAO,KAAkB;;AAG3B,QAAO;EAAE;EAAS;EAAM;;AAG1B,SAAS,EAAgB,GAAkD;AACzE,QAAO,EAAQ,MAAM,MAAM,EAAQ;;AASrC,IAAM,IAAyB,CAAC,kBAAkB,0BAA0B;AAE5E,SAAS,EAAyB,GAAuB;CACvD,IAAM,IAAO,EAAK,WAAW;AAC7B,QAAO,EAAuB,MAAM,MAAW,EAAK,WAAW,EAAO,CAAC;;AAkBzE,SAAS,EAAoB,GAAuD;CAClF,IAAM,IAAU,CAAC,GAAG,EAAI,CAAC,MAAM,GAAM,MAAU,EAAuB,EAAK,GAAG,EAAuB,EAAM,CAAC,EAEtG,IAAqC,EAAE,EACzC,IAAsD,MACtD,IAAkB,EAAE,EAElB,UAAwB;EAC5B,IAAM,IAAO;AACR,QACL,EAAQ,KAAK;GAAE,IAAI,EAAK;GAAI,MAAM;GAAQ,SAAS,EAAK;GAAS,CAAC,EAC9D,EAAM,SAAS,KACjB,EAAQ,KAAK;GACX,IAAI,GAAG,EAAK,GAAG;GACf,MAAM;GACN,SAAS,EAAS,EAAM,KAAK,OAAO,CAAC;GACtC,CAAC,EAEJ,IAAc,MACd,IAAQ,EAAE;;AAGZ,MAAK,IAAM,KAAW,GAAS;EAC7B,IAAM,IAAO,EAAe,EAAQ,EAC9B,IAAO,EAAiB,EAAQ;AAEtC,MAAI,MAAS,QAAQ;AACnB,OAAI,CAAC,KAAQ,EAAyB,EAAK,CAAE;AAE7C,GADA,GAAW,EACX,IAAc;IACZ,IAAI,EAAgB,EAAQ,IAAI,QAAQ,OAAO,EAAQ,OAAO;IAC9D,SAAS;IACV;SACQ,MAAS,eAAe,KAAe,KAGhD,EAAM,KAAK,EAAK;;AAKpB,QAFA,GAAW,EAEJ;;AAIT,IAAM,IAAwB,yBACxB,IAAoB;AAU1B,eAAe,EACb,GACA,GACA,GACA,GACiB;CACjB,IAAM,IAAe,EAAQ,OAAO,MAAM;AAC1C,KAAI,KAAgB,CAAC,EAAsB,KAAK,EAAa,CAAE,QAAO;AAEtE,KAAI;EAEF,IAAM,IAAc,CAAC,GADT,MAAM,EAAqB,GAAW,GAAa,EAAQ,IAAI,GAAa,EAA0B,CACtF,CACzB,MAAM,GAAM,MAAU,EAAuB,EAAK,GAAG,EAAuB,EAAM,CAAC,CACnF,QAAQ,MAAY,EAAe,EAAQ,KAAK,OAAO,CACvD,IAAI,EAAiB,CACrB,MAAM,MAAS,KAAQ,CAAC,EAAyB,EAAK,CAAC;AAE1D,MAAI,CAAC,EAAa,QAAO;EACzB,IAAM,IAAO,EAAY,QAAQ,QAAQ,IAAI,CAAC,MAAM;AACpD,SAAO,EAAK,SAAS,IAAoB,GAAG,EAAK,MAAM,GAAG,IAAoB,EAAE,CAAC,KAAK;SAChF;AACN,SAAO,KAAgB;;;AAI3B,SAAS,EAAS,GAA0B;AAC1C,QAAO,EACJ,QAAQ,6CAA6C,eAAe,CACpE,QAAQ,yDAAyD,eAAe,CAChF,QAAQ,4DAA4D,iBAAiB,CACrF,QAAQ,6BAA6B,qBAAqB;;AAG/D,SAAS,EAAc,GAAyB;CAC9C,IAAM,IAAU,aAAiB,QAAQ,EAAM,QAAQ,aAAa,GAAG,OAAO,EAAM,CAAC,aAAa;AAQlG,QANE,EAAQ,SAAS,sBAAsB,IACvC,EAAQ,SAAS,eAAe,IAChC,EAAQ,SAAS,oBAAoB,GAE9B,KAEF;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAKA;EACA;EAKA;EACD,CAAC,MAAM,MAAa,EAAQ,SAAS,EAAS,CAAC;;AAQlD,SAAgB,IAAmD;CACjE,IAAM,EAAE,mBAAgB,sBAAmB,WAAQ,aAAa,MAAmB,GAAc,EAY3F,CAAC,GAAkB,KAAoB,QACrC,CAAC,EAAa,EAAe,EAAE,EAAa,EAAe,CAAC,EAClE,EAAE,CACH,EAEK,IAAe,EAAsB,EAAiB,EACtD,IAAe,EAAsB,EAAiB,EAGtD,IAAmB,kBAA4B,IAAI,KAAK,CAAC,EAEzD,IAAmB,GAAa,MAAsB;AAE1D,EADA,EAAa,UAAU,GACvB,EAAc,GAAgB,EAAG;IAChC,EAAE,CAAC,EAEA,IAAmB,GAAa,MAAsB;AAE1D,EADA,EAAa,UAAU,GACvB,EAAc,GAAgB,EAAG;IAChC,EAAE,CAAC,EAEA,IAAgB,EACpB,OACE,GACA,MACsD;EACtD,IAAI,IAAY,EAAa;AAE7B,MADA,QAAQ,IAAI,8CAA8C;GAAE;GAAW;GAAa,CAAC,EACjF,CAAC,GAAW;AACd,OAAI,CAAC,GAAgB,CACnB,OAAU,MAAM,yEAAyE;AAM3F,OAJA,QAAQ,IAAI,oDAAoD,EAChE,IAAY,MAAM,EAAoB,GAAa,GAAM,EAAY,EACrE,QAAQ,IAAI,qDAAqD,EAAE,cAAW,CAAC,EAE3E,EACF,KAAI;AAMF,IALA,QAAQ,IAAI,8DAA8D,EAAE,cAAW,CAAC,EACxF,MAAM,EAAoB,GAAW,GAAa,EAAY,EAC9D,QAAQ,IAAI,4DAA4D,EAAE,cAAW,CAAC,EACtF,QAAQ,IAAI,uEAAuE,EAAE,cAAW,CAAC,EACjG,MAAM,EAA6B,GAAW,GAAa,GAAM,EAAY,EAC7E,QAAQ,IAAI,qEAAqE,EAAE,cAAW,CAAC;YACxF,GAAO;AAKd,IAJA,QAAQ,KAAK,mFAAmF;KAC9F;KACA,OAAO,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;KAC9D,CAAC,EACF,IAAY;;AAgBhB,GAZK,MACH,QAAQ,IAAI,uDAAuD,EACnE,IAAY,MAAM,EAAuB,GAAM,GAAa,EAAY,EACxE,QAAQ,IAAI,wDAAwD,EAAE,cAAW,CAAC,EAClF,QAAQ,IAAI,6DAA6D,EAAE,cAAW,CAAC,EACvF,MAAM,EAAoB,GAAW,GAAa,EAAY,EAC9D,QAAQ,IAAI,2DAA2D,EAAE,cAAW,CAAC,EACrF,QAAQ,IAAI,sEAAsE,EAAE,cAAW,CAAC,EAChG,MAAM,EAA6B,GAAW,GAAa,GAAM,EAAY,EAC7E,QAAQ,IAAI,oEAAoE,EAAE,cAAW,CAAC,GAGhG,EAAiB,EAAU;;EAG7B,IAAI,IAAY,EAAa;AAS7B,SARA,QAAQ,IAAI,wCAAwC;GAAE;GAAW;GAAW,CAAC,EACxE,MACH,QAAQ,IAAI,wDAAwD;GAAE;GAAW;GAAa,CAAC,EAE/F,KADe,MAAM,EAAuB,GAAW,GAAa,GAAM,EAAY,EACnE,WACnB,EAAiB,EAAU,GAGtB;GAAE;GAAW;GAAW;IAEjC,CAAC,EAAe,CACjB,EAEK,IAAa,EACjB,OAAO,EAAE,WAAQ,eAAY,gBAA2D;EACtF,IAAM,IAAc,EAAe,EAAe;AAYlD,MAXA,QAAQ,IAAI,4CAA4C;GACtD,cAAc,EAAO;GACrB;GACA,mBAAmB,CAAC,CAAC;GACrB,iBAAiB;IACf,gBAAgB,CAAC,CAAC,GAAgB;IAClC,mBAAmB,CAAC,CAAC,GAAmB;IACxC,WAAW,CAAC,CAAC;IACd;GACD,gBAAgB,OAAO,SAAS;GACjC,CAAC,EACE,CAAC,EACH,OAAU,MAAM,2EAA2E;EAE7F,IAAM,IAA2C;GAC/C,aAAa,GAAgB;GAC7B,gBAAgB,GAAmB;GACnC;GACA,gBAAgB,GAAmB;GACpC;AACD,UAAQ,IAAI,+CAA+C;GACzD,gBAAgB,CAAC,CAAC,EAAY;GAC9B,mBAAmB,CAAC,CAAC,EAAY;GACjC,WAAW,CAAC,CAAC,EAAY;GACzB,UAAU,CAAC,CAAC,EAAY;GACzB,CAAC;EAEF,IAAM,IAAM,OAAO,MAA8C;AAC/D,OAAI;AACF,YAAQ,IAAI,sCAAsC,EAAQ;IAC1D,IAAM,EAAE,cAAW,iBAAc,MAAM,EAAc,GAAa,EAAY;AAC9E,YAAQ,IAAI,iDAAiD;KAAE;KAAW;KAAW,CAAC;IAEtF,IAAM,IAAY,KAAK,KAAK;AAO5B,IANA,QAAQ,IAAI,2DAA2D;KACrE;KACA;KACA;KACA,cAAc,EAAO;KACtB,CAAC,EACF,MAAM,EACJ,GACA,GACA,GACA,GACA,GACA,GAAmB,EACnB,EACD;IAED,IAAM,IAAY,KAAK,KAAK,GAAG,GAC3B,IAAqB,KAAK,KAAK,EAC/B,IAAc,IACd,IAAsB,KAAK,KAAK,EAChC,IAAW,EAAc,EAAE,EAAE,EAAU;AAE3C,WAAO,KAAK,KAAK,GAAG,IAAW;AAC7B,SAAI,EAAO,QAAS,OAAU,MAAM,YAAY;KAEhD,IAAM,IAAW,MAAM,EAAqB,GAAW,GAAa,GAAW,GAAa,GAAG;AAkB/F,SAjBA,QAAQ,IAAI,+BAA+B;MACzC,WAAW,KAAK,KAAK,GAAG;MACxB,cAAc,EAAS;MACvB,eAAe,EAAS,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM,EAAe,EAAE,CAAC;MACjE,oBAAoB,EAAS,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM,EAAuB,EAAE,CAAC;MAC/E,CAAC,EACF,IAAW,EAAc,GAAU,GAAW,GAAa,EAAoB,EAC/E,QAAQ,IAAI,mCAAmC;MAC7C,gBAAgB,EAAS,QAAQ,MAAM,GAAG,IAAI;MAC9C,MAAM,EAAS;MACf,qBAAqB,KAAK,KAAK,GAAG;MACnC,CAAC,EACE,EAAS,YAAY,MACvB,IAAc,EAAS,SACvB,IAAsB,KAAK,KAAK,GAElC,EAAW,EAAS,EAAS,QAAQ,CAAC,EAClC,EAAS,MAAM;AACjB,cAAQ,IAAI,gEAAgE;AAC5E;;AAQF,KALI,KAAK,KAAK,GAAG,IAAqB,MACpC,MAAM,EAA0B,GAAW,GAAa,KAAK,EAAY,CAAC,YAAY,KAAA,EAAU,EAChG,IAAqB,KAAK,KAAK,GAGjC,MAAM,IAAI,SAAS,MAAY,WAAW,GAAS,EAAiB,CAAC;;AAGvE,QAAI,CAAC,EAAS,KAIZ,OAAU,MACR,wMACD;AAGH,WAAO,EAAE,MAAM,EAAS,EAAS,QAAQ,EAAE;YACpC,GAAO;AAUd,QATA,QAAQ,MAAM,oCAAoC;KAChD;KACA,OAAO,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;KAC7D,OAAO,aAAiB,QAAQ,EAAM,QAAQ,KAAA;KAC9C,WAAW,EAAa;KACxB,WAAW,EAAa;KACzB,CAAC,EAGE,MAAY,KAAK,EAAc,EAAM,CAGvC,QAFA,EAAiB,KAAK,EACtB,EAAiB,KAAK,EACf,EAAI,EAAE;AAEf,UAAM;;;AAIV,SAAO,EAAI,EAAE;IAEf;EAAC;EAAgB;EAAe;EAAgB;EAAmB;EAAQ;EAAkB;EAAiB,CAC/G,EAwBK,IAAuB,EAC3B,OAAO,GAAqB,MAAqE;AAC/F,MAAI,EAAa,QAAS,QAAO,EAAa;AAC9C,MAAI;GACF,IAAM,IAAQ,MAAM,EAAoB,GAAa,GAAM,EAAY;AAEvE,UADI,KAAO,EAAiB,EAAM,EAC3B;UACD;AACN,UAAO;;IAGX,CAAC,EAAiB,CACnB,EAEK,IAAc,EAAY,YAAgD;EAC9E,IAAM,IAAc,EAAe,EAAe;AAClD,MAAI,CAAC,KAAe,CAAC,GAAgB,CAAE,QAAO,EAAE;EAEhD,IAAM,IAAoC;GACxC,aAAa,GAAgB;GAC7B,gBAAgB,GAAmB;GACnC;GACA,gBAAgB,GAAmB;GACpC,EAEK,IAAY,MAAM,EAAqB,GAAa,EAAK;AAC/D,MAAI,CAAC,EAAW,QAAO,EAAE;EAKzB,IAAI,IAAY,EAAa;AAC7B,MAAI,CAAC,EACH,KAAI;GAEF,IAAM,IAAS,CAAC,GADC,MAAM,EAAsB,GAAW,GAAa,EAAK,CAC9C,CAAC,MAC1B,GAAM,OACJ,EAAM,MAAM,WAAW,EAAM,MAAM,WAAW,MAAM,EAAK,MAAM,WAAW,EAAK,MAAM,WAAW,GACpG,CAAC;AACF,OAAI,CAAC,EAAQ,QAAO,EAAE;AAEtB,GADA,IAAY,EAAO,IACnB,EAAiB,EAAU;UACrB;AACN,UAAO,EAAE;;AAIb,MAAI;AAEF,UAAO,EADK,MAAM,EAAqB,GAAW,GAAa,GAAW,GAAM,EAAsB,CACvE;WACxB,GAAO;AAWd,UAPA,QAAQ,KAAK,oEAAoE;IAC/E;IACA;IACA,OAAO,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;IAC9D,CAAC,EACF,EAAiB,KAAK,EACtB,EAAiB,KAAK,EACf,EAAE;;IAEV;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAQI,IAAU,SACsB;EAClC,aAAa,GAAgB;EAC7B,gBAAgB,GAAmB;EACnC;EACA,gBAAgB,GAAmB;EACpC,GACD;EAAC;EAAgB;EAAmB;EAAO,CAC5C,EAEK,IAAe,EAAY,YAAqD;EACpF,IAAM,IAAc,EAAe,EAAe;AAClD,MAAI,CAAC,EAAa,QAAO,EAAE;EAC3B,IAAM,IAAO,GAAS,EAIhB,IAAY,MAAM,EAAqB,GAAa,EAAK;AAC/D,MAAI,CAAC,EAAW,QAAO,EAAE;AAEzB,MAAI;GAGF,IAAM,KAFM,MAAM,EAAsB,GAAW,GAAa,EAAK,EAGlE,KAAK,OAAa;IACjB;IACA,WAAW,EAAQ,MAAM,WAAW,EAAQ,MAAM;IACnD,EAAE,CACF,MAAM,GAAM,OAAW,EAAM,aAAa,MAAM,EAAK,aAAa,GAAG,CACrE,MAAM,GAAG,EAAmB;AAc/B,UAAO,MAAM,QAAQ,IACnB,EAAO,IAAI,OAAO,EAAE,YAAS,mBAAgB;IAC3C,IAAM,IAAS,EAAiB,QAAQ,IAAI,EAAQ,GAAG,EACjD,IAAQ,KAAW,MAAM,EAAmB,GAAW,GAAa,GAAS,EAAK;AAExF,WADI,CAAC,KAAU,MAAU,cAAY,EAAiB,QAAQ,IAAI,EAAQ,IAAI,EAAM,EAC7E;KACL,IAAI,EAAQ;KACZ;KACA;KACA,QAAQ,EAAQ,OAAO,EAAa;KACrC;KACD,CACH;UACK;AACN,UAAO,EAAE;;IAEV;EAAC;EAAgB;EAAS;EAAqB,CAAC,EAkB7C,IAAa,EAAY,aAC7B,EAAiB,KAAK,EACf,QAAQ,SAAS,GACvB,CAAC,EAAiB,CAAC,EAShB,IAAgB,EACpB,OAAO,MAAqC;EAC1C,IAAM,IAAY,EAAa,SACzB,IAAc,EAAe,EAAe;AAC9C,GAAC,KAAa,CAAC,MAEnB,MAAM,EAAuB,GAAW,GAAa,GAAW,GAAS,CAAC,EAC1E,EAAiB,QAAQ,OAAO,EAAU,EAGtC,EAAa,YAAY,KAAW,EAAiB,KAAK;IAEhE;EAAC;EAAgB;EAAS;EAAiB,CAC5C,EAEK,IAAgB,EACpB,OAAO,MAA0D;EAC/D,IAAM,IAAY,EAAa,SACzB,IAAc,EAAe,EAAe;AAClD,MAAI,CAAC,KAAa,CAAC,EAAa,QAAO,EAAE;EAEzC,IAAM,IAAM,MAAM,EAAqB,GAAW,GAAa,GAAW,GAAS,EAAE,EAAsB;AAG3G,SADA,EAAiB,EAAU,EACpB,EAAoB,EAAI;IAEjC;EAAC;EAAgB;EAAS;EAAiB,CAC5C;AAED,QAAO,SACE;EAAE;EAAY;EAAa;EAAc;EAAY;EAAe;EAAe,GAC1F;EAAC;EAAY;EAAa;EAAc;EAAY;EAAe;EAAc,CAClF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.713.4",
3
+ "version": "2026.713.6",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [