neuralos 3.8.2 → 3.8.4
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 +362 -52
- package/package.json +1 -1
package/bin/gybackend.cjs
CHANGED
|
@@ -258480,6 +258480,9 @@ var HistorySqliteStore_exports = {};
|
|
|
258480
258480
|
__export(HistorySqliteStore_exports, {
|
|
258481
258481
|
HistorySqliteStore: () => HistorySqliteStore
|
|
258482
258482
|
});
|
|
258483
|
+
function digestFor(message) {
|
|
258484
|
+
return `${message.type}\0${message.dataJson ?? ""}`;
|
|
258485
|
+
}
|
|
258483
258486
|
var import_node_fs5, import_node_path8, HistorySqliteStore;
|
|
258484
258487
|
var init_HistorySqliteStore = __esm({
|
|
258485
258488
|
"../../packages/backend/src/services/history/HistorySqliteStore.ts"() {
|
|
@@ -258651,14 +258654,153 @@ var init_HistorySqliteStore = __esm({
|
|
|
258651
258654
|
lastProfileMaxTokens: row.last_profile_max_tokens ?? void 0
|
|
258652
258655
|
}));
|
|
258653
258656
|
}
|
|
258657
|
+
/**
|
|
258658
|
+
* Every session WITH all messages.
|
|
258659
|
+
*
|
|
258660
|
+
* FREEZE WARNING (v3.8.4): this JSON.parses every message of every session
|
|
258661
|
+
* in one synchronous burst — on this machine's 1.6 GB / multi-session store
|
|
258662
|
+
* that is seconds of blocked event loop (so the UI freezes, because
|
|
258663
|
+
* better-sqlite3 is synchronous and runs on the same thread).
|
|
258664
|
+
*
|
|
258665
|
+
* It is only acceptable for genuinely whole-store work. Callers that merely
|
|
258666
|
+
* need session *lists* must use `listChatSessionSummaries()` (COUNT only, no
|
|
258667
|
+
* message bodies) — see `searchChatHistoryBounded` in historySearch.ts,
|
|
258668
|
+
* which replaced the old bridge call to this method.
|
|
258669
|
+
*/
|
|
258654
258670
|
listChatSessions() {
|
|
258655
258671
|
return this.listChatSessionSummaries().map((summary) => this.loadChatSession(summary.id)).filter(
|
|
258656
258672
|
(session) => session !== null
|
|
258657
258673
|
);
|
|
258658
258674
|
}
|
|
258675
|
+
/**
|
|
258676
|
+
* Session meta WITHOUT messages.
|
|
258677
|
+
*
|
|
258678
|
+
* Two fixed-cost hot-path bugs this closes:
|
|
258679
|
+
*
|
|
258680
|
+
* 1. `ChatHistoryService.saveSession` called `loadChatSession()` purely to
|
|
258681
|
+
* learn `createdAt`. That JSON.parse'd the ENTIRE session — measured
|
|
258682
|
+
* ~117 MB / ~1.5 s of parse for a 6k-message session — and threw every
|
|
258683
|
+
* parsed message away, on every save.
|
|
258684
|
+
* 2. `AgentService_v2.trySaveSessionFromCheckpoint` called `loadSession()`
|
|
258685
|
+
* for a default it never used, because `updateSessionFromMessages()`
|
|
258686
|
+
* rebuilds `session.messages` from scratch. Dropping that call outright
|
|
258687
|
+
* would have reset the session TITLE to "New Session" on every restore,
|
|
258688
|
+
* so the title is read here too.
|
|
258689
|
+
*/
|
|
258690
|
+
getChatSessionMeta(sessionId) {
|
|
258691
|
+
const row = this.db.prepare("SELECT created_at, title FROM chat_sessions WHERE id = ?").get(sessionId);
|
|
258692
|
+
return row ? { createdAt: row.created_at, title: row.title } : null;
|
|
258693
|
+
}
|
|
258694
|
+
/** Created-at scalar only — a single indexed row, never a message parse. */
|
|
258695
|
+
getChatSessionCreatedAt(sessionId) {
|
|
258696
|
+
return this.getChatSessionMeta(sessionId)?.createdAt;
|
|
258697
|
+
}
|
|
258698
|
+
/**
|
|
258699
|
+
* Existing message bodies as RAW TEXT, ordered by position, WITHOUT parsing.
|
|
258700
|
+
*
|
|
258701
|
+
* The raw `message_data_json` is needed for a byte-exact comparison against
|
|
258702
|
+
* the newly serialized body. Deliberately NOT JSON.parse'd: the parse is the
|
|
258703
|
+
* expensive half of the freeze (~1.5 s / 117 MB measured) and is unnecessary
|
|
258704
|
+
* for an equality test.
|
|
258705
|
+
*
|
|
258706
|
+
* (An earlier revision compared only `substr(json, 1, 200)`. That was WRONG:
|
|
258707
|
+
* a message whose content grows past the first 200 characters — exactly what
|
|
258708
|
+
* streaming does to the last AI message — leaves the prefix untouched, so the
|
|
258709
|
+
* row would be skipped and the persisted history would silently stay STALE.
|
|
258710
|
+
* Full-text comparison is what makes "unchanged => skip" safe.)
|
|
258711
|
+
*/
|
|
258712
|
+
loadSessionMessageState(sessionId) {
|
|
258713
|
+
const rows = this.db.prepare(
|
|
258714
|
+
`SELECT message_id, message_type, position, message_data_json
|
|
258715
|
+
FROM chat_session_messages
|
|
258716
|
+
WHERE session_id = ?
|
|
258717
|
+
ORDER BY position ASC`
|
|
258718
|
+
).all(sessionId);
|
|
258719
|
+
return rows.map((row) => ({
|
|
258720
|
+
id: row.message_id,
|
|
258721
|
+
type: row.message_type,
|
|
258722
|
+
position: row.position,
|
|
258723
|
+
jsonText: row.message_data_json ?? ""
|
|
258724
|
+
}));
|
|
258725
|
+
}
|
|
258726
|
+
/**
|
|
258727
|
+
* Incremental write: touch only the rows that actually moved.
|
|
258728
|
+
*
|
|
258729
|
+
* The old path ran `DELETE ALL` + `INSERT ALL` on every save, rewriting the
|
|
258730
|
+
* whole session payload each time. Here, unchanged messages stay on disk
|
|
258731
|
+
* untouched; messages that are genuinely absent (compaction, rollback) are
|
|
258732
|
+
* removed individually.
|
|
258733
|
+
*/
|
|
258734
|
+
applyChatSessionDelta(sessionId, desired, existingDigests, writeAll) {
|
|
258735
|
+
const desiredIds = /* @__PURE__ */ new Set();
|
|
258736
|
+
for (const m2 of desired) {
|
|
258737
|
+
desiredIds.add(m2.id);
|
|
258738
|
+
}
|
|
258739
|
+
const changed = writeAll ? desired : desired.filter((m2) => existingDigests.get(m2.id) !== digestFor(m2));
|
|
258740
|
+
const toRemove = [];
|
|
258741
|
+
for (const id of existingDigests.keys()) {
|
|
258742
|
+
if (!desiredIds.has(id)) {
|
|
258743
|
+
toRemove.push(id);
|
|
258744
|
+
}
|
|
258745
|
+
}
|
|
258746
|
+
if (changed.length === 0 && toRemove.length === 0) {
|
|
258747
|
+
return;
|
|
258748
|
+
}
|
|
258749
|
+
const upsert = this.db.prepare(
|
|
258750
|
+
`INSERT INTO chat_session_messages (
|
|
258751
|
+
session_id, position, message_id, message_type, message_data_json
|
|
258752
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
258753
|
+
ON CONFLICT(session_id, message_id) DO UPDATE SET
|
|
258754
|
+
position = excluded.position,
|
|
258755
|
+
message_type = excluded.message_type,
|
|
258756
|
+
message_data_json = excluded.message_data_json`
|
|
258757
|
+
);
|
|
258758
|
+
const removeOne = this.db.prepare(
|
|
258759
|
+
"DELETE FROM chat_session_messages WHERE session_id = ? AND message_id = ?"
|
|
258760
|
+
);
|
|
258761
|
+
this.db.transaction(() => {
|
|
258762
|
+
for (const id of toRemove) {
|
|
258763
|
+
removeOne.run(sessionId, id);
|
|
258764
|
+
}
|
|
258765
|
+
for (const m2 of changed) {
|
|
258766
|
+
upsert.run(sessionId, m2.position, m2.id, m2.type, m2.dataJson);
|
|
258767
|
+
}
|
|
258768
|
+
})();
|
|
258769
|
+
}
|
|
258770
|
+
/**
|
|
258771
|
+
* Full positional rewrite. Positions are `PRIMARY KEY (session_id,
|
|
258772
|
+
* position)`, so an in-place UPDATE cannot express a reorder without a
|
|
258773
|
+
* transient collision — the order guard in saveChatSession routes here when
|
|
258774
|
+
* (and only when) the surviving ids changed relative order.
|
|
258775
|
+
*
|
|
258776
|
+
* Therefore this really does DELETE + INSERT rather than upsert. An
|
|
258777
|
+
* `ON CONFLICT(session_id, message_id)` upsert looks like it would work but
|
|
258778
|
+
* does NOT: reassigning `position` collides on the (session_id, position)
|
|
258779
|
+
* PRIMARY KEY against a row that has not moved yet, and that conflict is not
|
|
258780
|
+
* absorbed by an ON CONFLICT clause targeting a different key —
|
|
258781
|
+
* SqliteError: UNIQUE constraint failed:
|
|
258782
|
+
* chat_session_messages.session_id, chat_session_messages.position
|
|
258783
|
+
* Clearing first (inside the same transaction) sidesteps the ordering
|
|
258784
|
+
* problem entirely. This path is rare by design, so the rewrite is cheap
|
|
258785
|
+
* enough, and it matches the old DELETE-ALL/INSERT-ALL semantics exactly.
|
|
258786
|
+
*/
|
|
258787
|
+
replaceAllMessages(sessionId, desired) {
|
|
258788
|
+
const removeAll = this.db.prepare(
|
|
258789
|
+
"DELETE FROM chat_session_messages WHERE session_id = ?"
|
|
258790
|
+
);
|
|
258791
|
+
const insert = this.db.prepare(
|
|
258792
|
+
`INSERT INTO chat_session_messages (
|
|
258793
|
+
session_id, position, message_id, message_type, message_data_json
|
|
258794
|
+
) VALUES (?, ?, ?, ?, ?)`
|
|
258795
|
+
);
|
|
258796
|
+
this.db.transaction(() => {
|
|
258797
|
+
removeAll.run(sessionId);
|
|
258798
|
+
desired.forEach((m2, index2) => {
|
|
258799
|
+
insert.run(sessionId, index2, m2.id, m2.type, m2.dataJson);
|
|
258800
|
+
});
|
|
258801
|
+
})();
|
|
258802
|
+
}
|
|
258659
258803
|
saveChatSession(session) {
|
|
258660
|
-
const existingCreatedAt = this.db.prepare("SELECT created_at FROM chat_sessions WHERE id = ?").get(session.id);
|
|
258661
|
-
const createdAt = existingCreatedAt?.created_at ?? session.createdAt;
|
|
258662
258804
|
const upsertSession = this.db.prepare(
|
|
258663
258805
|
`INSERT INTO chat_sessions (
|
|
258664
258806
|
id, title, last_checkpoint_offset, last_profile_max_tokens, created_at, updated_at
|
|
@@ -258671,34 +258813,45 @@ var init_HistorySqliteStore = __esm({
|
|
|
258671
258813
|
last_profile_max_tokens = excluded.last_profile_max_tokens,
|
|
258672
258814
|
updated_at = excluded.updated_at`
|
|
258673
258815
|
);
|
|
258674
|
-
const deleteMessages = this.db.prepare(
|
|
258675
|
-
"DELETE FROM chat_session_messages WHERE session_id = ?"
|
|
258676
|
-
);
|
|
258677
|
-
const insertMessage = this.db.prepare(
|
|
258678
|
-
`INSERT INTO chat_session_messages (
|
|
258679
|
-
session_id, position, message_id, message_type, message_data_json
|
|
258680
|
-
) VALUES (?, ?, ?, ?, ?)`
|
|
258681
|
-
);
|
|
258682
258816
|
this.db.transaction(() => {
|
|
258683
258817
|
upsertSession.run({
|
|
258684
258818
|
id: session.id,
|
|
258685
258819
|
title: session.title,
|
|
258686
258820
|
lastCheckpointOffset: session.lastCheckpointOffset,
|
|
258687
258821
|
lastProfileMaxTokens: session.lastProfileMaxTokens ?? null,
|
|
258688
|
-
createdAt,
|
|
258822
|
+
createdAt: session.createdAt,
|
|
258689
258823
|
updatedAt: session.updatedAt
|
|
258690
258824
|
});
|
|
258691
|
-
deleteMessages.run(session.id);
|
|
258692
|
-
session.messages.forEach((message, index2) => {
|
|
258693
|
-
insertMessage.run(
|
|
258694
|
-
session.id,
|
|
258695
|
-
index2,
|
|
258696
|
-
message.id,
|
|
258697
|
-
message.type,
|
|
258698
|
-
JSON.stringify(message.data)
|
|
258699
|
-
);
|
|
258700
|
-
});
|
|
258701
258825
|
})();
|
|
258826
|
+
const desired = session.messages.map(
|
|
258827
|
+
(message, index2) => ({
|
|
258828
|
+
id: message.id,
|
|
258829
|
+
type: message.type,
|
|
258830
|
+
position: index2,
|
|
258831
|
+
dataJson: JSON.stringify(message.data)
|
|
258832
|
+
})
|
|
258833
|
+
);
|
|
258834
|
+
const existingState = this.loadSessionMessageState(session.id);
|
|
258835
|
+
const existingDigests = /* @__PURE__ */ new Map();
|
|
258836
|
+
for (const row of existingState) {
|
|
258837
|
+
existingDigests.set(
|
|
258838
|
+
row.id,
|
|
258839
|
+
digestFor({ type: row.type, dataJson: row.jsonText })
|
|
258840
|
+
);
|
|
258841
|
+
}
|
|
258842
|
+
const storedOrder = existingState.map((row) => row.id);
|
|
258843
|
+
const desiredOrderOfStored = [];
|
|
258844
|
+
for (const m2 of desired) {
|
|
258845
|
+
if (existingDigests.has(m2.id)) {
|
|
258846
|
+
desiredOrderOfStored.push(m2.id);
|
|
258847
|
+
}
|
|
258848
|
+
}
|
|
258849
|
+
const reordered = storedOrder.length !== desiredOrderOfStored.length || storedOrder.some((id, i) => id !== desiredOrderOfStored[i]);
|
|
258850
|
+
if (reordered) {
|
|
258851
|
+
this.replaceAllMessages(session.id, desired);
|
|
258852
|
+
return;
|
|
258853
|
+
}
|
|
258854
|
+
this.applyChatSessionDelta(session.id, desired, existingDigests, false);
|
|
258702
258855
|
}
|
|
258703
258856
|
deleteChatSessions(sessionIds) {
|
|
258704
258857
|
const ids = Array.from(
|
|
@@ -262553,7 +262706,8 @@ __export(historySearch_exports, {
|
|
|
262553
262706
|
extractMessageText: () => extractMessageText2,
|
|
262554
262707
|
findMatches: () => findMatches,
|
|
262555
262708
|
normalizeMessages: () => normalizeMessages,
|
|
262556
|
-
searchChatHistory: () => searchChatHistory
|
|
262709
|
+
searchChatHistory: () => searchChatHistory,
|
|
262710
|
+
searchChatHistoryBounded: () => searchChatHistoryBounded
|
|
262557
262711
|
});
|
|
262558
262712
|
function extractMessageText2(data) {
|
|
262559
262713
|
if (!data || typeof data !== "object") return "";
|
|
@@ -262615,6 +262769,39 @@ function normalizeMessages(messages) {
|
|
|
262615
262769
|
if (typeof messages === "object") return Object.values(messages);
|
|
262616
262770
|
return [];
|
|
262617
262771
|
}
|
|
262772
|
+
async function searchChatHistoryBounded(loadSummaries, loadSession, query, options = {}) {
|
|
262773
|
+
const trimmed = (query ?? "").trim();
|
|
262774
|
+
if (!trimmed) {
|
|
262775
|
+
return { query: trimmed, totalSessions: 0, totalMatches: 0, sessions: [], truncated: false };
|
|
262776
|
+
}
|
|
262777
|
+
const summaries = loadSummaries();
|
|
262778
|
+
const results = [];
|
|
262779
|
+
const yieldTick = () => new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
262780
|
+
for (const summary of summaries) {
|
|
262781
|
+
const session = loadSession(summary.id);
|
|
262782
|
+
if (session) {
|
|
262783
|
+
const hit = searchChatHistory([session], trimmed, {
|
|
262784
|
+
...options,
|
|
262785
|
+
// Per-session we want ALL snippets; the cap is applied per session by
|
|
262786
|
+
// searchChatHistory, and truncated is recomputed globally below.
|
|
262787
|
+
sessionLimit: 1
|
|
262788
|
+
});
|
|
262789
|
+
if (hit.sessions.length > 0) results.push(hit.sessions[0]);
|
|
262790
|
+
}
|
|
262791
|
+
await yieldTick();
|
|
262792
|
+
}
|
|
262793
|
+
results.sort((a, b) => b.matchCount - a.matchCount || b.updatedAt - a.updatedAt);
|
|
262794
|
+
const sessionLimit = options.sessionLimit ?? DEFAULT_SESSION_LIMIT;
|
|
262795
|
+
const totalMatches = results.reduce((sum, s) => sum + s.matchCount, 0);
|
|
262796
|
+
const truncated = results.length > sessionLimit;
|
|
262797
|
+
return {
|
|
262798
|
+
query: trimmed,
|
|
262799
|
+
totalSessions: results.length,
|
|
262800
|
+
totalMatches,
|
|
262801
|
+
sessions: results.slice(0, sessionLimit),
|
|
262802
|
+
truncated
|
|
262803
|
+
};
|
|
262804
|
+
}
|
|
262618
262805
|
function searchChatHistory(sessions, query, options = {}) {
|
|
262619
262806
|
const sessionLimit = options.sessionLimit ?? DEFAULT_SESSION_LIMIT;
|
|
262620
262807
|
const snippetLimit = options.snippetLimit ?? DEFAULT_SNIPPET_LIMIT;
|
|
@@ -347336,19 +347523,19 @@ async function runFileMutationTool(args, context2, toolName, unavailableOperatio
|
|
|
347336
347523
|
if (context2.signal?.aborted) throw new Error("AbortError");
|
|
347337
347524
|
const resolved = resolveTerminalForTool(context2, tabIdOrName);
|
|
347338
347525
|
if (!resolved.ok) {
|
|
347339
|
-
const
|
|
347526
|
+
const errorText2 = resolved.message;
|
|
347340
347527
|
sendEvent(sessionId, {
|
|
347341
347528
|
messageId,
|
|
347342
347529
|
type: "tool_call",
|
|
347343
347530
|
toolName,
|
|
347344
347531
|
input: JSON.stringify(args),
|
|
347345
|
-
output:
|
|
347532
|
+
output: errorText2
|
|
347346
347533
|
});
|
|
347347
|
-
return
|
|
347534
|
+
return errorText2;
|
|
347348
347535
|
}
|
|
347349
347536
|
const bestMatch = resolved.terminal;
|
|
347350
347537
|
if (!resolved.snapshot.canUseFilesystem) {
|
|
347351
|
-
const
|
|
347538
|
+
const errorText2 = bestMatch.capabilities?.supportsFilesystem !== true && resolved.snapshot.runtimeState === "ready" ? `Error: Terminal tab "${bestMatch.title || bestMatch.id}" (id=${bestMatch.id}, type=${bestMatch.type}) does not support filesystem operations.` : formatTerminalUnavailableForTool(
|
|
347352
347539
|
resolved.snapshot,
|
|
347353
347540
|
unavailableOperation
|
|
347354
347541
|
);
|
|
@@ -347356,12 +347543,12 @@ async function runFileMutationTool(args, context2, toolName, unavailableOperatio
|
|
|
347356
347543
|
messageId,
|
|
347357
347544
|
type: "file_edit",
|
|
347358
347545
|
toolName,
|
|
347359
|
-
output:
|
|
347546
|
+
output: errorText2,
|
|
347360
347547
|
filePath: filePathInput,
|
|
347361
347548
|
action: "error",
|
|
347362
347549
|
diff: ""
|
|
347363
347550
|
});
|
|
347364
|
-
return
|
|
347551
|
+
return errorText2;
|
|
347365
347552
|
}
|
|
347366
347553
|
let outputText = "";
|
|
347367
347554
|
let diffText2 = "";
|
|
@@ -349537,15 +349724,15 @@ async function readCommandOutput(args, context2) {
|
|
|
349537
349724
|
abortIfNeeded(context2.signal);
|
|
349538
349725
|
const terminalResolution = resolveTerminalForTool(context2, tabIdOrName);
|
|
349539
349726
|
if (!terminalResolution.ok) {
|
|
349540
|
-
const
|
|
349727
|
+
const errorText2 = terminalResolution.message;
|
|
349541
349728
|
sendEvent(sessionId, {
|
|
349542
349729
|
messageId,
|
|
349543
349730
|
type: "tool_call",
|
|
349544
349731
|
toolName: "read_command_output",
|
|
349545
349732
|
input: JSON.stringify(args ?? {}),
|
|
349546
|
-
output:
|
|
349733
|
+
output: errorText2
|
|
349547
349734
|
});
|
|
349548
|
-
return
|
|
349735
|
+
return errorText2;
|
|
349549
349736
|
}
|
|
349550
349737
|
const bestMatch = terminalResolution.terminal;
|
|
349551
349738
|
const task2 = terminalService.getCommandTask(bestMatch.id, history_command_match_id);
|
|
@@ -349555,16 +349742,16 @@ async function readCommandOutput(args, context2) {
|
|
|
349555
349742
|
const started = new Date(t.startTime).toISOString();
|
|
349556
349743
|
return `- id: ${t.id}, status: ${t.status}, command: ${t.command}, started: ${started}`;
|
|
349557
349744
|
}).join("\n") : "(No command history for this terminal)";
|
|
349558
|
-
const
|
|
349745
|
+
const errorText2 = `Error: history_command_match_id "${history_command_match_id}" not found in terminal "${bestMatch.title || bestMatch.id}".
|
|
349559
349746
|
${history2}`;
|
|
349560
349747
|
sendEvent(sessionId, {
|
|
349561
349748
|
messageId,
|
|
349562
349749
|
type: "tool_call",
|
|
349563
349750
|
toolName: "read_command_output",
|
|
349564
349751
|
input: JSON.stringify(args ?? {}),
|
|
349565
|
-
output:
|
|
349752
|
+
output: errorText2
|
|
349566
349753
|
});
|
|
349567
|
-
return
|
|
349754
|
+
return errorText2;
|
|
349568
349755
|
}
|
|
349569
349756
|
const output = task2.output || "";
|
|
349570
349757
|
const isRunning = task2.status === "running";
|
|
@@ -349600,19 +349787,19 @@ async function writeStdin(args, context2) {
|
|
|
349600
349787
|
abortIfNeeded(context2.signal);
|
|
349601
349788
|
const terminalResolution = resolveTerminalForTool(context2, tabIdOrName);
|
|
349602
349789
|
if (!terminalResolution.ok) {
|
|
349603
|
-
const
|
|
349790
|
+
const errorText2 = terminalResolution.message;
|
|
349604
349791
|
sendEvent(sessionId, {
|
|
349605
349792
|
messageId,
|
|
349606
349793
|
type: "tool_call",
|
|
349607
349794
|
toolName: "write_stdin",
|
|
349608
349795
|
input: JSON.stringify(sequence ?? []),
|
|
349609
|
-
output:
|
|
349796
|
+
output: errorText2
|
|
349610
349797
|
});
|
|
349611
|
-
return
|
|
349798
|
+
return errorText2;
|
|
349612
349799
|
}
|
|
349613
349800
|
const bestMatch = terminalResolution.terminal;
|
|
349614
349801
|
if (!terminalResolution.snapshot.canWrite) {
|
|
349615
|
-
const
|
|
349802
|
+
const errorText2 = formatTerminalUnavailableForTool(
|
|
349616
349803
|
terminalResolution.snapshot,
|
|
349617
349804
|
"send input to this terminal"
|
|
349618
349805
|
);
|
|
@@ -349621,9 +349808,9 @@ async function writeStdin(args, context2) {
|
|
|
349621
349808
|
type: "tool_call",
|
|
349622
349809
|
toolName: "write_stdin",
|
|
349623
349810
|
input: JSON.stringify(sequence ?? []),
|
|
349624
|
-
output:
|
|
349811
|
+
output: errorText2
|
|
349625
349812
|
});
|
|
349626
|
-
return
|
|
349813
|
+
return errorText2;
|
|
349627
349814
|
}
|
|
349628
349815
|
const commandText = (sequence ?? []).join("");
|
|
349629
349816
|
const allowed = await checkCommandPolicy(commandText, "write_stdin", context2);
|
|
@@ -365917,6 +366104,9 @@ async function invokeWithRetry(fn, maxRetries = 4, delays = [1e3, 2e3, 4e3, 6e3]
|
|
|
365917
366104
|
if (isAbortError3(error40)) {
|
|
365918
366105
|
throw error40;
|
|
365919
366106
|
}
|
|
366107
|
+
if (!isRetryableError(error40)) {
|
|
366108
|
+
throw error40;
|
|
366109
|
+
}
|
|
365920
366110
|
if (attempt < maxRetries - 1) {
|
|
365921
366111
|
const delay = delays[attempt];
|
|
365922
366112
|
console.warn(`[AgentService] Model invocation failed (Attempt ${attempt + 1}/${maxRetries}). Error: ${error40.message}. Retrying in ${delay}ms...`);
|
|
@@ -365935,8 +366125,80 @@ async function invokeWithRetry(fn, maxRetries = 4, delays = [1e3, 2e3, 4e3, 6e3]
|
|
|
365935
366125
|
}
|
|
365936
366126
|
throw lastError;
|
|
365937
366127
|
}
|
|
366128
|
+
function errorText(error40) {
|
|
366129
|
+
const parts = [];
|
|
366130
|
+
if (typeof error40?.message === "string") parts.push(error40.message);
|
|
366131
|
+
if (typeof error40?.error?.message === "string") parts.push(error40.error.message);
|
|
366132
|
+
if (typeof error40?.code === "string") parts.push(error40.code);
|
|
366133
|
+
if (typeof error40?.status === "number") parts.push(String(error40.status));
|
|
366134
|
+
if (typeof error40?.statusCode === "number") parts.push(String(error40.statusCode));
|
|
366135
|
+
if (typeof error40?.cause?.code === "string") parts.push(error40.cause.code);
|
|
366136
|
+
return parts.join(" ").toLowerCase();
|
|
366137
|
+
}
|
|
365938
366138
|
function isRetryableError(error40) {
|
|
365939
|
-
|
|
366139
|
+
if (isAbortError3(error40)) return false;
|
|
366140
|
+
const text = errorText(error40);
|
|
366141
|
+
if (!text) return false;
|
|
366142
|
+
const finishMatch = text.match(/finish_reason=([^.)]*)/);
|
|
366143
|
+
if (finishMatch) {
|
|
366144
|
+
const reasons = finishMatch[1].split(/[,\s]+/).filter(Boolean);
|
|
366145
|
+
if (reasons.includes("error")) return true;
|
|
366146
|
+
const deterministicReasons = /* @__PURE__ */ new Set(["length", "content_filter"]);
|
|
366147
|
+
if (reasons.length > 0 && reasons.every((r) => deterministicReasons.has(r))) {
|
|
366148
|
+
return false;
|
|
366149
|
+
}
|
|
366150
|
+
}
|
|
366151
|
+
const deterministic = [
|
|
366152
|
+
"empty unusable response",
|
|
366153
|
+
"cannot read properties of undefined",
|
|
366154
|
+
"cannot read properties of null",
|
|
366155
|
+
"is not a function",
|
|
366156
|
+
"bad request",
|
|
366157
|
+
"invalid_request",
|
|
366158
|
+
"validation",
|
|
366159
|
+
"context_length",
|
|
366160
|
+
"context length",
|
|
366161
|
+
"maximum context",
|
|
366162
|
+
"too many tokens",
|
|
366163
|
+
"unsupported",
|
|
366164
|
+
"malformed",
|
|
366165
|
+
"unauthorized",
|
|
366166
|
+
"forbidden",
|
|
366167
|
+
"model_not_found",
|
|
366168
|
+
"insufficient_quota",
|
|
366169
|
+
"invalid api key",
|
|
366170
|
+
"invalid_api_key"
|
|
366171
|
+
];
|
|
366172
|
+
if (deterministic.some((needle) => text.includes(needle))) return false;
|
|
366173
|
+
const transient = [
|
|
366174
|
+
"econnreset",
|
|
366175
|
+
"econnrefused",
|
|
366176
|
+
"etimedout",
|
|
366177
|
+
"esockettimedout",
|
|
366178
|
+
"epipe",
|
|
366179
|
+
"enotfound",
|
|
366180
|
+
"eai_again",
|
|
366181
|
+
"socket hang up",
|
|
366182
|
+
"network",
|
|
366183
|
+
"fetch failed",
|
|
366184
|
+
"connection reset",
|
|
366185
|
+
"connection error",
|
|
366186
|
+
"premature close",
|
|
366187
|
+
"timed out",
|
|
366188
|
+
"timeout",
|
|
366189
|
+
"terminated",
|
|
366190
|
+
"502",
|
|
366191
|
+
"503",
|
|
366192
|
+
"504",
|
|
366193
|
+
"429",
|
|
366194
|
+
"rate limit",
|
|
366195
|
+
"overloaded",
|
|
366196
|
+
"server error",
|
|
366197
|
+
"bad gateway",
|
|
366198
|
+
"service unavailable",
|
|
366199
|
+
"gateway timeout"
|
|
366200
|
+
];
|
|
366201
|
+
return transient.some((needle) => text.includes(needle));
|
|
365940
366202
|
}
|
|
365941
366203
|
function extractErrorDetails(error40) {
|
|
365942
366204
|
let details = "";
|
|
@@ -366141,6 +366403,28 @@ function getStreamedResponseModelName(response, rawChunks) {
|
|
|
366141
366403
|
(candidate) => typeof candidate === "string" && candidate.trim().length > 0
|
|
366142
366404
|
);
|
|
366143
366405
|
}
|
|
366406
|
+
var KNOWN_FINISH_REASONS = [
|
|
366407
|
+
"stop",
|
|
366408
|
+
"length",
|
|
366409
|
+
"tool_calls",
|
|
366410
|
+
"content_filter",
|
|
366411
|
+
"function_call",
|
|
366412
|
+
"error"
|
|
366413
|
+
];
|
|
366414
|
+
function normalizeFinishReason(reason) {
|
|
366415
|
+
if (typeof reason !== "string") return "";
|
|
366416
|
+
const trimmed = reason.trim();
|
|
366417
|
+
if (!trimmed) return "";
|
|
366418
|
+
const lowered = trimmed.toLowerCase();
|
|
366419
|
+
for (const known of KNOWN_FINISH_REASONS) {
|
|
366420
|
+
for (let repeats = 5; repeats >= 2; repeats--) {
|
|
366421
|
+
if (lowered === known.repeat(repeats)) {
|
|
366422
|
+
return known;
|
|
366423
|
+
}
|
|
366424
|
+
}
|
|
366425
|
+
}
|
|
366426
|
+
return trimmed;
|
|
366427
|
+
}
|
|
366144
366428
|
function isEmptyMalformedToolCallFinish(response, rawChunks) {
|
|
366145
366429
|
if (!hasToolCallFinishReason(response, rawChunks)) return false;
|
|
366146
366430
|
if (hasAnyToolCallPayload(response, rawChunks)) return false;
|
|
@@ -366198,7 +366482,7 @@ function extractRawResponseMetadata(rawChunk) {
|
|
|
366198
366482
|
}
|
|
366199
366483
|
function getRawFinishReason(rawChunk) {
|
|
366200
366484
|
const choices = Array.isArray(rawChunk?.choices) ? rawChunk.choices : [];
|
|
366201
|
-
return choices.map((choice) => choice?.finish_reason ?? choice?.finishReason).find((reason) => hasNonEmptyString(reason));
|
|
366485
|
+
return choices.map((choice) => normalizeFinishReason(choice?.finish_reason ?? choice?.finishReason)).find((reason) => hasNonEmptyString(reason));
|
|
366202
366486
|
}
|
|
366203
366487
|
function getFinishReasons(response, rawChunks) {
|
|
366204
366488
|
const candidates = [
|
|
@@ -366212,7 +366496,7 @@ function getFinishReasons(response, rawChunks) {
|
|
|
366212
366496
|
) : []
|
|
366213
366497
|
)
|
|
366214
366498
|
];
|
|
366215
|
-
return candidates.filter(hasNonEmptyString);
|
|
366499
|
+
return candidates.map((c) => normalizeFinishReason(c)).filter(hasNonEmptyString);
|
|
366216
366500
|
}
|
|
366217
366501
|
function getMessageType(message) {
|
|
366218
366502
|
const type2 = typeof message?._getType === "function" ? message._getType() : message?.type;
|
|
@@ -371635,9 +371919,11 @@ ${reminder}`;
|
|
|
371635
371919
|
messages = [...messages, this.lastAbortedMessage];
|
|
371636
371920
|
this.lastAbortedMessage = null;
|
|
371637
371921
|
}
|
|
371638
|
-
const
|
|
371922
|
+
const sessionMeta = this.chatHistoryService.getSessionMeta(sessionId);
|
|
371923
|
+
const session = {
|
|
371639
371924
|
id: sessionId,
|
|
371640
|
-
title: "New Session",
|
|
371925
|
+
title: sessionMeta?.title || "New Session",
|
|
371926
|
+
// Placeholder only — replaced by updateSessionFromMessages() below.
|
|
371641
371927
|
messages: /* @__PURE__ */ new Map(),
|
|
371642
371928
|
lastCheckpointOffset: 0,
|
|
371643
371929
|
lastProfileMaxTokens: this.getEffectiveMaxTokensForSession(sessionId)
|
|
@@ -373138,7 +373424,7 @@ var ChatHistoryService = class {
|
|
|
373138
373424
|
this.store = options?.store || new HistorySqliteStore();
|
|
373139
373425
|
}
|
|
373140
373426
|
saveSession(session) {
|
|
373141
|
-
const
|
|
373427
|
+
const createdAt = this.store.getChatSessionCreatedAt(session.id);
|
|
373142
373428
|
const now = Date.now();
|
|
373143
373429
|
this.store.saveChatSession({
|
|
373144
373430
|
id: session.id,
|
|
@@ -373150,10 +373436,18 @@ var ChatHistoryService = class {
|
|
|
373150
373436
|
})),
|
|
373151
373437
|
lastCheckpointOffset: session.lastCheckpointOffset,
|
|
373152
373438
|
lastProfileMaxTokens: session.lastProfileMaxTokens,
|
|
373153
|
-
createdAt:
|
|
373439
|
+
createdAt: createdAt || now,
|
|
373154
373440
|
updatedAt: now
|
|
373155
373441
|
});
|
|
373156
373442
|
}
|
|
373443
|
+
/**
|
|
373444
|
+
* Session row only (title/createdAt) — never parses stored messages.
|
|
373445
|
+
* Used on the checkpoint save path, which previously paid a full session
|
|
373446
|
+
* parse just to preserve the title.
|
|
373447
|
+
*/
|
|
373448
|
+
getSessionMeta(sessionId) {
|
|
373449
|
+
return this.store.getChatSessionMeta(sessionId);
|
|
373450
|
+
}
|
|
373157
373451
|
loadSession(sessionId) {
|
|
373158
373452
|
const storedSession = this.store.loadChatSession(sessionId);
|
|
373159
373453
|
if (!storedSession) {
|
|
@@ -374788,20 +375082,24 @@ var WebSocketGatewayAdapter = class {
|
|
|
374788
375082
|
}
|
|
374789
375083
|
case "history:search": {
|
|
374790
375084
|
const bridge = this.options.historyBridge;
|
|
374791
|
-
if (!bridge?.getAllSessions) {
|
|
375085
|
+
if (!bridge?.searchBounded && !bridge?.getAllSessions) {
|
|
374792
375086
|
throw new WebSocketRpcError(
|
|
374793
375087
|
"METHOD_NOT_FOUND",
|
|
374794
375088
|
"history:search is not available on this gateway (no history bridge)."
|
|
374795
375089
|
);
|
|
374796
375090
|
}
|
|
374797
|
-
const { searchChatHistory: searchChatHistory2 } = await Promise.resolve().then(() => (init_historySearch(), historySearch_exports));
|
|
374798
|
-
const sessions = await bridge.getAllSessions();
|
|
374799
375091
|
const query = this.readStringParam(params, "query");
|
|
374800
375092
|
const wholeWord = params?.wholeWord === true;
|
|
374801
375093
|
const includeTitles = params?.includeTitles !== false;
|
|
374802
375094
|
const sessionLimit = typeof params?.sessionLimit === "number" ? params.sessionLimit : void 0;
|
|
374803
375095
|
const snippetLimit = typeof params?.snippetLimit === "number" ? params.snippetLimit : void 0;
|
|
374804
|
-
|
|
375096
|
+
const options = { wholeWord, includeTitles, sessionLimit, snippetLimit };
|
|
375097
|
+
if (bridge.searchBounded) {
|
|
375098
|
+
return await bridge.searchBounded(query, options);
|
|
375099
|
+
}
|
|
375100
|
+
const { searchChatHistory: searchChatHistory2 } = await Promise.resolve().then(() => (init_historySearch(), historySearch_exports));
|
|
375101
|
+
const sessions = await bridge.getAllSessions();
|
|
375102
|
+
return searchChatHistory2(sessions, query, options);
|
|
374805
375103
|
}
|
|
374806
375104
|
case "settings:listBackups": {
|
|
374807
375105
|
const bridge = this.options.settingsBridge;
|
|
@@ -386881,6 +387179,7 @@ var HistoryStorageMigration = class {
|
|
|
386881
387179
|
|
|
386882
387180
|
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
386883
387181
|
init_HistorySqliteStore();
|
|
387182
|
+
init_historySearch();
|
|
386884
387183
|
|
|
386885
387184
|
// ../../packages/backend/src/services/AgentSettingProfileService.ts
|
|
386886
387185
|
var AgentSettingProfileService = class {
|
|
@@ -397894,7 +398193,18 @@ async function startGyBackend() {
|
|
|
397894
398193
|
);
|
|
397895
398194
|
}
|
|
397896
398195
|
return agentService.getAllChatHistory() ?? [];
|
|
397897
|
-
}
|
|
398196
|
+
},
|
|
398197
|
+
// v3.8.4 FREEZE FIX: the gateway's history:search called
|
|
398198
|
+
// `getAllSessions()`, which JSON.parses EVERY message of EVERY
|
|
398199
|
+
// session synchronously — seconds of blocked event loop on a large
|
|
398200
|
+
// store, freezing the UI. This searches one session at a time and
|
|
398201
|
+
// yields between them, so nothing blocks.
|
|
398202
|
+
searchBounded: async (query, options) => searchChatHistoryBounded(
|
|
398203
|
+
() => historyStore.listChatSessionSummaries(),
|
|
398204
|
+
(id) => historyStore.loadChatSession(id),
|
|
398205
|
+
query,
|
|
398206
|
+
options ?? {}
|
|
398207
|
+
)
|
|
397898
398208
|
},
|
|
397899
398209
|
commandPolicyBridge: {
|
|
397900
398210
|
getLists: async () => {
|