neuralos 3.4.0 → 3.4.1
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/bin/gybackend.cjs +177 -2
- package/neuralos-3.4.1.tgz +0 -0
- package/package.json +1 -1
- package/neuralos-3.4.0.tgz +0 -0
package/bin/gybackend.cjs
CHANGED
|
@@ -258528,6 +258528,81 @@ var init_HistorySqliteStore = __esm({
|
|
|
258528
258528
|
});
|
|
258529
258529
|
})();
|
|
258530
258530
|
}
|
|
258531
|
+
/**
|
|
258532
|
+
* v3.4.1: append only the NEW messages for a session instead of rewriting
|
|
258533
|
+
* the whole session. saveUiSessions() deletes every row and re-inserts the
|
|
258534
|
+
* entire message list — on a long session that is a large synchronous
|
|
258535
|
+
* better-sqlite3 transaction on the main event loop, which is the
|
|
258536
|
+
* spinning-wheel freeze. This method appends from a given position in one
|
|
258537
|
+
* small transaction, so a debounced flush costs O(new messages), not
|
|
258538
|
+
* O(all messages).
|
|
258539
|
+
*
|
|
258540
|
+
* Returns the number of rows appended.
|
|
258541
|
+
*/
|
|
258542
|
+
appendUiSessionMessages(sessionId, messages, fromPosition, summary) {
|
|
258543
|
+
if (messages.length === 0 || fromPosition >= messages.length) {
|
|
258544
|
+
return 0;
|
|
258545
|
+
}
|
|
258546
|
+
const upsertSession = this.db.prepare(
|
|
258547
|
+
`INSERT INTO ui_sessions (
|
|
258548
|
+
id, title, updated_at, messages_count, last_message_preview
|
|
258549
|
+
) VALUES (
|
|
258550
|
+
@id, @title, @updatedAt, @messagesCount, @lastMessagePreview
|
|
258551
|
+
)
|
|
258552
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
258553
|
+
title = excluded.title,
|
|
258554
|
+
updated_at = excluded.updated_at,
|
|
258555
|
+
messages_count = excluded.messages_count,
|
|
258556
|
+
last_message_preview = excluded.last_message_preview`
|
|
258557
|
+
);
|
|
258558
|
+
const insertMessage = this.db.prepare(
|
|
258559
|
+
`INSERT INTO ui_session_messages (
|
|
258560
|
+
session_id, position, ui_message_id, backend_message_id, role, message_type, content, metadata_json, timestamp, streaming
|
|
258561
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
258562
|
+
);
|
|
258563
|
+
const slice = messages.slice(fromPosition);
|
|
258564
|
+
this.db.transaction(() => {
|
|
258565
|
+
if (summary) {
|
|
258566
|
+
upsertSession.run({
|
|
258567
|
+
id: sessionId,
|
|
258568
|
+
title: summary.title,
|
|
258569
|
+
updatedAt: summary.updatedAt,
|
|
258570
|
+
messagesCount: summary.messagesCount,
|
|
258571
|
+
lastMessagePreview: summary.lastMessagePreview
|
|
258572
|
+
});
|
|
258573
|
+
} else {
|
|
258574
|
+
const existing = this.db.prepare("SELECT title, updated_at FROM ui_sessions WHERE id = ?").get(sessionId);
|
|
258575
|
+
upsertSession.run({
|
|
258576
|
+
id: sessionId,
|
|
258577
|
+
title: existing?.title ?? "New Chat",
|
|
258578
|
+
updatedAt: Date.now(),
|
|
258579
|
+
messagesCount: (existing ? 0 : 0) + fromPosition + slice.length,
|
|
258580
|
+
lastMessagePreview: slice[slice.length - 1]?.content?.slice(0, 200) ?? ""
|
|
258581
|
+
});
|
|
258582
|
+
}
|
|
258583
|
+
for (let i = 0; i < slice.length; i++) {
|
|
258584
|
+
const message = slice[i];
|
|
258585
|
+
insertMessage.run(
|
|
258586
|
+
sessionId,
|
|
258587
|
+
fromPosition + i,
|
|
258588
|
+
message.id,
|
|
258589
|
+
message.backendMessageId ?? null,
|
|
258590
|
+
message.role,
|
|
258591
|
+
message.type,
|
|
258592
|
+
message.content,
|
|
258593
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
258594
|
+
message.timestamp,
|
|
258595
|
+
message.streaming ? 1 : 0
|
|
258596
|
+
);
|
|
258597
|
+
}
|
|
258598
|
+
})();
|
|
258599
|
+
return slice.length;
|
|
258600
|
+
}
|
|
258601
|
+
/** v3.4.1: how many messages are already persisted for a session. */
|
|
258602
|
+
countUiSessionMessages(sessionId) {
|
|
258603
|
+
const row = this.db.prepare("SELECT COUNT(*) AS n FROM ui_session_messages WHERE session_id = ?").get(sessionId);
|
|
258604
|
+
return row?.n ?? 0;
|
|
258605
|
+
}
|
|
258531
258606
|
deleteUiSessions(sessionIds) {
|
|
258532
258607
|
const ids = Array.from(
|
|
258533
258608
|
new Set(sessionIds.filter((id) => id.trim().length > 0))
|
|
@@ -373404,16 +373479,84 @@ function buildUiSessionSummary(session) {
|
|
|
373404
373479
|
}
|
|
373405
373480
|
|
|
373406
373481
|
// ../../packages/backend/src/services/UIHistoryService.ts
|
|
373407
|
-
var UIHistoryService = class {
|
|
373482
|
+
var UIHistoryService = class _UIHistoryService {
|
|
373408
373483
|
store;
|
|
373409
373484
|
sessionsCache = {};
|
|
373410
373485
|
sessionSummaryCache = {};
|
|
373411
373486
|
dirtySessions = /* @__PURE__ */ new Set();
|
|
373487
|
+
/**
|
|
373488
|
+
* v3.4.1: debounced auto-flush. Previously recordEvent() marked a session
|
|
373489
|
+
* dirty but NEVER flushed — messages reached SQLite only on rename /
|
|
373490
|
+
* rollback / branch or a graceful app close. Kill the process mid-run
|
|
373491
|
+
* (freeze, crash, force-quit) and every message since the last flush was
|
|
373492
|
+
* lost. That is the "work done, no record" bug.
|
|
373493
|
+
*
|
|
373494
|
+
* Now: flush within FLUSH_DEBOUNCE_MS of the last event, plus a synchronous
|
|
373495
|
+
* flush on beforeExit / SIGINT / SIGTERM.
|
|
373496
|
+
*/
|
|
373497
|
+
flushTimer = null;
|
|
373498
|
+
shutdownHooksInstalled = false;
|
|
373499
|
+
/** v3.4.1: messages already written to SQLite per session, so flush()
|
|
373500
|
+
* appends only the new ones instead of rewriting the whole session. */
|
|
373501
|
+
persistedMessageCount = /* @__PURE__ */ new Map();
|
|
373502
|
+
/** Flush debounce window — short enough to survive a crash, long enough
|
|
373503
|
+
* to batch a streaming burst into one write. */
|
|
373504
|
+
static FLUSH_DEBOUNCE_MS = 1500;
|
|
373412
373505
|
constructor(options) {
|
|
373413
373506
|
this.store = options?.store || new HistorySqliteStore();
|
|
373414
373507
|
this.sessionSummaryCache = this.buildSessionSummaryCache(
|
|
373415
373508
|
this.store.listUiSessionSummaries()
|
|
373416
373509
|
);
|
|
373510
|
+
this.installShutdownHooks();
|
|
373511
|
+
}
|
|
373512
|
+
installShutdownHooks() {
|
|
373513
|
+
if (this.shutdownHooksInstalled) return;
|
|
373514
|
+
this.shutdownHooksInstalled = true;
|
|
373515
|
+
const flushNow = () => {
|
|
373516
|
+
try {
|
|
373517
|
+
this.flush();
|
|
373518
|
+
} catch {
|
|
373519
|
+
}
|
|
373520
|
+
};
|
|
373521
|
+
process.once("beforeExit", flushNow);
|
|
373522
|
+
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
373523
|
+
if (!_UIHistoryService.activeFlushOnSignal.has(sig)) {
|
|
373524
|
+
_UIHistoryService.activeFlushOnSignal.add(sig);
|
|
373525
|
+
const hadListeners = process.listenerCount(sig) > 0;
|
|
373526
|
+
process.once(sig, () => {
|
|
373527
|
+
for (const svc of _UIHistoryService.instances) {
|
|
373528
|
+
try {
|
|
373529
|
+
svc.flush();
|
|
373530
|
+
} catch {
|
|
373531
|
+
}
|
|
373532
|
+
}
|
|
373533
|
+
_UIHistoryService.activeFlushOnSignal.delete(sig);
|
|
373534
|
+
if (!hadListeners) {
|
|
373535
|
+
process.kill(process.pid, sig);
|
|
373536
|
+
}
|
|
373537
|
+
});
|
|
373538
|
+
}
|
|
373539
|
+
}
|
|
373540
|
+
_UIHistoryService.instances.add(this);
|
|
373541
|
+
}
|
|
373542
|
+
/** Live instances — the shared signal handler flushes all of them. */
|
|
373543
|
+
static instances = /* @__PURE__ */ new Set();
|
|
373544
|
+
static activeFlushOnSignal = /* @__PURE__ */ new Set();
|
|
373545
|
+
scheduleFlush() {
|
|
373546
|
+
if (this.flushTimer) {
|
|
373547
|
+
clearTimeout(this.flushTimer);
|
|
373548
|
+
}
|
|
373549
|
+
this.flushTimer = setTimeout(() => {
|
|
373550
|
+
this.flushTimer = null;
|
|
373551
|
+
try {
|
|
373552
|
+
this.flush();
|
|
373553
|
+
} catch (error40) {
|
|
373554
|
+
console.error("[UIHistory] auto-flush failed:", error40);
|
|
373555
|
+
}
|
|
373556
|
+
}, _UIHistoryService.FLUSH_DEBOUNCE_MS);
|
|
373557
|
+
if (typeof this.flushTimer.unref === "function") {
|
|
373558
|
+
this.flushTimer.unref();
|
|
373559
|
+
}
|
|
373417
373560
|
}
|
|
373418
373561
|
buildSessionSummaryCache(summaries) {
|
|
373419
373562
|
const cache2 = {};
|
|
@@ -373433,6 +373576,9 @@ var UIHistoryService = class {
|
|
|
373433
373576
|
}
|
|
373434
373577
|
const sanitized = sanitizeUiSession(loaded);
|
|
373435
373578
|
this.sessionsCache[sessionId] = sanitized;
|
|
373579
|
+
if (!this.persistedMessageCount.has(sessionId)) {
|
|
373580
|
+
this.persistedMessageCount.set(sessionId, sanitized.messages.length);
|
|
373581
|
+
}
|
|
373436
373582
|
this.syncSessionSummary(sessionId);
|
|
373437
373583
|
return sanitized;
|
|
373438
373584
|
}
|
|
@@ -373459,6 +373605,7 @@ var UIHistoryService = class {
|
|
|
373459
373605
|
const actions = this.processEvent(session, event, sessionId);
|
|
373460
373606
|
this.syncSessionSummary(sessionId);
|
|
373461
373607
|
this.dirtySessions.add(sessionId);
|
|
373608
|
+
this.scheduleFlush();
|
|
373462
373609
|
return actions;
|
|
373463
373610
|
}
|
|
373464
373611
|
flush(sessionId) {
|
|
@@ -373467,6 +373614,7 @@ var UIHistoryService = class {
|
|
|
373467
373614
|
return;
|
|
373468
373615
|
}
|
|
373469
373616
|
const entries = [];
|
|
373617
|
+
const appendFrom = {};
|
|
373470
373618
|
sessionIds.forEach((id) => {
|
|
373471
373619
|
const session = this.sessionsCache[id];
|
|
373472
373620
|
if (!session) {
|
|
@@ -373478,9 +373626,34 @@ var UIHistoryService = class {
|
|
|
373478
373626
|
const summary = buildUiSessionSummary(sanitized);
|
|
373479
373627
|
this.sessionSummaryCache[id] = summary;
|
|
373480
373628
|
entries.push({ session: sanitized, summary });
|
|
373629
|
+
const already = this.persistedMessageCount.get(id);
|
|
373630
|
+
appendFrom[id] = typeof already === "number" && already <= sanitized.messages.length ? already : 0;
|
|
373481
373631
|
});
|
|
373482
373632
|
if (entries.length > 0) {
|
|
373483
|
-
|
|
373633
|
+
let usedIncremental = false;
|
|
373634
|
+
try {
|
|
373635
|
+
let appended = 0;
|
|
373636
|
+
for (const { session, summary } of entries) {
|
|
373637
|
+
const from = appendFrom[session.id] ?? 0;
|
|
373638
|
+
const n2 = this.store.appendUiSessionMessages(
|
|
373639
|
+
session.id,
|
|
373640
|
+
session.messages,
|
|
373641
|
+
from,
|
|
373642
|
+
summary
|
|
373643
|
+
);
|
|
373644
|
+
appended += n2;
|
|
373645
|
+
this.persistedMessageCount.set(session.id, session.messages.length);
|
|
373646
|
+
}
|
|
373647
|
+
usedIncremental = true;
|
|
373648
|
+
} catch {
|
|
373649
|
+
usedIncremental = false;
|
|
373650
|
+
}
|
|
373651
|
+
if (!usedIncremental) {
|
|
373652
|
+
this.store.saveUiSessions(entries);
|
|
373653
|
+
entries.forEach(
|
|
373654
|
+
({ session }) => this.persistedMessageCount.set(session.id, session.messages.length)
|
|
373655
|
+
);
|
|
373656
|
+
}
|
|
373484
373657
|
}
|
|
373485
373658
|
sessionIds.forEach((id) => this.dirtySessions.delete(id));
|
|
373486
373659
|
}
|
|
@@ -373993,6 +374166,7 @@ Error: ${event.message}` : ""),
|
|
|
373993
374166
|
delete this.sessionsCache[id];
|
|
373994
374167
|
delete this.sessionSummaryCache[id];
|
|
373995
374168
|
this.dirtySessions.delete(id);
|
|
374169
|
+
this.persistedMessageCount.delete(id);
|
|
373996
374170
|
});
|
|
373997
374171
|
this.store.deleteUiSessions(ids);
|
|
373998
374172
|
}
|
|
@@ -374031,6 +374205,7 @@ Error: ${event.message}` : ""),
|
|
|
374031
374205
|
);
|
|
374032
374206
|
this.syncSessionSummary(sessionId);
|
|
374033
374207
|
this.dirtySessions.add(sessionId);
|
|
374208
|
+
this.persistedMessageCount.delete(sessionId);
|
|
374034
374209
|
this.flush(sessionId);
|
|
374035
374210
|
return removedCount;
|
|
374036
374211
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuralos",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "AI-native terminal & agentic-AI operations platform for Forward Deployed Engineers & SREs: AIOps closed-loop remediation, AI SRE, self-healing infrastructure, runbook automation, ChatOps; executes over SSH/WinRM/serial under policy with tamper-evident audit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"forward-deployed-engineer",
|
package/neuralos-3.4.0.tgz
DELETED
|
Binary file
|