@astermind/cybernetic-chatbot-client 2.2.59 → 2.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CyberneticClient.d.ts +31 -0
- package/dist/CyberneticClient.d.ts.map +1 -1
- package/dist/cybernetic-chatbot-client-full.esm.js +293 -5
- package/dist/cybernetic-chatbot-client-full.esm.js.map +1 -1
- package/dist/cybernetic-chatbot-client-full.min.js +1 -1
- package/dist/cybernetic-chatbot-client-full.min.js.map +1 -1
- package/dist/cybernetic-chatbot-client-full.umd.js +293 -5
- package/dist/cybernetic-chatbot-client-full.umd.js.map +1 -1
- package/dist/cybernetic-chatbot-client.esm.js +294 -6
- package/dist/cybernetic-chatbot-client.esm.js.map +1 -1
- package/dist/cybernetic-chatbot-client.min.js +1 -1
- package/dist/cybernetic-chatbot-client.min.js.map +1 -1
- package/dist/cybernetic-chatbot-client.umd.js +294 -5
- package/dist/cybernetic-chatbot-client.umd.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1377,6 +1377,230 @@
|
|
|
1377
1377
|
}
|
|
1378
1378
|
}
|
|
1379
1379
|
|
|
1380
|
+
// src/CyberneticSessionStorage.ts
|
|
1381
|
+
// Session storage manager for persisting chatbot session across page navigations
|
|
1382
|
+
const DEFAULT_CONFIG = {
|
|
1383
|
+
enabled: true,
|
|
1384
|
+
storageKey: 'astermind_session',
|
|
1385
|
+
sessionTtl: 30 * 60 * 1000, // 30 minutes
|
|
1386
|
+
persistMessages: true,
|
|
1387
|
+
maxMessages: 50
|
|
1388
|
+
};
|
|
1389
|
+
/**
|
|
1390
|
+
* Session storage manager for persisting chatbot sessions
|
|
1391
|
+
*
|
|
1392
|
+
* Uses sessionStorage to maintain conversation continuity across
|
|
1393
|
+
* page navigations within the same browser session.
|
|
1394
|
+
*/
|
|
1395
|
+
class CyberneticSessionStorage {
|
|
1396
|
+
constructor(config) {
|
|
1397
|
+
this.cachedSession = null;
|
|
1398
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1399
|
+
// Load cached session on initialization
|
|
1400
|
+
if (this.config.enabled && this.isAvailable()) {
|
|
1401
|
+
this.cachedSession = this.loadFromStorage();
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Check if sessionStorage is available
|
|
1406
|
+
*/
|
|
1407
|
+
isAvailable() {
|
|
1408
|
+
if (typeof window === 'undefined')
|
|
1409
|
+
return false;
|
|
1410
|
+
try {
|
|
1411
|
+
const testKey = '__astermind_test__';
|
|
1412
|
+
window.sessionStorage.setItem(testKey, 'test');
|
|
1413
|
+
window.sessionStorage.removeItem(testKey);
|
|
1414
|
+
return true;
|
|
1415
|
+
}
|
|
1416
|
+
catch {
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Get the current session ID (if valid)
|
|
1422
|
+
*/
|
|
1423
|
+
getSessionId() {
|
|
1424
|
+
if (!this.config.enabled)
|
|
1425
|
+
return null;
|
|
1426
|
+
const session = this.getSession();
|
|
1427
|
+
return session?.sessionId ?? null;
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Get the full stored session (if valid)
|
|
1431
|
+
*/
|
|
1432
|
+
getSession() {
|
|
1433
|
+
if (!this.config.enabled)
|
|
1434
|
+
return null;
|
|
1435
|
+
// Use cached session if available
|
|
1436
|
+
if (this.cachedSession) {
|
|
1437
|
+
if (this.isSessionValid(this.cachedSession)) {
|
|
1438
|
+
return this.cachedSession;
|
|
1439
|
+
}
|
|
1440
|
+
// Session expired, clear it
|
|
1441
|
+
this.clear();
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
// Try loading from storage
|
|
1445
|
+
const session = this.loadFromStorage();
|
|
1446
|
+
if (session && this.isSessionValid(session)) {
|
|
1447
|
+
this.cachedSession = session;
|
|
1448
|
+
return session;
|
|
1449
|
+
}
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Save/update session ID
|
|
1454
|
+
*/
|
|
1455
|
+
saveSessionId(sessionId) {
|
|
1456
|
+
if (!this.config.enabled || !sessionId)
|
|
1457
|
+
return;
|
|
1458
|
+
const existingSession = this.getSession();
|
|
1459
|
+
const session = {
|
|
1460
|
+
sessionId,
|
|
1461
|
+
updatedAt: Date.now(),
|
|
1462
|
+
messages: existingSession?.messages ?? []
|
|
1463
|
+
};
|
|
1464
|
+
this.saveToStorage(session);
|
|
1465
|
+
this.cachedSession = session;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Add a message to the conversation history
|
|
1469
|
+
*/
|
|
1470
|
+
addMessage(message) {
|
|
1471
|
+
if (!this.config.enabled || !this.config.persistMessages)
|
|
1472
|
+
return;
|
|
1473
|
+
const session = this.getSession();
|
|
1474
|
+
if (!session)
|
|
1475
|
+
return;
|
|
1476
|
+
const newMessage = {
|
|
1477
|
+
...message,
|
|
1478
|
+
id: this.generateMessageId(),
|
|
1479
|
+
timestamp: Date.now()
|
|
1480
|
+
};
|
|
1481
|
+
// Add new message and trim if necessary
|
|
1482
|
+
session.messages = session.messages ?? [];
|
|
1483
|
+
session.messages.push(newMessage);
|
|
1484
|
+
// Keep only the most recent messages
|
|
1485
|
+
if (session.messages.length > this.config.maxMessages) {
|
|
1486
|
+
session.messages = session.messages.slice(-this.config.maxMessages);
|
|
1487
|
+
}
|
|
1488
|
+
session.updatedAt = Date.now();
|
|
1489
|
+
this.saveToStorage(session);
|
|
1490
|
+
this.cachedSession = session;
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Get conversation messages
|
|
1494
|
+
*/
|
|
1495
|
+
getMessages() {
|
|
1496
|
+
if (!this.config.enabled || !this.config.persistMessages)
|
|
1497
|
+
return [];
|
|
1498
|
+
const session = this.getSession();
|
|
1499
|
+
return session?.messages ?? [];
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Clear the stored session
|
|
1503
|
+
*/
|
|
1504
|
+
clear() {
|
|
1505
|
+
this.cachedSession = null;
|
|
1506
|
+
if (!this.isAvailable())
|
|
1507
|
+
return;
|
|
1508
|
+
try {
|
|
1509
|
+
window.sessionStorage.removeItem(this.config.storageKey);
|
|
1510
|
+
}
|
|
1511
|
+
catch (error) {
|
|
1512
|
+
console.warn('[CyberneticSessionStorage] Failed to clear session:', error);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Start a new session (clears existing and optionally sets new ID)
|
|
1517
|
+
*/
|
|
1518
|
+
startNewSession(sessionId) {
|
|
1519
|
+
this.clear();
|
|
1520
|
+
if (sessionId) {
|
|
1521
|
+
this.saveSessionId(sessionId);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Check if we have a valid stored session
|
|
1526
|
+
*/
|
|
1527
|
+
hasValidSession() {
|
|
1528
|
+
return this.getSession() !== null;
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Get session info for debugging/status
|
|
1532
|
+
*/
|
|
1533
|
+
getSessionInfo() {
|
|
1534
|
+
const session = this.getSession();
|
|
1535
|
+
if (!session) {
|
|
1536
|
+
return {
|
|
1537
|
+
hasSession: false,
|
|
1538
|
+
sessionId: null,
|
|
1539
|
+
messageCount: 0,
|
|
1540
|
+
lastUpdated: null,
|
|
1541
|
+
ttlRemaining: null
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
const ttlRemaining = Math.max(0, this.config.sessionTtl - (Date.now() - session.updatedAt));
|
|
1545
|
+
return {
|
|
1546
|
+
hasSession: true,
|
|
1547
|
+
sessionId: session.sessionId,
|
|
1548
|
+
messageCount: session.messages?.length ?? 0,
|
|
1549
|
+
lastUpdated: new Date(session.updatedAt),
|
|
1550
|
+
ttlRemaining
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
// ==================== PRIVATE METHODS ====================
|
|
1554
|
+
/**
|
|
1555
|
+
* Check if a session is still valid (not expired)
|
|
1556
|
+
*/
|
|
1557
|
+
isSessionValid(session) {
|
|
1558
|
+
const age = Date.now() - session.updatedAt;
|
|
1559
|
+
return age < this.config.sessionTtl;
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Load session from sessionStorage
|
|
1563
|
+
*/
|
|
1564
|
+
loadFromStorage() {
|
|
1565
|
+
if (!this.isAvailable())
|
|
1566
|
+
return null;
|
|
1567
|
+
try {
|
|
1568
|
+
const stored = window.sessionStorage.getItem(this.config.storageKey);
|
|
1569
|
+
if (!stored)
|
|
1570
|
+
return null;
|
|
1571
|
+
const session = JSON.parse(stored);
|
|
1572
|
+
// Validate structure
|
|
1573
|
+
if (!session.sessionId || typeof session.updatedAt !== 'number') {
|
|
1574
|
+
return null;
|
|
1575
|
+
}
|
|
1576
|
+
return session;
|
|
1577
|
+
}
|
|
1578
|
+
catch (error) {
|
|
1579
|
+
console.warn('[CyberneticSessionStorage] Failed to load session:', error);
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Save session to sessionStorage
|
|
1585
|
+
*/
|
|
1586
|
+
saveToStorage(session) {
|
|
1587
|
+
if (!this.isAvailable())
|
|
1588
|
+
return;
|
|
1589
|
+
try {
|
|
1590
|
+
window.sessionStorage.setItem(this.config.storageKey, JSON.stringify(session));
|
|
1591
|
+
}
|
|
1592
|
+
catch (error) {
|
|
1593
|
+
console.warn('[CyberneticSessionStorage] Failed to save session:', error);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Generate a unique message ID
|
|
1598
|
+
*/
|
|
1599
|
+
generateMessageId() {
|
|
1600
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1380
1604
|
// src/config.ts
|
|
1381
1605
|
// Configuration loading and validation
|
|
1382
1606
|
/** Default API URL when not specified */
|
|
@@ -2341,6 +2565,8 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2341
2565
|
licenseKey: config.licenseKey,
|
|
2342
2566
|
environment: config.environment,
|
|
2343
2567
|
});
|
|
2568
|
+
// Initialize session storage (auto-loads existing session from browser sessionStorage)
|
|
2569
|
+
this.sessionStorage = new CyberneticSessionStorage(config.session);
|
|
2344
2570
|
// Verify license asynchronously (non-blocking)
|
|
2345
2571
|
this.licenseManager.verify().then(() => {
|
|
2346
2572
|
// Check client feature after verification
|
|
@@ -2641,6 +2867,40 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2641
2867
|
getOfflineStorage() {
|
|
2642
2868
|
return this.offlineStorage;
|
|
2643
2869
|
}
|
|
2870
|
+
// ==================== SESSION PERSISTENCE METHODS (ADR-028) ====================
|
|
2871
|
+
/**
|
|
2872
|
+
* Get stored messages from a previous session for UI restoration.
|
|
2873
|
+
* Call after construction to check for resumable conversations.
|
|
2874
|
+
*/
|
|
2875
|
+
getStoredMessages() {
|
|
2876
|
+
return this.sessionStorage.getMessages();
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Check if a valid stored session exists that can be resumed.
|
|
2880
|
+
*/
|
|
2881
|
+
hasStoredSession() {
|
|
2882
|
+
return this.sessionStorage.hasValidSession();
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Get session info for debugging/status display.
|
|
2886
|
+
*/
|
|
2887
|
+
getSessionInfo() {
|
|
2888
|
+
return this.sessionStorage.getSessionInfo();
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Clear the stored session and start fresh.
|
|
2892
|
+
* Use for "New Conversation" actions.
|
|
2893
|
+
*/
|
|
2894
|
+
clearSession() {
|
|
2895
|
+
this.sessionStorage.clear();
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Start a new session, clearing any stored data.
|
|
2899
|
+
* Optionally sets a new sessionId immediately.
|
|
2900
|
+
*/
|
|
2901
|
+
startNewSession(sessionId) {
|
|
2902
|
+
this.sessionStorage.startNewSession(sessionId);
|
|
2903
|
+
}
|
|
2644
2904
|
// ==================== CORE METHODS ====================
|
|
2645
2905
|
/**
|
|
2646
2906
|
* Send a message to the chatbot
|
|
@@ -2654,6 +2914,11 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2654
2914
|
if (!message || typeof message !== 'string') {
|
|
2655
2915
|
return this.createErrorResponse('Message is required', 'none');
|
|
2656
2916
|
}
|
|
2917
|
+
// Auto-resolve sessionId: explicit option > stored session > none (ADR-028)
|
|
2918
|
+
const resolvedSessionId = options?.sessionId ?? this.sessionStorage.getSessionId() ?? undefined;
|
|
2919
|
+
const resolvedOptions = resolvedSessionId
|
|
2920
|
+
? { ...options, sessionId: resolvedSessionId }
|
|
2921
|
+
: options;
|
|
2657
2922
|
// Check maintenance mode before API call (ADR-200)
|
|
2658
2923
|
const settings = await this.checkSystemStatus();
|
|
2659
2924
|
if (settings.maintenanceMode || settings.forceOfflineClients) {
|
|
@@ -2674,10 +2939,16 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2674
2939
|
}
|
|
2675
2940
|
// Try API first
|
|
2676
2941
|
try {
|
|
2677
|
-
const response = await this.apiWithRetry(message,
|
|
2942
|
+
const response = await this.apiWithRetry(message, resolvedOptions);
|
|
2678
2943
|
this.setStatus('online');
|
|
2679
2944
|
// Process response through license manager (may add warning in production)
|
|
2680
2945
|
const processedReply = this.licenseManager.processResponse(response.reply);
|
|
2946
|
+
// Persist session state (ADR-028)
|
|
2947
|
+
if (response.sessionId) {
|
|
2948
|
+
this.sessionStorage.saveSessionId(response.sessionId);
|
|
2949
|
+
}
|
|
2950
|
+
this.sessionStorage.addMessage({ role: 'user', content: message });
|
|
2951
|
+
this.sessionStorage.addMessage({ role: 'assistant', content: processedReply, confidence: 'high' });
|
|
2681
2952
|
return {
|
|
2682
2953
|
reply: processedReply,
|
|
2683
2954
|
confidence: 'high',
|
|
@@ -2723,6 +2994,13 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2723
2994
|
});
|
|
2724
2995
|
return;
|
|
2725
2996
|
}
|
|
2997
|
+
// Auto-resolve sessionId: explicit option > stored session > none (ADR-028)
|
|
2998
|
+
const resolvedSessionId = options?.sessionId ?? this.sessionStorage.getSessionId() ?? undefined;
|
|
2999
|
+
const resolvedOptions = resolvedSessionId
|
|
3000
|
+
? { ...options, sessionId: resolvedSessionId }
|
|
3001
|
+
: options;
|
|
3002
|
+
// Save user message immediately (ADR-028)
|
|
3003
|
+
this.sessionStorage.addMessage({ role: 'user', content: message });
|
|
2726
3004
|
// Check maintenance mode before API call (ADR-200)
|
|
2727
3005
|
const settings = await this.checkSystemStatus();
|
|
2728
3006
|
if (settings.maintenanceMode || settings.forceOfflineClients) {
|
|
@@ -2735,14 +3013,19 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2735
3013
|
if (this.wsTransport && this.config.transport !== 'rest') {
|
|
2736
3014
|
try {
|
|
2737
3015
|
await this.wsTransport.chatStream(message, {
|
|
2738
|
-
sessionId:
|
|
2739
|
-
context:
|
|
3016
|
+
sessionId: resolvedOptions?.sessionId,
|
|
3017
|
+
context: resolvedOptions?.context,
|
|
2740
3018
|
onToken: callbacks.onToken,
|
|
2741
3019
|
onSources: callbacks.onSources,
|
|
2742
3020
|
onComplete: (response) => {
|
|
2743
3021
|
this.setStatus('online');
|
|
2744
3022
|
// Process through license manager
|
|
2745
3023
|
const processedReply = this.licenseManager.processResponse(response.reply);
|
|
3024
|
+
// Persist session state (ADR-028)
|
|
3025
|
+
if (response.sessionId) {
|
|
3026
|
+
this.sessionStorage.saveSessionId(response.sessionId);
|
|
3027
|
+
}
|
|
3028
|
+
this.sessionStorage.addMessage({ role: 'assistant', content: processedReply, confidence: 'high' });
|
|
2746
3029
|
callbacks.onComplete?.({
|
|
2747
3030
|
...response,
|
|
2748
3031
|
reply: processedReply
|
|
@@ -2752,7 +3035,7 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2752
3035
|
// In 'auto' mode, fall back to SSE on WS error
|
|
2753
3036
|
if (this.config.transport === 'auto') {
|
|
2754
3037
|
console.warn('[Cybernetic] WebSocket error, falling back to SSE:', error.message);
|
|
2755
|
-
this.streamViaSSE(message, callbacks,
|
|
3038
|
+
this.streamViaSSE(message, callbacks, resolvedOptions);
|
|
2756
3039
|
}
|
|
2757
3040
|
else {
|
|
2758
3041
|
// 'websocket' mode — no fallback
|
|
@@ -2778,7 +3061,7 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2778
3061
|
}
|
|
2779
3062
|
}
|
|
2780
3063
|
// REST+SSE path (on-prem, or fallback from WebSocket)
|
|
2781
|
-
await this.streamViaSSE(message, callbacks,
|
|
3064
|
+
await this.streamViaSSE(message, callbacks, resolvedOptions);
|
|
2782
3065
|
}
|
|
2783
3066
|
/**
|
|
2784
3067
|
* Stream chat via REST+SSE (original transport).
|
|
@@ -2795,6 +3078,11 @@ LJ5AZXvOhHaXdHzMuYKX5BpK4w7TqbPvJ6QPvKmLKvHh1VKcUJ6mJQgJJw==
|
|
|
2795
3078
|
this.setStatus('online');
|
|
2796
3079
|
// Process response through license manager (may add warning in production)
|
|
2797
3080
|
const processedReply = this.licenseManager.processResponse(data.fullText);
|
|
3081
|
+
// Persist session state (ADR-028)
|
|
3082
|
+
if (data.sessionId) {
|
|
3083
|
+
this.sessionStorage.saveSessionId(data.sessionId);
|
|
3084
|
+
}
|
|
3085
|
+
this.sessionStorage.addMessage({ role: 'assistant', content: processedReply, confidence: 'high' });
|
|
2798
3086
|
callbacks.onComplete?.({
|
|
2799
3087
|
reply: processedReply,
|
|
2800
3088
|
confidence: 'high',
|