rterm-backend 3.8.3 → 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.
Files changed (2) hide show
  1. package/bin/gybackend.cjs +187 -27
  2. package/package.json +1 -1
package/bin/gybackend.cjs CHANGED
@@ -258654,6 +258654,19 @@ var init_HistorySqliteStore = __esm({
258654
258654
  lastProfileMaxTokens: row.last_profile_max_tokens ?? void 0
258655
258655
  }));
258656
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
+ */
258657
258670
  listChatSessions() {
258658
258671
  return this.listChatSessionSummaries().map((summary) => this.loadChatSession(summary.id)).filter(
258659
258672
  (session) => session !== null
@@ -262693,7 +262706,8 @@ __export(historySearch_exports, {
262693
262706
  extractMessageText: () => extractMessageText2,
262694
262707
  findMatches: () => findMatches,
262695
262708
  normalizeMessages: () => normalizeMessages,
262696
- searchChatHistory: () => searchChatHistory
262709
+ searchChatHistory: () => searchChatHistory,
262710
+ searchChatHistoryBounded: () => searchChatHistoryBounded
262697
262711
  });
262698
262712
  function extractMessageText2(data) {
262699
262713
  if (!data || typeof data !== "object") return "";
@@ -262755,6 +262769,39 @@ function normalizeMessages(messages) {
262755
262769
  if (typeof messages === "object") return Object.values(messages);
262756
262770
  return [];
262757
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
+ }
262758
262805
  function searchChatHistory(sessions, query, options = {}) {
262759
262806
  const sessionLimit = options.sessionLimit ?? DEFAULT_SESSION_LIMIT;
262760
262807
  const snippetLimit = options.snippetLimit ?? DEFAULT_SNIPPET_LIMIT;
@@ -347476,19 +347523,19 @@ async function runFileMutationTool(args, context2, toolName, unavailableOperatio
347476
347523
  if (context2.signal?.aborted) throw new Error("AbortError");
347477
347524
  const resolved = resolveTerminalForTool(context2, tabIdOrName);
347478
347525
  if (!resolved.ok) {
347479
- const errorText = resolved.message;
347526
+ const errorText2 = resolved.message;
347480
347527
  sendEvent(sessionId, {
347481
347528
  messageId,
347482
347529
  type: "tool_call",
347483
347530
  toolName,
347484
347531
  input: JSON.stringify(args),
347485
- output: errorText
347532
+ output: errorText2
347486
347533
  });
347487
- return errorText;
347534
+ return errorText2;
347488
347535
  }
347489
347536
  const bestMatch = resolved.terminal;
347490
347537
  if (!resolved.snapshot.canUseFilesystem) {
347491
- const errorText = 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(
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(
347492
347539
  resolved.snapshot,
347493
347540
  unavailableOperation
347494
347541
  );
@@ -347496,12 +347543,12 @@ async function runFileMutationTool(args, context2, toolName, unavailableOperatio
347496
347543
  messageId,
347497
347544
  type: "file_edit",
347498
347545
  toolName,
347499
- output: errorText,
347546
+ output: errorText2,
347500
347547
  filePath: filePathInput,
347501
347548
  action: "error",
347502
347549
  diff: ""
347503
347550
  });
347504
- return errorText;
347551
+ return errorText2;
347505
347552
  }
347506
347553
  let outputText = "";
347507
347554
  let diffText2 = "";
@@ -349677,15 +349724,15 @@ async function readCommandOutput(args, context2) {
349677
349724
  abortIfNeeded(context2.signal);
349678
349725
  const terminalResolution = resolveTerminalForTool(context2, tabIdOrName);
349679
349726
  if (!terminalResolution.ok) {
349680
- const errorText = terminalResolution.message;
349727
+ const errorText2 = terminalResolution.message;
349681
349728
  sendEvent(sessionId, {
349682
349729
  messageId,
349683
349730
  type: "tool_call",
349684
349731
  toolName: "read_command_output",
349685
349732
  input: JSON.stringify(args ?? {}),
349686
- output: errorText
349733
+ output: errorText2
349687
349734
  });
349688
- return errorText;
349735
+ return errorText2;
349689
349736
  }
349690
349737
  const bestMatch = terminalResolution.terminal;
349691
349738
  const task2 = terminalService.getCommandTask(bestMatch.id, history_command_match_id);
@@ -349695,16 +349742,16 @@ async function readCommandOutput(args, context2) {
349695
349742
  const started = new Date(t.startTime).toISOString();
349696
349743
  return `- id: ${t.id}, status: ${t.status}, command: ${t.command}, started: ${started}`;
349697
349744
  }).join("\n") : "(No command history for this terminal)";
349698
- const errorText = `Error: history_command_match_id "${history_command_match_id}" not found in terminal "${bestMatch.title || bestMatch.id}".
349745
+ const errorText2 = `Error: history_command_match_id "${history_command_match_id}" not found in terminal "${bestMatch.title || bestMatch.id}".
349699
349746
  ${history2}`;
349700
349747
  sendEvent(sessionId, {
349701
349748
  messageId,
349702
349749
  type: "tool_call",
349703
349750
  toolName: "read_command_output",
349704
349751
  input: JSON.stringify(args ?? {}),
349705
- output: errorText
349752
+ output: errorText2
349706
349753
  });
349707
- return errorText;
349754
+ return errorText2;
349708
349755
  }
349709
349756
  const output = task2.output || "";
349710
349757
  const isRunning = task2.status === "running";
@@ -349740,19 +349787,19 @@ async function writeStdin(args, context2) {
349740
349787
  abortIfNeeded(context2.signal);
349741
349788
  const terminalResolution = resolveTerminalForTool(context2, tabIdOrName);
349742
349789
  if (!terminalResolution.ok) {
349743
- const errorText = terminalResolution.message;
349790
+ const errorText2 = terminalResolution.message;
349744
349791
  sendEvent(sessionId, {
349745
349792
  messageId,
349746
349793
  type: "tool_call",
349747
349794
  toolName: "write_stdin",
349748
349795
  input: JSON.stringify(sequence ?? []),
349749
- output: errorText
349796
+ output: errorText2
349750
349797
  });
349751
- return errorText;
349798
+ return errorText2;
349752
349799
  }
349753
349800
  const bestMatch = terminalResolution.terminal;
349754
349801
  if (!terminalResolution.snapshot.canWrite) {
349755
- const errorText = formatTerminalUnavailableForTool(
349802
+ const errorText2 = formatTerminalUnavailableForTool(
349756
349803
  terminalResolution.snapshot,
349757
349804
  "send input to this terminal"
349758
349805
  );
@@ -349761,9 +349808,9 @@ async function writeStdin(args, context2) {
349761
349808
  type: "tool_call",
349762
349809
  toolName: "write_stdin",
349763
349810
  input: JSON.stringify(sequence ?? []),
349764
- output: errorText
349811
+ output: errorText2
349765
349812
  });
349766
- return errorText;
349813
+ return errorText2;
349767
349814
  }
349768
349815
  const commandText = (sequence ?? []).join("");
349769
349816
  const allowed = await checkCommandPolicy(commandText, "write_stdin", context2);
@@ -366057,6 +366104,9 @@ async function invokeWithRetry(fn, maxRetries = 4, delays = [1e3, 2e3, 4e3, 6e3]
366057
366104
  if (isAbortError3(error40)) {
366058
366105
  throw error40;
366059
366106
  }
366107
+ if (!isRetryableError(error40)) {
366108
+ throw error40;
366109
+ }
366060
366110
  if (attempt < maxRetries - 1) {
366061
366111
  const delay = delays[attempt];
366062
366112
  console.warn(`[AgentService] Model invocation failed (Attempt ${attempt + 1}/${maxRetries}). Error: ${error40.message}. Retrying in ${delay}ms...`);
@@ -366075,8 +366125,80 @@ async function invokeWithRetry(fn, maxRetries = 4, delays = [1e3, 2e3, 4e3, 6e3]
366075
366125
  }
366076
366126
  throw lastError;
366077
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
+ }
366078
366138
  function isRetryableError(error40) {
366079
- return !isAbortError3(error40);
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));
366080
366202
  }
366081
366203
  function extractErrorDetails(error40) {
366082
366204
  let details = "";
@@ -366281,6 +366403,28 @@ function getStreamedResponseModelName(response, rawChunks) {
366281
366403
  (candidate) => typeof candidate === "string" && candidate.trim().length > 0
366282
366404
  );
366283
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
+ }
366284
366428
  function isEmptyMalformedToolCallFinish(response, rawChunks) {
366285
366429
  if (!hasToolCallFinishReason(response, rawChunks)) return false;
366286
366430
  if (hasAnyToolCallPayload(response, rawChunks)) return false;
@@ -366338,7 +366482,7 @@ function extractRawResponseMetadata(rawChunk) {
366338
366482
  }
366339
366483
  function getRawFinishReason(rawChunk) {
366340
366484
  const choices = Array.isArray(rawChunk?.choices) ? rawChunk.choices : [];
366341
- 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));
366342
366486
  }
366343
366487
  function getFinishReasons(response, rawChunks) {
366344
366488
  const candidates = [
@@ -366352,7 +366496,7 @@ function getFinishReasons(response, rawChunks) {
366352
366496
  ) : []
366353
366497
  )
366354
366498
  ];
366355
- return candidates.filter(hasNonEmptyString);
366499
+ return candidates.map((c) => normalizeFinishReason(c)).filter(hasNonEmptyString);
366356
366500
  }
366357
366501
  function getMessageType(message) {
366358
366502
  const type2 = typeof message?._getType === "function" ? message._getType() : message?.type;
@@ -374938,20 +375082,24 @@ var WebSocketGatewayAdapter = class {
374938
375082
  }
374939
375083
  case "history:search": {
374940
375084
  const bridge = this.options.historyBridge;
374941
- if (!bridge?.getAllSessions) {
375085
+ if (!bridge?.searchBounded && !bridge?.getAllSessions) {
374942
375086
  throw new WebSocketRpcError(
374943
375087
  "METHOD_NOT_FOUND",
374944
375088
  "history:search is not available on this gateway (no history bridge)."
374945
375089
  );
374946
375090
  }
374947
- const { searchChatHistory: searchChatHistory2 } = await Promise.resolve().then(() => (init_historySearch(), historySearch_exports));
374948
- const sessions = await bridge.getAllSessions();
374949
375091
  const query = this.readStringParam(params, "query");
374950
375092
  const wholeWord = params?.wholeWord === true;
374951
375093
  const includeTitles = params?.includeTitles !== false;
374952
375094
  const sessionLimit = typeof params?.sessionLimit === "number" ? params.sessionLimit : void 0;
374953
375095
  const snippetLimit = typeof params?.snippetLimit === "number" ? params.snippetLimit : void 0;
374954
- return searchChatHistory2(sessions, query, { wholeWord, includeTitles, sessionLimit, snippetLimit });
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);
374955
375103
  }
374956
375104
  case "settings:listBackups": {
374957
375105
  const bridge = this.options.settingsBridge;
@@ -387031,6 +387179,7 @@ var HistoryStorageMigration = class {
387031
387179
 
387032
387180
  // ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
387033
387181
  init_HistorySqliteStore();
387182
+ init_historySearch();
387034
387183
 
387035
387184
  // ../../packages/backend/src/services/AgentSettingProfileService.ts
387036
387185
  var AgentSettingProfileService = class {
@@ -398044,7 +398193,18 @@ async function startGyBackend() {
398044
398193
  );
398045
398194
  }
398046
398195
  return agentService.getAllChatHistory() ?? [];
398047
- }
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
+ )
398048
398208
  },
398049
398209
  commandPolicyBridge: {
398050
398210
  getLists: async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rterm-backend",
3
- "version": "3.8.3",
3
+ "version": "3.8.4",
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",