@robosystems/core 0.8.4 → 0.8.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.
package/auth-core/sso.js CHANGED
@@ -9,6 +9,28 @@ const debugLog = (message, error) => {
9
9
  console.debug(`[SSO] ${message}`, error);
10
10
  }
11
11
  };
12
+ /**
13
+ * Reduce a `returnUrl` to a same-origin path, or reject it.
14
+ *
15
+ * The value reaches us from a query parameter (or the sessionStorage mirror of
16
+ * one), so it is attacker-controlled on any crafted SSO link: an absolute URL
17
+ * would bounce the freshly authenticated user to another site, and a
18
+ * `javascript:` payload would execute as this origin. Cross-app returns don't
19
+ * need the escape hatch — `getSSORedirectUrl` hands the path to the *target*
20
+ * app, which resolves it against its own origin — so a bare absolute path is
21
+ * the only shape we ever legitimately produce.
22
+ */
23
+ const sanitizeReturnUrl = (returnUrl) => {
24
+ if (!returnUrl)
25
+ return null;
26
+ // `//host` and `/\host` are both protocol-relative once a browser
27
+ // normalises them, so a leading slash alone isn't enough.
28
+ if (!returnUrl.startsWith('/'))
29
+ return null;
30
+ if (returnUrl.startsWith('//') || returnUrl.startsWith('/\\'))
31
+ return null;
32
+ return returnUrl;
33
+ };
12
34
  export class SSOManager {
13
35
  constructor(apiUrl) {
14
36
  this.authClient = new RoboSystemsAuthClient(apiUrl);
@@ -84,7 +106,7 @@ export class SSOManager {
84
106
  debugLog('sessionStorage read failed during SSO login', error);
85
107
  }
86
108
  const finalSessionId = sessionId; // Only use URL parameter for session ID
87
- const finalReturnUrl = returnUrl || sessionStorageReturn;
109
+ const finalReturnUrl = sanitizeReturnUrl(returnUrl || sessionStorageReturn);
88
110
  if (!finalSessionId) {
89
111
  return null;
90
112
  }
@@ -68,6 +68,10 @@ export function ConsoleContent({ config }) {
68
68
  const [historyIndex, setHistoryIndex] = useState(-1);
69
69
  const terminalEndRef = useRef(null);
70
70
  const terminalScrollRef = useRef(null);
71
+ // Prior operator exchanges, sent as `history` so a follow-up like "and for
72
+ // June?" resolves against the previous answer. Bounded to the last three
73
+ // exchanges — the backend seeds only its last five turns anyway.
74
+ const conversationRef = useRef([]);
71
75
  const [currentQueryStartTime, setCurrentQueryStartTime] = useState(null);
72
76
  const [apiVersion, setApiVersion] = useState(null);
73
77
  const [operatorProgress, setOperatorProgress] = useState({ isRunning: false, message: '' });
@@ -190,6 +194,9 @@ export function ConsoleContent({ config }) {
190
194
  streamingQuery.cancelQuery();
191
195
  }
192
196
  setTerminalMessages([]);
197
+ // A follow-up on the new graph must not resolve against the previous
198
+ // graph's answers.
199
+ conversationRef.current = [];
193
200
  setCurrentQueryStartTime(null);
194
201
  addSystemMessage(`═══════════════════════════════════════════════════════════════\n` +
195
202
  `${config.welcome.contextLabel} context changed: ${previousGraphId.current} → ${graphId}\n` +
@@ -317,10 +324,11 @@ export function ConsoleContent({ config }) {
317
324
  const startTime = Date.now();
318
325
  try {
319
326
  const { clients } = await import('@robosystems/client/clients');
320
- const result = await clients.operator.executeQuery(graphId, {
321
- message: userQuery,
322
- mode: 'quick',
323
- }, {
327
+ const history = conversationRef.current;
328
+ const result = await clients.operator.executeQuery(graphId, Object.assign({ message: userQuery,
329
+ // `standard` gives the operator loop six tool turns; `quick` (three)
330
+ // was exhausted by schema + examples before the first query ran.
331
+ mode: 'standard' }, (history.length > 0 ? { history } : {})), {
324
332
  mode: 'auto',
325
333
  onProgress: (message, percentage) => {
326
334
  setOperatorProgress({
@@ -331,6 +339,19 @@ export function ConsoleContent({ config }) {
331
339
  },
332
340
  });
333
341
  setOperatorProgress({ isRunning: false, message: '' });
342
+ // The API reports operator failures (credit pre-flight, timeouts,
343
+ // unbilled calls) as HTTP 200 with `error_details` set and an
344
+ // explanatory `content`. Render those as errors, not as answers.
345
+ const errorDetails = result.error_details;
346
+ if (errorDetails) {
347
+ const detail = [
348
+ errorDetails.message,
349
+ errorDetails.error,
350
+ errorDetails.detail,
351
+ ].find((v) => typeof v === 'string' && v.length > 0);
352
+ addErrorMessage(`Operator error: ${detail || result.content || 'The operator could not complete this request.'}`);
353
+ return;
354
+ }
334
355
  const duration = Date.now() - startTime;
335
356
  const metadata = (result.metadata || {});
336
357
  const creditsUsed = metadata.credits_consumed;
@@ -385,6 +406,13 @@ export function ConsoleContent({ config }) {
385
406
  // plain footer, matching the direct-query result style.
386
407
  if (narrative) {
387
408
  addResultMessage(narrative, rows, cypher, { markdown: true, footer });
409
+ // Record the exchange for follow-ups. Only a narrative answer is
410
+ // worth carrying; the answer is clipped so history stays cheap.
411
+ conversationRef.current = [
412
+ ...conversationRef.current,
413
+ { role: 'user', content: userQuery },
414
+ { role: 'assistant', content: narrative.slice(0, 2000) },
415
+ ].slice(-6);
388
416
  }
389
417
  else {
390
418
  addResultMessage(footer, rows, cypher);
@@ -611,6 +639,7 @@ export function ConsoleContent({ config }) {
611
639
  return;
612
640
  case '/clear':
613
641
  setTerminalMessages([]);
642
+ conversationRef.current = [];
614
643
  addSystemMessage('Console cleared.', true);
615
644
  return;
616
645
  case '/examples': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/core",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Shared RoboSystems frontend core library (auth, contexts, task monitoring, UI components) for the RoboSystems Next.js apps",
5
5
  "license": "MIT",
6
6
  "private": false,