@a9i5k4/dsh-auto-memory 0.1.20 → 0.1.22
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/client.js +61 -9
- package/lib/index.js +96 -35
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -324,6 +324,22 @@ window.__ModuleLoader__.load({
|
|
|
324
324
|
|
|
325
325
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
326
326
|
var CHANGELOG = {
|
|
327
|
+
'0.1.22': { zh: [
|
|
328
|
+
'修复:更新说明可能被公告、欢迎或自动总结弹窗覆盖,导致升级后没有看到 changelog。',
|
|
329
|
+
'优化:更新说明现在优先展示并排队其他弹窗,只有点击“知道了”后才标记为已读;未确认时重启仍会再次显示。',
|
|
330
|
+
], en: [
|
|
331
|
+
'Fix: the update changelog could be replaced by notice, welcome, or summary dialogs during startup.',
|
|
332
|
+
'Polish: update notes now take priority and queue other dialogs; the version is marked seen only after acknowledgement, so it appears again after restart when unconfirmed.',
|
|
333
|
+
] },
|
|
334
|
+
'0.1.21': { zh: [
|
|
335
|
+
'上下文与缓存:动态记忆改为运行时快照,静态规则保持稳定;切换模型、跨天和记忆刷新不再反复击穿前缀缓存。',
|
|
336
|
+
'记忆系统:三层记忆、普通检索与多关键词 recall、自动沉淀已完成运行时验证,写入、读取和检索链路稳定。',
|
|
337
|
+
'可靠性与成本:修复重启后与会话消息提取问题;自动沉淀增加最小内容门槛、冷却时间、每日上限和反递归保护,减少无效子代理调用。',
|
|
338
|
+
], en: [
|
|
339
|
+
'Context and cache: dynamic memory now uses runtime snapshots while static rules stay stable; model switches, day changes, and refreshes no longer repeatedly break prefix caching.',
|
|
340
|
+
'Memory system: three-layer memory, standard search, multi-keyword recall, and auto-consolidation have passed runtime verification for writing, reading, and retrieval.',
|
|
341
|
+
'Reliability and cost: fixed restart and session-message extraction issues; auto-consolidation now has a content threshold, cooldown, daily cap, and recursion guard to reduce unnecessary subagent calls.',
|
|
342
|
+
] },
|
|
327
343
|
'0.1.19': { zh: [
|
|
328
344
|
'稳定版:整合 0.1.16~0.1.19 全部修复与优化',
|
|
329
345
|
'核心:上下文缓存策略重写——记忆注入迁至运行时上下文快照,系统提示词保持稳定,DeepSeek 前缀缓存全程命中,不再白白消耗 token(命中率恢复 95%+)',
|
|
@@ -384,9 +400,35 @@ window.__ModuleLoader__.load({
|
|
|
384
400
|
'0.1.10': { zh: ['自动检查更新 + 设置页一键更新', '日界:凌晨的活儿归前一天(默认 7:30)', '记忆根目录系统文件夹选择器,换位置自动迁移', '每日写入预算,超限自动压缩旧内容', '30 天 AI 蒸馏'], en: ['Auto update check + one-click update in settings', 'Day boundary: late-night work logs to yesterday (default 07:30)', 'Native OS folder picker for memory root, auto-migration', 'Daily write budget with auto-compaction', '30-day AI distillation'] },
|
|
385
401
|
}
|
|
386
402
|
var dialogListeners = new Set()
|
|
387
|
-
var dialogState = null //
|
|
388
|
-
|
|
389
|
-
function
|
|
403
|
+
var dialogState = null // 当前展示的弹窗
|
|
404
|
+
var dialogQueue = [] // 启动期多个异步弹窗按优先级排队,不相互覆盖
|
|
405
|
+
function dialogPriority(d) {
|
|
406
|
+
if (!d) return 0
|
|
407
|
+
if (d.kind === 'update') return 100
|
|
408
|
+
if (d.kind === 'first') return 90
|
|
409
|
+
if (d.kind === 'notice') return d.notice && d.notice.level === 'urgent' ? 80 : 70
|
|
410
|
+
if (d.kind === 'summary') return 60
|
|
411
|
+
if (d.kind === 'welcomeBack') return 50
|
|
412
|
+
return 10
|
|
413
|
+
}
|
|
414
|
+
function dialogKey(d) {
|
|
415
|
+
if (!d) return ''
|
|
416
|
+
if (d.kind === 'update') return 'update:' + (d.currentVersion || '')
|
|
417
|
+
if (d.kind === 'notice') return 'notice:' + ((d.notice && d.notice.id) || '')
|
|
418
|
+
if (d.kind === 'summary') return 'summary:' + ((d.summary && d.summary.date) || '') + ':' + ((d.summary && d.summary.time) || '')
|
|
419
|
+
return d.kind
|
|
420
|
+
}
|
|
421
|
+
function notifyDialog() { dialogListeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
|
|
422
|
+
function openDialog(d) {
|
|
423
|
+
if (!d) return
|
|
424
|
+
var key = dialogKey(d)
|
|
425
|
+
if ((dialogState && dialogKey(dialogState) === key) || dialogQueue.some(function (x) { return dialogKey(x) === key })) return
|
|
426
|
+
if (!dialogState) dialogState = d
|
|
427
|
+
else if (dialogPriority(d) > dialogPriority(dialogState)) { dialogQueue.unshift(dialogState); dialogState = d }
|
|
428
|
+
else dialogQueue.push(d)
|
|
429
|
+
notifyDialog()
|
|
430
|
+
}
|
|
431
|
+
function closeDialog() { dialogState = dialogQueue.shift() || null; notifyDialog() }
|
|
390
432
|
function onDialog(fn) { dialogListeners.add(fn); return function () { dialogListeners.delete(fn) } }
|
|
391
433
|
function cmpVersion(a, b) {
|
|
392
434
|
var pa = String(a).split('.').map(Number), pb = String(b).split('.').map(Number)
|
|
@@ -1350,7 +1392,13 @@ window.__ModuleLoader__.load({
|
|
|
1350
1392
|
h('div', { style: sub }, t('guideSub')),
|
|
1351
1393
|
feats.map(function (f) { return h('div', { style: item }, h('span', { style: dot }), f) }),
|
|
1352
1394
|
h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', opacity: .8, marginTop: '4px' } }, t('guideTip')),
|
|
1353
|
-
h('button', { 'data-dam-btn': '', style: close, onClick:
|
|
1395
|
+
h('button', { 'data-dam-btn': '', style: close, onClick: function () {
|
|
1396
|
+
try {
|
|
1397
|
+
localStorage.setItem('dsh-auto-memory.firstRunDone', '1')
|
|
1398
|
+
if (dialogState && dialogState.currentVersion) localStorage.setItem('dsh-auto-memory.seenVersion', dialogState.currentVersion)
|
|
1399
|
+
} catch (e3) {}
|
|
1400
|
+
closeDialog()
|
|
1401
|
+
} }, t('gotIt'))))
|
|
1354
1402
|
}
|
|
1355
1403
|
if (dialogState.kind === 'notice') {
|
|
1356
1404
|
var n = dialogState.notice || {}
|
|
@@ -1407,7 +1455,10 @@ window.__ModuleLoader__.load({
|
|
|
1407
1455
|
h('div', { style: { fontSize: 'calc(13px * var(--dam-scale))', fontWeight: 700, margin: '6px 0 2px', opacity: .9 } }, 'v' + v.version),
|
|
1408
1456
|
items.map(function (it) { return h('div', { style: item }, h('span', { style: dot }), it) }))
|
|
1409
1457
|
}),
|
|
1410
|
-
h('button', { 'data-dam-btn': '', style: close, onClick:
|
|
1458
|
+
h('button', { 'data-dam-btn': '', style: close, onClick: function () {
|
|
1459
|
+
try { if (lastV) localStorage.setItem('dsh-auto-memory.seenVersion', lastV) } catch (e3) {}
|
|
1460
|
+
closeDialog()
|
|
1461
|
+
} }, t('gotIt'))))
|
|
1411
1462
|
}
|
|
1412
1463
|
|
|
1413
1464
|
// ───────────────────────── 设置页 ─────────────────────────
|
|
@@ -1634,13 +1685,14 @@ window.__ModuleLoader__.load({
|
|
|
1634
1685
|
try {
|
|
1635
1686
|
var seen = localStorage.getItem('dsh-auto-memory.seenVersion')
|
|
1636
1687
|
if (!seen && !localStorage.getItem('dsh-auto-memory.firstRunDone')) {
|
|
1637
|
-
|
|
1638
|
-
openDialog({ kind: 'first' })
|
|
1688
|
+
openDialog({ kind: 'first', currentVersion: d.current })
|
|
1639
1689
|
} else if (seen && seen !== d.current) {
|
|
1640
1690
|
var versions = changelogBetween(seen, d.current)
|
|
1641
|
-
if (versions.length) openDialog({ kind: 'update', versions: versions })
|
|
1691
|
+
if (versions.length) openDialog({ kind: 'update', versions: versions, currentVersion: d.current })
|
|
1692
|
+
else try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
|
|
1693
|
+
} else if (!seen && localStorage.getItem('dsh-auto-memory.firstRunDone')) {
|
|
1694
|
+
try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
|
|
1642
1695
|
}
|
|
1643
|
-
try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
|
|
1644
1696
|
} catch (e) {}
|
|
1645
1697
|
}
|
|
1646
1698
|
}).catch(function () {})
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - 路由:/api/dsh-auto-memory/{state,list,file,recall,config,reflect}(loopback-only)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { readFile, writeFile, mkdir, readdir, stat, rm, copyFile } from 'node:fs/promises'
|
|
20
|
+
import { readFile, writeFile, mkdir, readdir, stat, rm, copyFile, appendFile } from 'node:fs/promises'
|
|
21
21
|
import { createReadStream, existsSync } from 'node:fs'
|
|
22
22
|
import { exec as cpExec } from 'node:child_process'
|
|
23
23
|
import { promisify } from 'node:util'
|
|
@@ -82,7 +82,11 @@ const DEFAULT_CONFIG = {
|
|
|
82
82
|
/** 每轮对话结束自动沉淀记忆(subagent 判断+提炼,有 API 成本;默认开)。 */
|
|
83
83
|
autoConsolidate: true,
|
|
84
84
|
/** 自动沉淀内容门槛:本轮 user+assistant 文本总字符数低于此值视为寒暄,跳过。 */
|
|
85
|
-
autoConsolidateMinChars:
|
|
85
|
+
autoConsolidateMinChars: 240,
|
|
86
|
+
/** 自动沉淀冷却分钟:避免连续短轮反复调用 subagent。 */
|
|
87
|
+
autoConsolidateCooldownMinutes: 5,
|
|
88
|
+
/** 自动沉淀每日最多调用次数(跨插件实例应只保留一个实例)。 */
|
|
89
|
+
autoConsolidateDailyMax: 8,
|
|
86
90
|
/** 暂离阈值(分钟):距上次活动超过该值视为暂离,回归时自动弹出记忆窗口并欢迎。默认 60。 */
|
|
87
91
|
awayMinutes: 60,
|
|
88
92
|
/** 自动总结时间点(24h "HH:MM" 数组,如 ["12:00","18:00","22:00"]):到点自动生成本时段总结并弹窗展示。空数组=关闭。 */
|
|
@@ -136,6 +140,16 @@ function dshHome() {
|
|
|
136
140
|
return path.join(homedir(), '.dsh')
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
/** 诊断输出:写 ~/.dsh/dsh-auto-memory-diagnose.log(append)+console.log 双保险。验证完移除。 */
|
|
144
|
+
let _diagChain = Promise.resolve()
|
|
145
|
+
function diag(msg) {
|
|
146
|
+
try {
|
|
147
|
+
const line = new Date().toISOString() + ' ' + msg + '\n'
|
|
148
|
+
_diagChain = _diagChain.then(() => appendFile(path.join(dshHome(), 'dsh-auto-memory-diagnose.log'), line, 'utf8')).catch(() => {})
|
|
149
|
+
console.log('[dsh-auto-memory] ' + msg)
|
|
150
|
+
} catch (e) {}
|
|
151
|
+
}
|
|
152
|
+
|
|
139
153
|
/** 记忆引擎:路径解析、缓存、文件读写、检索、反思状态。 */
|
|
140
154
|
class MemoryEngine {
|
|
141
155
|
constructor() {
|
|
@@ -158,6 +172,9 @@ class MemoryEngine {
|
|
|
158
172
|
this._lastAgent = undefined // 最近一次 agent 引用(subagent parent 需要完整 agent 对象)
|
|
159
173
|
this._lastTurnByAgent = undefined // Map<agentId, turn>:自动沉淀去重(每轮只写一次)
|
|
160
174
|
this._consolidating = undefined // 自动沉淀进行中标记(防重入)
|
|
175
|
+
this._autoCallDate = ''
|
|
176
|
+
this._autoCallCount = 0
|
|
177
|
+
this._lastConsolidateStartedAt = 0
|
|
161
178
|
this._budgets = undefined // 每日写入预算:用户级4000/项目级3000字/天(所有会话共享,跨天重置)
|
|
162
179
|
this.autoStats = { count: 0, lastAt: 0, lastText: '', lastDate: '' } // 自动沉淀统计(GUI 即时反馈)
|
|
163
180
|
this.external = new ExternalMemory(this)
|
|
@@ -1187,7 +1204,10 @@ class MemoryEngine {
|
|
|
1187
1204
|
heartbeat,
|
|
1188
1205
|
autoConsolidate: {
|
|
1189
1206
|
enabled: this.config.autoConsolidate !== false,
|
|
1190
|
-
minChars: Math.max(Number(this.config.autoConsolidateMinChars) ||
|
|
1207
|
+
minChars: Math.max(Number(this.config.autoConsolidateMinChars) || 240, 80),
|
|
1208
|
+
cooldownMinutes: Math.max(Number(this.config.autoConsolidateCooldownMinutes) || 5, 1),
|
|
1209
|
+
dailyMax: Math.max(Number(this.config.autoConsolidateDailyMax) || 8, 1),
|
|
1210
|
+
callCountToday: this._autoCallCount || 0,
|
|
1191
1211
|
consolidating: !!this._consolidating,
|
|
1192
1212
|
pendingQueue: (this._pendingConsolidations || []).length,
|
|
1193
1213
|
stats: this.autoStats,
|
|
@@ -1429,9 +1449,9 @@ class MemoryEngine {
|
|
|
1429
1449
|
try {
|
|
1430
1450
|
const sessionsSvc = this._sessionsSvc
|
|
1431
1451
|
const agentSvc = this._agentSvc
|
|
1432
|
-
if (!sessionsSvc || !agentSvc || typeof sessionsSvc.list !== 'function') return
|
|
1452
|
+
if (!sessionsSvc || !agentSvc || typeof sessionsSvc.list !== 'function') { diag('restoreLastAgent: svc missing sessions=' + !!sessionsSvc + ' agent=' + !!agentSvc); return }
|
|
1433
1453
|
const sessions = sessionsSvc.list()
|
|
1434
|
-
if (!sessions || !sessions.length) return
|
|
1454
|
+
if (!sessions || !sessions.length) { diag('restoreLastAgent: no sessions'); return }
|
|
1435
1455
|
// 找最近活跃的顶层会话(log 最后事件时间最大)
|
|
1436
1456
|
let best = null, bestTime = 0
|
|
1437
1457
|
for (const s of sessions) {
|
|
@@ -1439,10 +1459,15 @@ class MemoryEngine {
|
|
|
1439
1459
|
try { const last = s.log && s.log[s.log.length - 1]; if (last && last.time) t = last.time } catch (e) {}
|
|
1440
1460
|
if (t >= bestTime) { bestTime = t; best = s }
|
|
1441
1461
|
}
|
|
1442
|
-
if (!best || typeof agentSvc.get !== 'function') return
|
|
1462
|
+
if (!best || typeof agentSvc.get !== 'function') { diag('restoreLastAgent: no best session or no get'); return }
|
|
1443
1463
|
const a = agentSvc.get(best.id)
|
|
1444
|
-
if (a && a.session && a.session.header && a.session.header.parentSession === undefined)
|
|
1445
|
-
|
|
1464
|
+
if (a && a.session && a.session.header && a.session.header.parentSession === undefined) {
|
|
1465
|
+
this._lastAgent = a
|
|
1466
|
+
diag('restoreLastAgent: recovered agent id=' + a.id + ' session=' + a.session.id + ' logEvents=' + ((a.session.events && a.session.events.length) || 0))
|
|
1467
|
+
} else {
|
|
1468
|
+
diag('restoreLastAgent: candidate rejected (id=' + (a && a.id) + ' hasSession=' + !!(a && a.session) + ' parent=' + (a && a.session && a.session.header && a.session.header.parentSession) + ')')
|
|
1469
|
+
}
|
|
1470
|
+
} catch (e) { diag('restoreLastAgent error: ' + (e && e.message)) }
|
|
1446
1471
|
}
|
|
1447
1472
|
|
|
1448
1473
|
/** 自动总结:按时间点推断时段,生成总结并置 pendingSummary(供 client 弹窗)。 */
|
|
@@ -1503,6 +1528,10 @@ class MemoryEngine {
|
|
|
1503
1528
|
if (!subagents) return ''
|
|
1504
1529
|
// parent 必须是完整 agent 对象(captureDelegatedPolicyOverrides 读 parent.ctx/parent.session);路由调用用缓存的最近 agent
|
|
1505
1530
|
const parent = agent || this._lastAgent
|
|
1531
|
+
if (!parent || !parent.session || !parent.ctx || typeof parent.ctx.get !== 'function') {
|
|
1532
|
+
diag('subagent ' + label + ' skipped: incomplete parent context')
|
|
1533
|
+
return ''
|
|
1534
|
+
}
|
|
1506
1535
|
// 动态选择可用的 subagent provider(优先 spawn,否则取已注册的第一个)
|
|
1507
1536
|
let providerName = 'spawn'
|
|
1508
1537
|
let registered = []
|
|
@@ -1524,7 +1553,9 @@ class MemoryEngine {
|
|
|
1524
1553
|
const blocks = result && result.output ? result.output : []
|
|
1525
1554
|
return blocks.filter((b) => b && b.type === 'text').map((b) => b.text).join('').trim()
|
|
1526
1555
|
} catch (e) {
|
|
1527
|
-
|
|
1556
|
+
const em = e && e.message ? e.message : String(e)
|
|
1557
|
+
console.error('[dsh-auto-memory] ' + label + ' failed', em)
|
|
1558
|
+
diag('subagent ' + label + ' failed: ' + em)
|
|
1528
1559
|
return ''
|
|
1529
1560
|
} finally {
|
|
1530
1561
|
clearTimeout(timer)
|
|
@@ -1545,31 +1576,41 @@ class MemoryEngine {
|
|
|
1545
1576
|
|
|
1546
1577
|
/** 每轮对话结束自动沉淀:取本轮 user+assistant 消息 → subagent 判断/提炼 → 写今日日志+升格。 */
|
|
1547
1578
|
async consolidateTurn(turn, agent) {
|
|
1548
|
-
|
|
1579
|
+
const why = (reason) => diag('consolidate skip: ' + reason + ' (turn=' + JSON.stringify(turn) + ' agentId=' + ((agent && (agent.id || (agent.session && agent.session.id))) || '?') + ')')
|
|
1580
|
+
if (this._consolidating) { why('_consolidating busy'); return }
|
|
1549
1581
|
if (!this.configLoaded) { try { await this.loadConfig() } catch (e) {} }
|
|
1550
|
-
if (this.config.autoConsolidate === false) return
|
|
1551
|
-
if (!agent || !agent.session) return
|
|
1582
|
+
if (this.config.autoConsolidate === false) { why('config.autoConsolidate=false'); return }
|
|
1583
|
+
if (!agent || !agent.session) { why('no agent/session'); return }
|
|
1552
1584
|
// 只处理顶层会话(子代理/接续会话的 header.parentSession 非空,避免子代理轮次误沉淀)
|
|
1553
|
-
try { if (agent.session.header && agent.session.header.parentSession) return } catch (e) {}
|
|
1554
|
-
const minChars = Math.max(Number(this.config.autoConsolidateMinChars) ||
|
|
1585
|
+
try { if (agent.session.header && agent.session.header.parentSession) { why('parentSession sub-agent'); return } } catch (e) {}
|
|
1586
|
+
const minChars = Math.max(Number(this.config.autoConsolidateMinChars) || 240, 80)
|
|
1587
|
+
const today = this.memToday()
|
|
1588
|
+
if (this._autoCallDate !== today) { this._autoCallDate = today; this._autoCallCount = 0 }
|
|
1589
|
+
const cooldownMs = Math.max(Number(this.config.autoConsolidateCooldownMinutes) || 5, 1) * 60000
|
|
1590
|
+
const dailyMax = Math.max(Number(this.config.autoConsolidateDailyMax) || 8, 1)
|
|
1591
|
+
if (this._autoCallCount >= dailyMax) { why('daily subagent cap=' + dailyMax); return }
|
|
1592
|
+
if (this._lastConsolidateStartedAt && Date.now() - this._lastConsolidateStartedAt < cooldownMs) { why('cooldown'); return }
|
|
1555
1593
|
// 按 turn 去重:同一 agent 的同一轮只处理一次
|
|
1556
1594
|
const agentId = agent.id || (agent.session && agent.session.id) || '?'
|
|
1557
1595
|
if (!this._lastTurnByAgent) this._lastTurnByAgent = new Map()
|
|
1558
1596
|
const lastTurn = this._lastTurnByAgent.get(agentId)
|
|
1559
|
-
if (lastTurn === turn) return
|
|
1597
|
+
if (lastTurn === turn) { why('dup turn'); return }
|
|
1560
1598
|
this._lastTurnByAgent.set(agentId, turn)
|
|
1561
1599
|
// 取本轮最后一条 user + 最后一条 assistant(模型可见消息序列)
|
|
1562
1600
|
const messages = extractSessionMessages(agent)
|
|
1563
|
-
if (messages.length < 2) return
|
|
1601
|
+
if (messages.length < 2) { why('messages<2 got=' + messages.length + ' seqs=' + ((agent.session.surface && agent.session.surface.nodes && (Array.isArray(agent.session.surface.nodes) ? agent.session.surface.nodes.length : 'set')) || 'none') + ' events=' + ((agent.session.events && agent.session.events.length) || 'none')); return }
|
|
1564
1602
|
let userText = ''
|
|
1565
1603
|
let assistantText = ''
|
|
1566
1604
|
for (const m of messages) {
|
|
1567
1605
|
if (m.role === 'user') userText = m.text
|
|
1568
1606
|
else if (m.role === 'assistant' && m.text) assistantText = m.text
|
|
1569
1607
|
}
|
|
1570
|
-
if (!userText.trim() || !assistantText.trim()) return
|
|
1608
|
+
if (!userText.trim() || !assistantText.trim()) { why('empty text user=' + userText.length + ' asst=' + assistantText.length); return }
|
|
1571
1609
|
const combined = userText + '\n' + assistantText
|
|
1572
|
-
if (combined.length < minChars) return
|
|
1610
|
+
if (combined.length < minChars) { why('too short combined=' + combined.length + ' min=' + minChars); return }
|
|
1611
|
+
this._lastConsolidateStartedAt = Date.now()
|
|
1612
|
+
this._autoCallCount++
|
|
1613
|
+
diag('consolidate subagent start count=' + this._autoCallCount + '/' + dailyMax + ' inputChars=' + Math.min(combined.length, 6000))
|
|
1573
1614
|
this._consolidating = (async () => {
|
|
1574
1615
|
try {
|
|
1575
1616
|
await this.refresh(agent)
|
|
@@ -1606,6 +1647,7 @@ class MemoryEngine {
|
|
|
1606
1647
|
].join('\n')
|
|
1607
1648
|
const text = await this.runSubagent(prompt, 'auto-memory-consolidate', agent)
|
|
1608
1649
|
if (!text) {
|
|
1650
|
+
diag('consolidate: subagent returned empty text (queued for retry)')
|
|
1609
1651
|
// subagent 失败(返回空):入重试队列,由后台轮询兜底重试
|
|
1610
1652
|
if (!this._pendingConsolidations) this._pendingConsolidations = []
|
|
1611
1653
|
if (this._pendingConsolidations.length < 5) this._pendingConsolidations.push({ turn, agent })
|
|
@@ -2220,28 +2262,46 @@ class ExternalMemory {
|
|
|
2220
2262
|
}
|
|
2221
2263
|
}
|
|
2222
2264
|
|
|
2223
|
-
/** 从
|
|
2265
|
+
/** 从 session 提取消息。surface 不是完整可靠的 user 来源,因此失败时回退完整事件日志。 */
|
|
2266
|
+
function messageOfEvent(ev) {
|
|
2267
|
+
if (!ev) return null
|
|
2268
|
+
if (ev.type === 'user/message') return ev.data && ev.data.message ? ev.data.message : ev.data
|
|
2269
|
+
if (ev.type === 'assistant/message' || ev.type === 'tool/result') return ev.data && ev.data.message
|
|
2270
|
+
return null
|
|
2271
|
+
}
|
|
2272
|
+
function textOfContent(content) {
|
|
2273
|
+
const out = []
|
|
2274
|
+
const walk = (v, depth) => {
|
|
2275
|
+
if (depth > 8 || v == null) return
|
|
2276
|
+
if (typeof v === 'string') { out.push(v); return }
|
|
2277
|
+
if (Array.isArray(v)) { for (const x of v) walk(x, depth + 1); return }
|
|
2278
|
+
if (typeof v !== 'object') return
|
|
2279
|
+
if (typeof v.text === 'string') out.push(v.text)
|
|
2280
|
+
else if (typeof v.input_text === 'string') out.push(v.input_text)
|
|
2281
|
+
if (v.content !== undefined) walk(v.content, depth + 1)
|
|
2282
|
+
}
|
|
2283
|
+
walk(content, 0)
|
|
2284
|
+
return out.join('')
|
|
2285
|
+
}
|
|
2224
2286
|
function extractSessionMessages(agent) {
|
|
2225
2287
|
try {
|
|
2226
2288
|
const session = agent && agent.session
|
|
2227
2289
|
if (!session) return []
|
|
2228
|
-
const nodes = session.surface && session.surface.nodes
|
|
2229
|
-
if (!nodes) return []
|
|
2230
|
-
const seqs = Array.isArray(nodes) ? nodes : (nodes instanceof Set ? Array.from(nodes) : [])
|
|
2231
|
-
if (!seqs.length) return []
|
|
2232
2290
|
const events = session.events || []
|
|
2291
|
+
const seqs = []
|
|
2292
|
+
try {
|
|
2293
|
+
const nodes = session.surface && session.surface.nodes
|
|
2294
|
+
if (Array.isArray(nodes)) seqs.push(...nodes)
|
|
2295
|
+
else if (nodes instanceof Set) seqs.push(...Array.from(nodes))
|
|
2296
|
+
} catch (e) {}
|
|
2297
|
+
// surface 可能遗漏刚提交的 user/message;追加完整事件序列并去重。
|
|
2298
|
+
for (let i = 0; i < events.length; i++) if (!seqs.includes(i)) seqs.push(i)
|
|
2233
2299
|
const out = []
|
|
2234
2300
|
for (const seq of seqs) {
|
|
2235
2301
|
const ev = events[seq]
|
|
2236
|
-
|
|
2237
|
-
let msg = null
|
|
2238
|
-
if (ev.type === 'user/message') msg = ev.data
|
|
2239
|
-
else if (ev.type === 'assistant/message' || ev.type === 'tool/result') msg = ev.data && ev.data.message
|
|
2302
|
+
const msg = messageOfEvent(ev)
|
|
2240
2303
|
if (!msg || !msg.role || !Array.isArray(msg.content)) continue
|
|
2241
|
-
|
|
2242
|
-
.filter((b) => b && typeof b === 'object' && typeof b.text === 'string')
|
|
2243
|
-
.map((b) => b.text).join('')
|
|
2244
|
-
out.push({ role: msg.role, text, sourceKind: msg.source && msg.source.kind })
|
|
2304
|
+
out.push({ role: msg.role, text: textOfContent(msg.content), sourceKind: msg.source && msg.source.kind })
|
|
2245
2305
|
}
|
|
2246
2306
|
return out
|
|
2247
2307
|
} catch (e) { return [] }
|
|
@@ -2321,11 +2381,12 @@ export function apply(ctx, config) {
|
|
|
2321
2381
|
// 记录最后活动时间(判断暂离回来用)
|
|
2322
2382
|
try { engine._lastActiveAt = Date.now() } catch (e) {}
|
|
2323
2383
|
// 每轮自动沉淀:取本轮消息 → subagent 判断/提炼 → 写今日日志([自动沉淀])+升格长期记忆
|
|
2324
|
-
// 注意:turn-stopping payload 只有 {turn, signal},没有 agent 字段,必须用 _lastAgent 兜底
|
|
2325
2384
|
try {
|
|
2326
2385
|
const agent = (payload && payload.agent) || engine._lastAgent
|
|
2327
|
-
|
|
2328
|
-
|
|
2386
|
+
const hasAgent = !!(agent && agent.session)
|
|
2387
|
+
diag('turn-stopping fired: turn=' + JSON.stringify(payload && payload.turn) + ' hasAgent=' + hasAgent + ' payloadKeys=' + (payload ? Object.keys(payload).join(',') : 'null') + ' _lastAgent=' + !!(engine._lastAgent && engine._lastAgent.session))
|
|
2388
|
+
if (hasAgent) void engine.consolidateTurn(payload.turn, agent)
|
|
2389
|
+
} catch (e) { diag('turn-stopping handler error: ' + (e && e.message)) }
|
|
2329
2390
|
})
|
|
2330
2391
|
// 注入层保障:systemPrompt section.text 是同步函数不能 await,state 异步加载会导致首轮注入为空。
|
|
2331
2392
|
// pre-step 是 waterfall(可 await),在放行每个 step 前条件性刷新:
|
|
@@ -2335,7 +2396,7 @@ export function apply(ctx, config) {
|
|
|
2335
2396
|
const agent = (payload && payload.agent) || engine._lastAgent
|
|
2336
2397
|
// 兜底恢复 _lastAgent(仅启动后首次为空时设置一次,不每步覆盖):重启后恢复的会话不触发 session-start,
|
|
2337
2398
|
// 导致 turn-stopping 自动沉淀拿不到 agent 而永久失效;首次 pre-step 即恢复,之后由 session-start 正常维护
|
|
2338
|
-
try { if (!engine._lastAgent && agent && agent.session && agent.session.header && agent.session.header.parentSession === undefined) engine._lastAgent = agent } catch (e) {}
|
|
2399
|
+
try { if (!engine._lastAgent && agent && agent.session && agent.session.header && agent.session.header.parentSession === undefined) { engine._lastAgent = agent; diag('pre-step recovered _lastAgent id=' + agent.id) } } catch (e) {}
|
|
2339
2400
|
// 只刷新顶层会话:子代理(自动沉淀/固化的 subagent)session 无 cwd,刷新会把 state 切到错误工作区
|
|
2340
2401
|
let skip = false
|
|
2341
2402
|
try { if (agent && agent.session && agent.session.header && agent.session.header.parentSession) skip = true } catch (e) {}
|
package/package.json
CHANGED