memory-eternal 0.8.0 → 0.9.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/lib/capture.js CHANGED
@@ -230,12 +230,80 @@ export function extractLastTurn(events, _turn) {
230
230
  return lines.join('\n').trim()
231
231
  }
232
232
 
233
+ /**
234
+ * 读会话事件快照。
235
+ *
236
+ * 优先 `ownEvents()`(DSH 公开 API):只取本会话自身事件,恢复/分叉会话不会把
237
+ * 继承来的历史前缀重复沉淀;`snapshotEvents()` 次之,老结构里的 `events` 兜底。
238
+ * 三条路都取不到时返回空数组,而不是让监听器抛异常(抛异常 = 管线静默死亡,
239
+ * 页面只表现为「一直不写卡」)。
240
+ */
241
+ export function sessionEvents(session) {
242
+ if (!session) return []
243
+ // 逐个试:某个取法报了错就换下一个,不要让监听器跟着挂掉。
244
+ for (const pick of ['ownEvents', 'snapshotEvents']) {
245
+ if (typeof session[pick] === 'function') {
246
+ try {
247
+ const out = session[pick]()
248
+ if (Array.isArray(out)) return out
249
+ } catch { /* 换下一个取法 */ }
250
+ }
251
+ }
252
+ return Array.isArray(session.events) ? session.events : []
253
+ }
254
+
255
+ /**
256
+ * 自动沉淀健康状态(进程内)。
257
+ *
258
+ * 目的:任何一条失败路径都必须留下「坏消息」,供 systemPrompt 段和 UI 横幅提示用户——
259
+ * 沉淀是后台静默管线,坏了不能只写日志等人来看。`fail()` 记录当前故障与起始时间,
260
+ * `succeed()` 表示一次真实写卡成功(故障自动清除)。
261
+ */
262
+ export function createCaptureHealth() {
263
+ let ok = true
264
+ let reason = ''
265
+ let since = 0
266
+ let lastOkAt = 0
267
+ let lastFailAt = 0
268
+ return {
269
+ fail(why) {
270
+ ok = false
271
+ reason = String(why || '未知原因').replace(/\s+/g, ' ').slice(0, 160)
272
+ since = Date.now()
273
+ lastFailAt = since
274
+ },
275
+ succeed() {
276
+ ok = true
277
+ reason = ''
278
+ since = 0
279
+ lastOkAt = Date.now()
280
+ },
281
+ snapshot() { return { ok, reason, since, lastOkAt, lastFailAt } },
282
+ }
283
+ }
284
+
285
+ /**
286
+ * 探测会话对象认识哪种「取事件」方式,返回用到的成员名('' = 一个都不认识)。
287
+ *
288
+ * 这个返回值会写进自动沉淀日志:一旦 DSH 再改接口,日志第一行就会显示
289
+ * 「没有事件接口」,而不是继续静默无输出。
290
+ */
291
+ export function sessionEventApi(session) {
292
+ if (!session) return ''
293
+ // 顺序即优先级:ownEvents() 只含本会话自身事件(恢复/分叉不会重复沉淀历史)。
294
+ for (const pick of ['ownEvents', 'snapshotEvents']) {
295
+ if (typeof session[pick] === 'function') return pick
296
+ }
297
+ return Array.isArray(session.events) ? 'events' : ''
298
+ }
299
+
233
300
  /** 取增量事件:seq 大于 lastSeq 的 user/assistant 消息事件。 */
234
301
  export function sliceNewEvents(events, lastSeq) {
235
302
  const out = []
236
303
  for (const event of events) {
237
304
  const seq = event.seq
238
- if (seq !== undefined && lastSeq !== undefined && seq <= lastSeq) continue
305
+ // 无 seq 的事件(非常规日志)也收,避免漏掉消息;有 seq 则按水位过滤。
306
+ if (typeof seq === 'number' && typeof lastSeq === 'number' && seq <= lastSeq) continue
239
307
  const type = event.type
240
308
  if (type === 'user/message' || type === 'assistant/message') out.push(event)
241
309
  }