neuralos 3.8.8 → 3.8.9

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 +108 -3
  2. package/package.json +1 -1
package/bin/gybackend.cjs CHANGED
@@ -346328,6 +346328,7 @@ var AgentRunLedger = class {
346328
346328
  var CHECKPOINT_STRING_CAP = 8 * 1024;
346329
346329
  var CHECKPOINT_BYTES_CAP = 16 * 1024 * 1024;
346330
346330
  var CHECKPOINT_MAX_MESSAGES = 40;
346331
+ var CHECKPOINT_RETENTION = 8;
346331
346332
  function truncateString(input, max = CHECKPOINT_STRING_CAP) {
346332
346333
  if (input.length <= max) return input;
346333
346334
  const omitted = input.length - max;
@@ -346395,8 +346396,18 @@ function isAllocationError(error40) {
346395
346396
  var SafeMemorySaver = class extends MemorySaver {
346396
346397
  async put(config2, checkpoint, metadata) {
346397
346398
  const pruned = pruneCheckpointForSaver(checkpoint);
346399
+ const threadId = config2?.configurable?.thread_id;
346400
+ const ns = config2?.configurable?.checkpoint_ns ?? "";
346401
+ const checkpointId = checkpoint?.id;
346402
+ if (threadId && checkpointId) {
346403
+ const order = this.putOrderFor(threadId, ns);
346404
+ order.push(checkpointId);
346405
+ this.setPutOrder(threadId, ns, order.slice(-CHECKPOINT_RETENTION * 4));
346406
+ }
346398
346407
  try {
346399
- return await super.put(config2, pruned, pruneValue(metadata));
346408
+ const result = await super.put(config2, pruned, pruneValue(metadata));
346409
+ this.evictOldCheckpoints(config2);
346410
+ return result;
346400
346411
  } catch (error40) {
346401
346412
  if (!isAllocationError(error40)) throw error40;
346402
346413
  console.warn(
@@ -346409,12 +346420,15 @@ var SafeMemorySaver = class extends MemorySaver {
346409
346420
  channels.messages = channels.messages.slice(-8);
346410
346421
  }
346411
346422
  tighter.channel_values = channels;
346412
- return await super.put(config2, tighter, { source: "oom-pruned" });
346423
+ const result = await super.put(config2, tighter, { source: "oom-pruned" });
346424
+ this.evictOldCheckpoints(config2);
346425
+ return result;
346413
346426
  } catch (retryError) {
346414
346427
  if (!isAllocationError(retryError)) throw retryError;
346415
346428
  console.warn(
346416
346429
  "[SafeMemorySaver] checkpoint still too large \u2014 skipping persist so the run can finish"
346417
346430
  );
346431
+ this.evictOldCheckpoints(config2);
346418
346432
  return {
346419
346433
  configurable: {
346420
346434
  thread_id: config2?.configurable?.thread_id,
@@ -346425,6 +346439,46 @@ var SafeMemorySaver = class extends MemorySaver {
346425
346439
  }
346426
346440
  }
346427
346441
  }
346442
+ /**
346443
+ * Keep only the newest CHECKPOINT_RETENTION checkpoints for the thread/ns
346444
+ * in this config. MemorySaver stores them as
346445
+ * storage[threadId][ns][checkpointId] = [serializedCheckpoint, metadata,
346446
+ * parentId]; there is no timestamp, but checkpoint ids from langgraph are
346447
+ * UUIDv4-orderable-ish in practice and — more reliably — the number of
346448
+ * entries is what matters for memory. We cannot know insertion order from
346449
+ * the object shape alone, so we track it ourselves: the ids we just put
346450
+ * are recorded, and anything beyond the newest window is dropped.
346451
+ */
346452
+ evictOldCheckpoints(config2) {
346453
+ try {
346454
+ const threadId = config2?.configurable?.thread_id;
346455
+ const ns = config2?.configurable?.checkpoint_ns ?? "";
346456
+ if (!threadId) return;
346457
+ const storage = this.storage;
346458
+ const nsMap = storage?.[threadId]?.[ns];
346459
+ if (!nsMap) return;
346460
+ const ids = Object.keys(nsMap);
346461
+ if (ids.length <= CHECKPOINT_RETENTION) return;
346462
+ const order = this.putOrderFor(threadId, ns);
346463
+ const keep = new Set(order.slice(-CHECKPOINT_RETENTION));
346464
+ for (const id of ids) {
346465
+ if (!keep.has(id) && !order.includes(id)) keep.add(id);
346466
+ }
346467
+ for (const id of ids) {
346468
+ if (!keep.has(id)) delete nsMap[id];
346469
+ }
346470
+ const remaining = order.filter((id) => id in nsMap);
346471
+ this.setPutOrder(threadId, ns, remaining.slice(-CHECKPOINT_RETENTION * 2));
346472
+ } catch {
346473
+ }
346474
+ }
346475
+ putOrders = /* @__PURE__ */ new Map();
346476
+ putOrderFor(threadId, ns) {
346477
+ return this.putOrders.get(`${threadId}\0${ns}`) ?? [];
346478
+ }
346479
+ setPutOrder(threadId, ns, ids) {
346480
+ this.putOrders.set(`${threadId}\0${ns}`, ids);
346481
+ }
346428
346482
  async putWrites(config2, writes, taskId) {
346429
346483
  const prunedWrites = Array.isArray(writes) ? writes.map((w) => {
346430
346484
  if (Array.isArray(w) && w.length >= 2) {
@@ -371792,6 +371846,23 @@ ${reminder}`;
371792
371846
  baseMessages = mapStoredMessagesToChatMessages(
371793
371847
  sanitizedStoredMessages.messages
371794
371848
  );
371849
+ const interruptedRun = this.chatHistoryService.getRunMarker(sessionId);
371850
+ if (interruptedRun) {
371851
+ const minutesAgo = Math.max(
371852
+ 0,
371853
+ Math.round((Date.now() - (interruptedRun.startedAt || 0)) / 6e4)
371854
+ );
371855
+ baseMessages.push(
371856
+ new SystemMessage(
371857
+ `[INTERRUPTION NOTICE] The previous task on this session was interrupted ${minutesAgo > 0 ? `${minutesAgo} minutes ` : ""}ago (the application stopped before the response completed \u2014 likely a force quit or crash). The last assistant response above may be PARTIAL: it can end mid-sentence, mid-tool-call, or before the task was actually finished. ` + (interruptedRun.inputPreview ? `The interrupted task began with: "${interruptedRun.inputPreview}"
371858
+ ` : "") + `Do not assume the prior task completed. If the user's next message relates to it, offer to continue or finish the interrupted work rather than starting from scratch.`
371859
+ )
371860
+ );
371861
+ this.chatHistoryService.clearRunMarker(sessionId);
371862
+ console.warn(
371863
+ `[AgentService_v2] Restored session ${sessionId} after an interrupted run (${interruptedRun.runId}); injected interruption notice into context.`
371864
+ );
371865
+ }
371795
371866
  }
371796
371867
  const runExperimentalFlags = resolveRunExperimentalFlags(
371797
371868
  context2,
@@ -371811,6 +371882,11 @@ ${reminder}`;
371811
371882
  execCommandActionModelEnabled: runExperimentalFlags.execCommandActionModelEnabled,
371812
371883
  writeStdinActionModelEnabled: runExperimentalFlags.writeStdinActionModelEnabled
371813
371884
  };
371885
+ this.chatHistoryService.setRunMarker(sessionId, {
371886
+ runId: ledgerRunId,
371887
+ startedAt: Date.now(),
371888
+ inputPreview: ledgerInputPreview.slice(0, 500)
371889
+ });
371814
371890
  try {
371815
371891
  const result = await this.graph.invoke(initialState, {
371816
371892
  recursionLimit,
@@ -371869,6 +371945,7 @@ ${reminder}`;
371869
371945
  this.currentRunIdBySession.delete(sessionId);
371870
371946
  this.agentRunLedger?.finishRun(ledgerRunId, ledgerExitStatus, ledgerExitError);
371871
371947
  void this.recordCompoundingLessons(sessionId, ledgerRunId, ledgerExitStatus, ledgerExitError);
371948
+ this.chatHistoryService.clearRunMarker(sessionId);
371872
371949
  await this.clearCheckpoint(sessionId);
371873
371950
  }
371874
371951
  }
@@ -373440,9 +373517,11 @@ var ChatHistoryService = class {
373440
373517
  saveSession(session) {
373441
373518
  const createdAt = this.store.getChatSessionCreatedAt(session.id);
373442
373519
  const now = Date.now();
373520
+ const storedMeta = this.store.getChatSessionMeta(session.id);
373521
+ const title = storedMeta?.title ?? session.title;
373443
373522
  this.store.saveChatSession({
373444
373523
  id: session.id,
373445
- title: session.title,
373524
+ title,
373446
373525
  messages: Array.from(session.messages.entries()).map(([id, message]) => ({
373447
373526
  id,
373448
373527
  type: message._getType ? message._getType() : "unknown",
@@ -373462,6 +373541,32 @@ var ChatHistoryService = class {
373462
373541
  getSessionMeta(sessionId) {
373463
373542
  return this.store.getChatSessionMeta(sessionId);
373464
373543
  }
373544
+ // ─── v3.8.9: run interruption markers ─────────────────────────────────────
373545
+ // A marker is SET at run start and CLEARED in the run's finally block.
373546
+ // Only a hard kill (force quit, crash, power loss) skips the finally —
373547
+ // so a leftover marker is a durable, restart-surviving record that the
373548
+ // session's previous run was interrupted. Stored in the history_meta
373549
+ // table (plain SQLite, survives anything short of disk loss).
373550
+ runMarkerKey(sessionId) {
373551
+ return `run-marker:${sessionId}`;
373552
+ }
373553
+ setRunMarker(sessionId, marker) {
373554
+ this.store.setMeta(this.runMarkerKey(sessionId), JSON.stringify(marker));
373555
+ }
373556
+ getRunMarker(sessionId) {
373557
+ const raw = this.store.getMeta(this.runMarkerKey(sessionId));
373558
+ if (!raw) return null;
373559
+ try {
373560
+ const parsed = JSON.parse(raw);
373561
+ if (typeof parsed?.runId === "string") return parsed;
373562
+ return null;
373563
+ } catch {
373564
+ return null;
373565
+ }
373566
+ }
373567
+ clearRunMarker(sessionId) {
373568
+ this.store.setMeta(this.runMarkerKey(sessionId), "");
373569
+ }
373465
373570
  loadSession(sessionId) {
373466
373571
  const storedSession = this.store.loadChatSession(sessionId);
373467
373572
  if (!storedSession) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuralos",
3
- "version": "3.8.8",
3
+ "version": "3.8.9",
4
4
  "description": "Standalone neuralOS backend (rterm-backend): AI-native terminal & agentic-AI operations platform.",
5
5
  "keywords": [
6
6
  "forward-deployed-engineer",