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