@a9i5k4/dsh-auto-memory 0.1.19 → 0.1.21
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/cordis.patch.yml +2 -2
- package/lib/client.js +9 -0
- package/lib/index.js +126 -46
- package/package.json +1 -1
package/cordis.patch.yml
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
# the `dsh.client` declaration in package.json makes the browser half
|
|
6
6
|
# (exports "./client", served at /plugins/<id>/client.js) load in the web GUI.
|
|
7
7
|
- insert:
|
|
8
|
-
- id: auto-memory
|
|
9
|
-
name: '@
|
|
8
|
+
- id: auto-memory
|
|
9
|
+
name: '@a9i5k4/dsh-auto-memory'
|
package/lib/client.js
CHANGED
|
@@ -324,6 +324,15 @@ window.__ModuleLoader__.load({
|
|
|
324
324
|
|
|
325
325
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
326
326
|
var CHANGELOG = {
|
|
327
|
+
'0.1.21': { zh: [
|
|
328
|
+
'上下文与缓存:动态记忆改为运行时快照,静态规则保持稳定;切换模型、跨天和记忆刷新不再反复击穿前缀缓存。',
|
|
329
|
+
'记忆系统:三层记忆、普通检索与多关键词 recall、自动沉淀已完成运行时验证,写入、读取和检索链路稳定。',
|
|
330
|
+
'可靠性与成本:修复重启后与会话消息提取问题;自动沉淀增加最小内容门槛、冷却时间、每日上限和反递归保护,减少无效子代理调用。',
|
|
331
|
+
], en: [
|
|
332
|
+
'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.',
|
|
333
|
+
'Memory system: three-layer memory, standard search, multi-keyword recall, and auto-consolidation have passed runtime verification for writing, reading, and retrieval.',
|
|
334
|
+
'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.',
|
|
335
|
+
] },
|
|
327
336
|
'0.1.19': { zh: [
|
|
328
337
|
'稳定版:整合 0.1.16~0.1.19 全部修复与优化',
|
|
329
338
|
'核心:上下文缓存策略重写——记忆注入迁至运行时上下文快照,系统提示词保持稳定,DeepSeek 前缀缓存全程命中,不再白白消耗 token(命中率恢复 95%+)',
|
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)
|
|
@@ -842,6 +859,8 @@ class MemoryEngine {
|
|
|
842
859
|
async recall(query, limit = 8, agent) {
|
|
843
860
|
const q = String(query || '').toLowerCase().trim()
|
|
844
861
|
if (!q) return 'memory_recall: query 为空。'
|
|
862
|
+
// 多词查询:按空白/中文标点分词,任一词命中即算命中(OR),按命中词数排序取相关度最高的
|
|
863
|
+
const terms = q.split(/[\s,,、;;。::]+/).filter((t) => t.length > 0)
|
|
845
864
|
const p = await this.resolvePaths(agent)
|
|
846
865
|
const out = []
|
|
847
866
|
const hits = []
|
|
@@ -850,12 +869,17 @@ class MemoryEngine {
|
|
|
850
869
|
if (!text) return
|
|
851
870
|
const matched = []
|
|
852
871
|
for (const line of text.split('\n')) {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
872
|
+
const low = line.toLowerCase()
|
|
873
|
+
const score = terms.reduce((a, t) => a + (low.includes(t) ? 1 : 0), 0)
|
|
874
|
+
if (score > 0) {
|
|
875
|
+
matched.push({ line: line.trim().slice(0, 200), score })
|
|
876
|
+
if (matched.length >= maxMatches * 4) break
|
|
856
877
|
}
|
|
857
878
|
}
|
|
858
|
-
if (matched.length)
|
|
879
|
+
if (matched.length) {
|
|
880
|
+
matched.sort((a, b) => b.score - a.score)
|
|
881
|
+
target.push({ where: label, matches: matched.slice(0, maxMatches).map((m) => m.line) })
|
|
882
|
+
}
|
|
859
883
|
}
|
|
860
884
|
// 读取顺序:progress(日志/反思)先行,再读 memory(用户级/项目笔记)
|
|
861
885
|
const logs = await this.listDailyLogs(p.projectDir, 40)
|
|
@@ -1180,7 +1204,10 @@ class MemoryEngine {
|
|
|
1180
1204
|
heartbeat,
|
|
1181
1205
|
autoConsolidate: {
|
|
1182
1206
|
enabled: this.config.autoConsolidate !== false,
|
|
1183
|
-
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,
|
|
1184
1211
|
consolidating: !!this._consolidating,
|
|
1185
1212
|
pendingQueue: (this._pendingConsolidations || []).length,
|
|
1186
1213
|
stats: this.autoStats,
|
|
@@ -1422,9 +1449,9 @@ class MemoryEngine {
|
|
|
1422
1449
|
try {
|
|
1423
1450
|
const sessionsSvc = this._sessionsSvc
|
|
1424
1451
|
const agentSvc = this._agentSvc
|
|
1425
|
-
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 }
|
|
1426
1453
|
const sessions = sessionsSvc.list()
|
|
1427
|
-
if (!sessions || !sessions.length) return
|
|
1454
|
+
if (!sessions || !sessions.length) { diag('restoreLastAgent: no sessions'); return }
|
|
1428
1455
|
// 找最近活跃的顶层会话(log 最后事件时间最大)
|
|
1429
1456
|
let best = null, bestTime = 0
|
|
1430
1457
|
for (const s of sessions) {
|
|
@@ -1432,10 +1459,15 @@ class MemoryEngine {
|
|
|
1432
1459
|
try { const last = s.log && s.log[s.log.length - 1]; if (last && last.time) t = last.time } catch (e) {}
|
|
1433
1460
|
if (t >= bestTime) { bestTime = t; best = s }
|
|
1434
1461
|
}
|
|
1435
|
-
if (!best || typeof agentSvc.get !== 'function') return
|
|
1462
|
+
if (!best || typeof agentSvc.get !== 'function') { diag('restoreLastAgent: no best session or no get'); return }
|
|
1436
1463
|
const a = agentSvc.get(best.id)
|
|
1437
|
-
if (a && a.session && a.session.header && a.session.header.parentSession === undefined)
|
|
1438
|
-
|
|
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)) }
|
|
1439
1471
|
}
|
|
1440
1472
|
|
|
1441
1473
|
/** 自动总结:按时间点推断时段,生成总结并置 pendingSummary(供 client 弹窗)。 */
|
|
@@ -1496,6 +1528,10 @@ class MemoryEngine {
|
|
|
1496
1528
|
if (!subagents) return ''
|
|
1497
1529
|
// parent 必须是完整 agent 对象(captureDelegatedPolicyOverrides 读 parent.ctx/parent.session);路由调用用缓存的最近 agent
|
|
1498
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
|
+
}
|
|
1499
1535
|
// 动态选择可用的 subagent provider(优先 spawn,否则取已注册的第一个)
|
|
1500
1536
|
let providerName = 'spawn'
|
|
1501
1537
|
let registered = []
|
|
@@ -1517,7 +1553,9 @@ class MemoryEngine {
|
|
|
1517
1553
|
const blocks = result && result.output ? result.output : []
|
|
1518
1554
|
return blocks.filter((b) => b && b.type === 'text').map((b) => b.text).join('').trim()
|
|
1519
1555
|
} catch (e) {
|
|
1520
|
-
|
|
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)
|
|
1521
1559
|
return ''
|
|
1522
1560
|
} finally {
|
|
1523
1561
|
clearTimeout(timer)
|
|
@@ -1538,31 +1576,41 @@ class MemoryEngine {
|
|
|
1538
1576
|
|
|
1539
1577
|
/** 每轮对话结束自动沉淀:取本轮 user+assistant 消息 → subagent 判断/提炼 → 写今日日志+升格。 */
|
|
1540
1578
|
async consolidateTurn(turn, agent) {
|
|
1541
|
-
|
|
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 }
|
|
1542
1581
|
if (!this.configLoaded) { try { await this.loadConfig() } catch (e) {} }
|
|
1543
|
-
if (this.config.autoConsolidate === false) return
|
|
1544
|
-
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 }
|
|
1545
1584
|
// 只处理顶层会话(子代理/接续会话的 header.parentSession 非空,避免子代理轮次误沉淀)
|
|
1546
|
-
try { if (agent.session.header && agent.session.header.parentSession) return } catch (e) {}
|
|
1547
|
-
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 }
|
|
1548
1593
|
// 按 turn 去重:同一 agent 的同一轮只处理一次
|
|
1549
1594
|
const agentId = agent.id || (agent.session && agent.session.id) || '?'
|
|
1550
1595
|
if (!this._lastTurnByAgent) this._lastTurnByAgent = new Map()
|
|
1551
1596
|
const lastTurn = this._lastTurnByAgent.get(agentId)
|
|
1552
|
-
if (lastTurn === turn) return
|
|
1597
|
+
if (lastTurn === turn) { why('dup turn'); return }
|
|
1553
1598
|
this._lastTurnByAgent.set(agentId, turn)
|
|
1554
1599
|
// 取本轮最后一条 user + 最后一条 assistant(模型可见消息序列)
|
|
1555
1600
|
const messages = extractSessionMessages(agent)
|
|
1556
|
-
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 }
|
|
1557
1602
|
let userText = ''
|
|
1558
1603
|
let assistantText = ''
|
|
1559
1604
|
for (const m of messages) {
|
|
1560
1605
|
if (m.role === 'user') userText = m.text
|
|
1561
1606
|
else if (m.role === 'assistant' && m.text) assistantText = m.text
|
|
1562
1607
|
}
|
|
1563
|
-
if (!userText.trim() || !assistantText.trim()) return
|
|
1608
|
+
if (!userText.trim() || !assistantText.trim()) { why('empty text user=' + userText.length + ' asst=' + assistantText.length); return }
|
|
1564
1609
|
const combined = userText + '\n' + assistantText
|
|
1565
|
-
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))
|
|
1566
1614
|
this._consolidating = (async () => {
|
|
1567
1615
|
try {
|
|
1568
1616
|
await this.refresh(agent)
|
|
@@ -1599,6 +1647,7 @@ class MemoryEngine {
|
|
|
1599
1647
|
].join('\n')
|
|
1600
1648
|
const text = await this.runSubagent(prompt, 'auto-memory-consolidate', agent)
|
|
1601
1649
|
if (!text) {
|
|
1650
|
+
diag('consolidate: subagent returned empty text (queued for retry)')
|
|
1602
1651
|
// subagent 失败(返回空):入重试队列,由后台轮询兜底重试
|
|
1603
1652
|
if (!this._pendingConsolidations) this._pendingConsolidations = []
|
|
1604
1653
|
if (this._pendingConsolidations.length < 5) this._pendingConsolidations.push({ turn, agent })
|
|
@@ -2136,6 +2185,12 @@ class ExternalMemory {
|
|
|
2136
2185
|
async search(query, limit = 6) {
|
|
2137
2186
|
const q = String(query || '').toLowerCase().trim()
|
|
2138
2187
|
if (!q) return []
|
|
2188
|
+
// 多词查询:任一词命中即算命中(OR),按命中词数排序取相关度最高的
|
|
2189
|
+
const terms = q.split(/[\s,,、;;。::]+/).filter((t) => t.length > 0)
|
|
2190
|
+
const scoreLine = (line) => {
|
|
2191
|
+
const low = line.toLowerCase()
|
|
2192
|
+
return terms.reduce((a, t) => a + (low.includes(t) ? 1 : 0), 0)
|
|
2193
|
+
}
|
|
2139
2194
|
const srcs = await this.discover(false)
|
|
2140
2195
|
const out = []
|
|
2141
2196
|
for (const s of srcs) {
|
|
@@ -2147,22 +2202,28 @@ class ExternalMemory {
|
|
|
2147
2202
|
if (hits.length >= 3 || scanned >= 8 || out.length >= limit) break
|
|
2148
2203
|
scanned++
|
|
2149
2204
|
const text = await this.extractSessionText(f.path)
|
|
2205
|
+
const matched = []
|
|
2150
2206
|
for (const line of text.split('\n')) {
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2207
|
+
const score = scoreLine(line)
|
|
2208
|
+
if (score > 0) {
|
|
2209
|
+
matched.push({ line: '(' + path.basename(f.path).slice(0, 20) + ') ' + line.trim().slice(0, 200), score })
|
|
2210
|
+
if (matched.length >= 6) break
|
|
2154
2211
|
}
|
|
2155
2212
|
}
|
|
2213
|
+
matched.sort((a, b) => b.score - a.score)
|
|
2214
|
+
hits.push(...matched.slice(0, 3).map((m) => m.line))
|
|
2156
2215
|
}
|
|
2157
2216
|
} else {
|
|
2158
2217
|
const matched = []
|
|
2159
2218
|
for (const line of s.content.split('\n')) {
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2219
|
+
const score = scoreLine(line)
|
|
2220
|
+
if (score > 0) {
|
|
2221
|
+
matched.push({ line: line.trim().slice(0, 200), score })
|
|
2222
|
+
if (matched.length >= 9) break
|
|
2163
2223
|
}
|
|
2164
2224
|
}
|
|
2165
|
-
|
|
2225
|
+
matched.sort((a, b) => b.score - a.score)
|
|
2226
|
+
hits.push(...matched.slice(0, 3).map((m) => m.line))
|
|
2166
2227
|
}
|
|
2167
2228
|
if (hits.length) out.push({ source: s.name, tool: s.tool, kind: s.kind, lines: hits })
|
|
2168
2229
|
}
|
|
@@ -2201,28 +2262,46 @@ class ExternalMemory {
|
|
|
2201
2262
|
}
|
|
2202
2263
|
}
|
|
2203
2264
|
|
|
2204
|
-
/** 从
|
|
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
|
+
}
|
|
2205
2286
|
function extractSessionMessages(agent) {
|
|
2206
2287
|
try {
|
|
2207
2288
|
const session = agent && agent.session
|
|
2208
2289
|
if (!session) return []
|
|
2209
|
-
const nodes = session.surface && session.surface.nodes
|
|
2210
|
-
if (!nodes) return []
|
|
2211
|
-
const seqs = Array.isArray(nodes) ? nodes : (nodes instanceof Set ? Array.from(nodes) : [])
|
|
2212
|
-
if (!seqs.length) return []
|
|
2213
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)
|
|
2214
2299
|
const out = []
|
|
2215
2300
|
for (const seq of seqs) {
|
|
2216
2301
|
const ev = events[seq]
|
|
2217
|
-
|
|
2218
|
-
let msg = null
|
|
2219
|
-
if (ev.type === 'user/message') msg = ev.data
|
|
2220
|
-
else if (ev.type === 'assistant/message' || ev.type === 'tool/result') msg = ev.data && ev.data.message
|
|
2302
|
+
const msg = messageOfEvent(ev)
|
|
2221
2303
|
if (!msg || !msg.role || !Array.isArray(msg.content)) continue
|
|
2222
|
-
|
|
2223
|
-
.filter((b) => b && typeof b === 'object' && typeof b.text === 'string')
|
|
2224
|
-
.map((b) => b.text).join('')
|
|
2225
|
-
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 })
|
|
2226
2305
|
}
|
|
2227
2306
|
return out
|
|
2228
2307
|
} catch (e) { return [] }
|
|
@@ -2302,11 +2381,12 @@ export function apply(ctx, config) {
|
|
|
2302
2381
|
// 记录最后活动时间(判断暂离回来用)
|
|
2303
2382
|
try { engine._lastActiveAt = Date.now() } catch (e) {}
|
|
2304
2383
|
// 每轮自动沉淀:取本轮消息 → subagent 判断/提炼 → 写今日日志([自动沉淀])+升格长期记忆
|
|
2305
|
-
// 注意:turn-stopping payload 只有 {turn, signal},没有 agent 字段,必须用 _lastAgent 兜底
|
|
2306
2384
|
try {
|
|
2307
2385
|
const agent = (payload && payload.agent) || engine._lastAgent
|
|
2308
|
-
|
|
2309
|
-
|
|
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)) }
|
|
2310
2390
|
})
|
|
2311
2391
|
// 注入层保障:systemPrompt section.text 是同步函数不能 await,state 异步加载会导致首轮注入为空。
|
|
2312
2392
|
// pre-step 是 waterfall(可 await),在放行每个 step 前条件性刷新:
|
|
@@ -2316,7 +2396,7 @@ export function apply(ctx, config) {
|
|
|
2316
2396
|
const agent = (payload && payload.agent) || engine._lastAgent
|
|
2317
2397
|
// 兜底恢复 _lastAgent(仅启动后首次为空时设置一次,不每步覆盖):重启后恢复的会话不触发 session-start,
|
|
2318
2398
|
// 导致 turn-stopping 自动沉淀拿不到 agent 而永久失效;首次 pre-step 即恢复,之后由 session-start 正常维护
|
|
2319
|
-
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) {}
|
|
2320
2400
|
// 只刷新顶层会话:子代理(自动沉淀/固化的 subagent)session 无 cwd,刷新会把 state 切到错误工作区
|
|
2321
2401
|
let skip = false
|
|
2322
2402
|
try { if (agent && agent.session && agent.session.header && agent.session.header.parentSession) skip = true } catch (e) {}
|
package/package.json
CHANGED