@zooique/memora 5.1.0 → 5.3.0
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/CHANGELOG.md +107 -4
- package/README.en.md +6 -2
- package/README.md +6 -2
- package/dist/agent/agent.d.ts +42 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +61 -3
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +7 -0
- package/dist/agent/assembler.js.map +1 -1
- package/dist/agent/constants.d.ts +49 -0
- package/dist/agent/constants.d.ts.map +1 -1
- package/dist/agent/constants.js +49 -0
- package/dist/agent/constants.js.map +1 -1
- package/dist/agent/contextManager.d.ts +31 -3
- package/dist/agent/contextManager.d.ts.map +1 -1
- package/dist/agent/contextManager.js +39 -2
- package/dist/agent/contextManager.js.map +1 -1
- package/dist/agent/loop.d.ts +14 -7
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +43 -18
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/managers/llmCaller.d.ts.map +1 -1
- package/dist/agent/managers/llmCaller.js +14 -1
- package/dist/agent/managers/llmCaller.js.map +1 -1
- package/dist/agent/managers/sessionManager.d.ts.map +1 -1
- package/dist/agent/managers/sessionManager.js +1 -0
- package/dist/agent/managers/sessionManager.js.map +1 -1
- package/dist/agent/managers/toolCallHelpers.d.ts.map +1 -1
- package/dist/agent/managers/toolCallHelpers.js +3 -0
- package/dist/agent/managers/toolCallHelpers.js.map +1 -1
- package/dist/agent/messageHistory.d.ts +86 -16
- package/dist/agent/messageHistory.d.ts.map +1 -1
- package/dist/agent/messageHistory.js +203 -65
- package/dist/agent/messageHistory.js.map +1 -1
- package/dist/agent/seed/orchestrator.d.ts +2 -1
- package/dist/agent/seed/orchestrator.d.ts.map +1 -1
- package/dist/agent/seed/orchestrator.js +48 -17
- package/dist/agent/seed/orchestrator.js.map +1 -1
- package/dist/agent/seed/prepare.d.ts +3 -1
- package/dist/agent/seed/prepare.d.ts.map +1 -1
- package/dist/agent/seed/prepare.js +10 -2
- package/dist/agent/seed/prepare.js.map +1 -1
- package/dist/agent/sessionStateMachine.d.ts +3 -0
- package/dist/agent/sessionStateMachine.d.ts.map +1 -1
- package/dist/agent/sessionStateMachine.js.map +1 -1
- package/dist/agent/textSanitize.d.ts +1 -1
- package/dist/agent/textSanitize.js +1 -1
- package/dist/agent/types.d.ts +9 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/llm/factory.d.ts +21 -0
- package/dist/llm/factory.d.ts.map +1 -1
- package/dist/llm/factory.js +4 -1
- package/dist/llm/factory.js.map +1 -1
- package/dist/llm/openaiCompatible.d.ts +8 -1
- package/dist/llm/openaiCompatible.d.ts.map +1 -1
- package/dist/llm/openaiCompatible.js +26 -0
- package/dist/llm/openaiCompatible.js.map +1 -1
- package/dist/llm/provider.d.ts +65 -0
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +10 -0
- package/dist/llm/provider.js.map +1 -1
- package/dist/memory/gcService.d.ts +8 -4
- package/dist/memory/gcService.d.ts.map +1 -1
- package/dist/memory/gcService.js.map +1 -1
- package/dist/memory/inMemoryRoundStore.d.ts +8 -3
- package/dist/memory/inMemoryRoundStore.d.ts.map +1 -1
- package/dist/memory/inMemoryRoundStore.js +19 -10
- package/dist/memory/inMemoryRoundStore.js.map +1 -1
- package/dist/memory/inMemorySessionStore.d.ts.map +1 -1
- package/dist/memory/inMemorySessionStore.js +11 -0
- package/dist/memory/inMemorySessionStore.js.map +1 -1
- package/dist/memory/roundStore.d.ts +84 -11
- package/dist/memory/roundStore.d.ts.map +1 -1
- package/dist/memory/roundStore.js +18 -0
- package/dist/memory/roundStore.js.map +1 -1
- package/dist/memory/sessionStore.d.ts +8 -0
- package/dist/memory/sessionStore.d.ts.map +1 -1
- package/dist/memory/sessionStore.js.map +1 -1
- package/dist/utils/time.d.ts +24 -4
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +32 -4
- package/dist/utils/time.js.map +1 -1
- package/docs/memora-api-reference.md +14 -4
- package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +45 -17
- package/package.json +5 -2
|
@@ -27,8 +27,11 @@ export function parseAskCalls(askCalls) {
|
|
|
27
27
|
catch {
|
|
28
28
|
// 参数非法:降级为空问题(宿主渲染兜底,不阻断)
|
|
29
29
|
}
|
|
30
|
+
// ADR-043:题身份在此**单点填入**(工具调用 id 即题身份),下游一律透传不自造。
|
|
31
|
+
// 批内第 i 题 ↔ askCalls[i].id,与 AgentLoop.pendingAsk.toolCallIds 同序同源。
|
|
30
32
|
return {
|
|
31
33
|
slot: 'ask',
|
|
34
|
+
askCallId: tc.id,
|
|
32
35
|
question,
|
|
33
36
|
...(options && options.length > 0 ? { options } : {}),
|
|
34
37
|
...(allowCustom !== undefined ? { allowCustom } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolCallHelpers.js","sourceRoot":"","sources":["../../../src/agent/managers/toolCallHelpers.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolCallHelpers.js","sourceRoot":"","sources":["../../../src/agent/managers/toolCallHelpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* 会话仅持有 roundIds,不再另存扁平消息列表。
|
|
7
7
|
*/
|
|
8
8
|
import type { ISessionStore, SessionMessage } from '../memory/sessionStore.js';
|
|
9
|
+
import type { ImageRef } from '../llm/provider.js';
|
|
9
10
|
import type { InteractiveInputKind, IRoundStore, RoundEvidenceEvent } from '../memory/roundStore.js';
|
|
10
11
|
/** 分叉结果(round-based 模式) */
|
|
11
12
|
export interface ForkResult {
|
|
@@ -16,6 +17,19 @@ export interface ForkResult {
|
|
|
16
17
|
/** 新会话的 Round ID 列表(已复制的指针) */
|
|
17
18
|
roundIds: string[];
|
|
18
19
|
}
|
|
20
|
+
/** 交互输入落档草稿(appendUser opts.group 元素):逐题展开的最小描述,id/timestamp/role 由 buildInteractiveInputs 统一装配 */
|
|
21
|
+
interface InteractiveInputDraft {
|
|
22
|
+
content: string;
|
|
23
|
+
kind: InteractiveInputKind;
|
|
24
|
+
/**
|
|
25
|
+
* 题身份(ADR-043 单源):来自内核 `AskQuestion.askCallId`(发起提问的工具调用 id)。
|
|
26
|
+
* 有则**直接作为落盘 id**(运行时行与盘面行同一身份,去重可靠配对);
|
|
27
|
+
* 无(supplement / 历史数据 / 未带身份的收场转化)则回退轮内序号公式。
|
|
28
|
+
*/
|
|
29
|
+
id?: string;
|
|
30
|
+
question?: string;
|
|
31
|
+
options?: string[];
|
|
32
|
+
}
|
|
19
33
|
/** 消息历史类 */
|
|
20
34
|
export declare class MessageHistory {
|
|
21
35
|
/** 会话存储(可选);注入则持久化,否则仅在内存保存(AgentLoop.messages[]) */
|
|
@@ -79,24 +93,59 @@ export declare class MessageHistory {
|
|
|
79
93
|
/**
|
|
80
94
|
* 追加 user 消息到当前会话(持久化失败不抛出,不阻塞对话)。
|
|
81
95
|
*
|
|
82
|
-
* Round-based 路径:roundStore 注入且 roundId 存在时,创建 pending Round
|
|
83
|
-
*
|
|
84
|
-
*
|
|
96
|
+
* Round-based 路径:roundStore 注入且 roundId 存在时,创建 pending Round 并**立即**归属当前会话
|
|
97
|
+
* (refCount 1 + appendRoundId,ADR-044「建轮即入会话」)。存在性由**创建**决定、不由**收场**决定——
|
|
98
|
+
* 未收场的轮同样可见、可删、可续跑;崩溃残留的 pending 轮不再是「GC 可清孤儿」,而是用户可见的
|
|
99
|
+
* 未完成轮(GC 只回收「连归属都没写成功」的残留,判据仍是 `refCount === 0`)。
|
|
85
100
|
*
|
|
86
101
|
* 交互输入:opts.interactive 为真且 roundId 对应轮已存在(prepare 建 pending 或
|
|
87
102
|
* 暂停已完成轮)时,不再创建/覆盖新轮——按序追加到该轮 interactiveInputs,保问答闭环不分裂。
|
|
88
103
|
*
|
|
89
104
|
* @param content 用户输入内容
|
|
90
105
|
* @param roundId 闭环节点轮次 ID(可选,交互输入必须携带 = appendUser 的 head roundId)
|
|
91
|
-
* @param opts 交互输入选项(interactive=是否交互归属;kind
|
|
92
|
-
*
|
|
106
|
+
* @param opts 交互输入选项(interactive=是否交互归属;kind/question/options=单条形态的
|
|
107
|
+
* 折叠块类型与所对 ask_user 提问原文/候选选项;group=整组形态,ASK-MULTI-Q-1:
|
|
108
|
+
* 作答/超时多题批次逐题落档,与崩溃收场转化同宽——提供时按序展开为 N 条,
|
|
109
|
+
* 单条形态等价于 1 元素组,同时提供以 group 为准)
|
|
93
110
|
*/
|
|
94
111
|
appendUser(content: string, roundId?: string, opts?: {
|
|
95
112
|
interactive?: boolean;
|
|
96
113
|
kind?: InteractiveInputKind;
|
|
97
114
|
question?: string;
|
|
98
115
|
options?: string[];
|
|
116
|
+
/** 整组交互输入(多题批次逐题落档);元素结构见 InteractiveInputDraft */
|
|
117
|
+
group?: ReadonlyArray<InteractiveInputDraft>;
|
|
118
|
+
/** 图片附件(可选,多模态输入);须显式透传到 `userMessage` 白名单与 SessionMessage,否则重启 / 切会话后图静默消失(方案 §7 P0-12) */
|
|
119
|
+
attachments?: readonly ImageRef[];
|
|
99
120
|
}): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* 把轮登记进当前会话的 `roundIds`(**ADR-044 归属登记单点**)。
|
|
123
|
+
*
|
|
124
|
+
* 语义:**建轮即登记**,收场不再登记。失败只记日志、不抛出——不阻塞对话。
|
|
125
|
+
*
|
|
126
|
+
* ⚠️ 失败的后果(ADR-044 第四刀写清,勿再按旧口径理解「退回登记前行为」):
|
|
127
|
+
* 轮已按 `refCount: 1` 落盘、但会话 `roundIds` 里没有它 ⇒ **不可见且不会被 GC 回收**
|
|
128
|
+
* (孤儿判据是 `refCount === 0`,它不满足)——即「静默保留、用户看不见也删不掉」。
|
|
129
|
+
* 这是**有意的取舍**:宁可留一个看不见的文件,也不静默删掉用户刚输入的内容;
|
|
130
|
+
* 触发条件 = 会话存储写入抛错(已 warn 日志可查)。
|
|
131
|
+
*
|
|
132
|
+
* 调用面只有三处,判据各不相同(勿扩张):
|
|
133
|
+
* - `appendUser` 建轮后(新轮必登记);
|
|
134
|
+
* - `appendAssistant` 兜底分支(轮原本不存在,新建即归属);
|
|
135
|
+
* - `attachUnownedRound`(存量 `refCount 0` 轮补登记,见其注释)。
|
|
136
|
+
*/
|
|
137
|
+
private registerRoundInSession;
|
|
138
|
+
/**
|
|
139
|
+
* 归属兜底(ADR-044 兼容面):`refCount === 0` 的轮 = **ADR-044 之前**落盘的存量
|
|
140
|
+
* (当时建轮写 0、收场才 +1),收场时补到 1 并登记进会话。
|
|
141
|
+
*
|
|
142
|
+
* 为什么不能直接删掉收场侧登记:存量半截轮(pending + refCount 0)在升级后仍必须可见,
|
|
143
|
+
* 删掉登记会让它们**永久不可见**——比「被 GC 回收」更糟(用户连删都删不到)。
|
|
144
|
+
* 新数据建轮即 refCount 1,本方法是 no-op。
|
|
145
|
+
*
|
|
146
|
+
* @returns 需要落盘的 Round(refCount 可能被补到 1);调用方负责 save。
|
|
147
|
+
*/
|
|
148
|
+
private attachUnownedRound;
|
|
100
149
|
/**
|
|
101
150
|
* 追加裁决证据到在途 Round(悬案取证轨;持久化失败不抛出,不阻塞对话)。
|
|
102
151
|
*
|
|
@@ -109,13 +158,32 @@ export declare class MessageHistory {
|
|
|
109
158
|
* @param ev 裁决证据事件(空响应 / 台账替身回显个案证据)
|
|
110
159
|
*/
|
|
111
160
|
appendEvidence(roundId: string, ev: RoundEvidenceEvent): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* 登记在途提问到当前轮(E2E-ASK-LOSS-1 写点;持久化失败不抛出)。
|
|
163
|
+
*
|
|
164
|
+
* 调用时机 = 提问挂起(yield paused)**之前**(装配层经 loop.onPendingQuestion 接线),
|
|
165
|
+
* 保证「AI 已提问」这一事实先于挂起落盘——挂起后崩溃,问题文本仍可从盘上恢复,
|
|
166
|
+
* 不再只活在进程内存(loop.pendingAsk)。
|
|
167
|
+
*
|
|
168
|
+
* 形状收窄:入参直取内核 `AskQuestion`,落盘只留 `question/options` 两个重放必需面
|
|
169
|
+
* (见 `RoundPendingAsk`——slot/allowCustom 是交互期参数,历史重放不消费)。
|
|
170
|
+
* 轮缺失(异常兜底):warn 后丢弃——挂起轮由 appendUser 建出,缺失即更上游已异常,
|
|
171
|
+
* 此处不伪造第二轨道补写(与 appendEvidence 同款降级语义)。
|
|
172
|
+
*
|
|
173
|
+
* @param roundId 闭环节点轮次 ID(loop.getCurrentRoundId(),与 appendUser 同源同值)
|
|
174
|
+
* @param questions 本批挂起提问(ask_user 解析产物,可多题)
|
|
175
|
+
*/
|
|
176
|
+
markPendingAsk(roundId: string, questions: readonly {
|
|
177
|
+
question: string;
|
|
178
|
+
options?: string[];
|
|
179
|
+
}[]): Promise<void>;
|
|
112
180
|
/**
|
|
113
181
|
* 追加 assistant 消息到当前会话(持久化失败不抛出)。
|
|
114
182
|
*
|
|
115
|
-
* Round-based 路径:完成 pending Round
|
|
116
|
-
* incrementRef
|
|
117
|
-
* RoundStore 取回补全;Round 不存在(异常兜底)时创建独立
|
|
118
|
-
*
|
|
183
|
+
* Round-based 路径:完成 pending Round 时**只改状态**——归属已在建轮时登记(ADR-044),
|
|
184
|
+
* 此处不再 incrementRef / appendRoundId;`refCount === 0` 的存量轮由 attachUnownedRound 补登记。
|
|
185
|
+
* pending 缓存丢失(跨重启)时从 RoundStore 取回补全;Round 不存在(异常兜底)时创建独立
|
|
186
|
+
* complete Round 并登记,保证用户消息不丢失。
|
|
119
187
|
*
|
|
120
188
|
* 闭环节点续写:同一 roundId 已存在 assistantMessage(跨暂停-续跑:
|
|
121
189
|
* 暂停轮已把 ask_user 提问/中断半截落为 assistantMessage)时,旧段入 assistantLog,
|
|
@@ -126,10 +194,12 @@ export declare class MessageHistory {
|
|
|
126
194
|
/**
|
|
127
195
|
* 中断轮收场:把「未正常完成」的轮升级为正常 stop turn 并入会话。
|
|
128
196
|
*
|
|
129
|
-
* 语义定案(step-atomic-persistence.md
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
197
|
+
* 语义定案(step-atomic-persistence.md §一·五):崩溃残留轮 = 等同于用户点「停止」的正常 turn
|
|
198
|
+
* ——可删、入会话 roundIds、作后续上下文,**不是**半成品草稿/孤儿。
|
|
199
|
+
* ⚠️ ADR-044 起:新建轮**建轮即入会话**,故本方法的「入会话」动作只对**存量无归属轮**
|
|
200
|
+
* (`refCount === 0`,ADR-044 之前落盘或建轮时登记失败)生效,见 attachUnownedRound。
|
|
201
|
+
* 宿主经 `IRoundStore.listInterruptedRecent` 打捞后再调本方法完成收场,恢复为普通 turn
|
|
202
|
+
* 渲染无需特殊草稿卡。
|
|
133
203
|
*
|
|
134
204
|
* **两个调用时机**:
|
|
135
205
|
* 1. 崩溃/断电后重启——宿主 `chatPanel.upgradeInterruptedRounds` 打捞后调用;
|
|
@@ -138,9 +208,8 @@ export declare class MessageHistory {
|
|
|
138
208
|
* 留下 pending 孤儿轮、须等下次重启才打捞(LLM 4xx 中断的长任务轮即此类)。
|
|
139
209
|
* 依据 = `memory/roundStore.ts` RoundStatus 文档「运行时失败不翻状态机,一律按中断处理」。
|
|
140
210
|
*
|
|
141
|
-
* 收场约定与 appendAssistant 同一真理源(
|
|
142
|
-
*
|
|
143
|
-
* 存储格式不变;
|
|
211
|
+
* 收场约定与 appendAssistant 同一真理源(status interrupted + completedAt + 存量无归属轮补登记
|
|
212
|
+
* 同走 attachUnownedRound;「已收场即跳过」仍用 `isRoundSettled`),Round schema / 存储格式不变;
|
|
144
213
|
* **有意不直接复用 appendAssistant**,两处差异:
|
|
145
214
|
* - appendAssistant 顶层跳过空内容 → 无文本中断轮无法收场;本方法允许多段「即使无
|
|
146
215
|
* assistantMessage 总结也按 stop 语义收场」(§一·五验收口径)。
|
|
@@ -183,4 +252,5 @@ export declare class MessageHistory {
|
|
|
183
252
|
*/
|
|
184
253
|
awaitPendingArchives(timeoutMs?: number): Promise<boolean>;
|
|
185
254
|
}
|
|
255
|
+
export {};
|
|
186
256
|
//# sourceMappingURL=messageHistory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messageHistory.d.ts","sourceRoot":"","sources":["../../src/agent/messageHistory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAe,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"messageHistory.d.ts","sourceRoot":"","sources":["../../src/agent/messageHistory.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,25 @@ import { logger } from '../logging/logger.js';
|
|
|
4
4
|
import { configError } from '../utils/errors.js';
|
|
5
5
|
import { safeSetTimeout } from '../utils/safeTimer.js';
|
|
6
6
|
import { nowIso, todayDate } from '../utils/time.js';
|
|
7
|
+
/**
|
|
8
|
+
* 整组交互输入 → 逐题 RoundInteractiveInput(ASK-MULTI-Q-1 单点装配):
|
|
9
|
+
* id 公式 / role / timestamp / question-options 摊开条件**只在此一处**。
|
|
10
|
+
* appendUser 交互分支(作答/超时整组)与 appendInterrupted 收场转化(unanswered 整组)
|
|
11
|
+
* 共用同一装配,两条消费路径从源头同宽。
|
|
12
|
+
*/
|
|
13
|
+
function buildInteractiveInputs(roundId, existingCount, drafts, timestamp) {
|
|
14
|
+
return drafts.map((d, i) => ({
|
|
15
|
+
// ADR-043:题身份优先(内核 toolCallId,跨重启恒定 ⇒ 运行时与重放可配对);
|
|
16
|
+
// 缺省回退轮内序号(supplement 等无题身份来源的路径,行为与历史一致)。
|
|
17
|
+
id: d.id ?? `msg-${roundId}-input-${existingCount + i + 1}`,
|
|
18
|
+
role: 'user',
|
|
19
|
+
content: d.content,
|
|
20
|
+
timestamp,
|
|
21
|
+
kind: d.kind,
|
|
22
|
+
...(d.question ? { question: d.question } : {}),
|
|
23
|
+
...(d.options && d.options.length > 0 ? { options: d.options } : {}),
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
7
26
|
/** 消息历史类 */
|
|
8
27
|
export class MessageHistory {
|
|
9
28
|
sessionStore;
|
|
@@ -173,17 +192,20 @@ export class MessageHistory {
|
|
|
173
192
|
/**
|
|
174
193
|
* 追加 user 消息到当前会话(持久化失败不抛出,不阻塞对话)。
|
|
175
194
|
*
|
|
176
|
-
* Round-based 路径:roundStore 注入且 roundId 存在时,创建 pending Round
|
|
177
|
-
*
|
|
178
|
-
*
|
|
195
|
+
* Round-based 路径:roundStore 注入且 roundId 存在时,创建 pending Round 并**立即**归属当前会话
|
|
196
|
+
* (refCount 1 + appendRoundId,ADR-044「建轮即入会话」)。存在性由**创建**决定、不由**收场**决定——
|
|
197
|
+
* 未收场的轮同样可见、可删、可续跑;崩溃残留的 pending 轮不再是「GC 可清孤儿」,而是用户可见的
|
|
198
|
+
* 未完成轮(GC 只回收「连归属都没写成功」的残留,判据仍是 `refCount === 0`)。
|
|
179
199
|
*
|
|
180
200
|
* 交互输入:opts.interactive 为真且 roundId 对应轮已存在(prepare 建 pending 或
|
|
181
201
|
* 暂停已完成轮)时,不再创建/覆盖新轮——按序追加到该轮 interactiveInputs,保问答闭环不分裂。
|
|
182
202
|
*
|
|
183
203
|
* @param content 用户输入内容
|
|
184
204
|
* @param roundId 闭环节点轮次 ID(可选,交互输入必须携带 = appendUser 的 head roundId)
|
|
185
|
-
* @param opts 交互输入选项(interactive=是否交互归属;kind
|
|
186
|
-
*
|
|
205
|
+
* @param opts 交互输入选项(interactive=是否交互归属;kind/question/options=单条形态的
|
|
206
|
+
* 折叠块类型与所对 ask_user 提问原文/候选选项;group=整组形态,ASK-MULTI-Q-1:
|
|
207
|
+
* 作答/超时多题批次逐题落档,与崩溃收场转化同宽——提供时按序展开为 N 条,
|
|
208
|
+
* 单条形态等价于 1 元素组,同时提供以 group 为准)
|
|
187
209
|
*/
|
|
188
210
|
async appendUser(content, roundId, opts) {
|
|
189
211
|
const message = {
|
|
@@ -191,6 +213,7 @@ export class MessageHistory {
|
|
|
191
213
|
content,
|
|
192
214
|
timestamp: nowIso(),
|
|
193
215
|
...(roundId ? { roundId } : {}),
|
|
216
|
+
...(opts?.attachments ? { attachments: opts.attachments } : {}),
|
|
194
217
|
};
|
|
195
218
|
// 会话日期锚点不随输入漂移:currentDate 由显式加载/新建决定,跨天续聊沿用原会话日期
|
|
196
219
|
// Round-based 路径:roundStore 注入且有 roundId 时写入
|
|
@@ -200,28 +223,42 @@ export class MessageHistory {
|
|
|
200
223
|
if (opts?.interactive) {
|
|
201
224
|
const existing = this.roundStore.getById(roundId) ?? this.pendingRounds.get(roundId);
|
|
202
225
|
if (existing) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
// 单条形态 = 退化 1 元素组:一条装配路径承载两种入参(无分叉实现)
|
|
227
|
+
const drafts = opts.group ?? [
|
|
228
|
+
{
|
|
229
|
+
content,
|
|
230
|
+
kind: opts.kind ?? 'supplement',
|
|
231
|
+
...(opts.question ? { question: opts.question } : {}),
|
|
232
|
+
...(opts.options && opts.options.length > 0 ? { options: opts.options } : {}),
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
const inputs = buildInteractiveInputs(roundId, existing.interactiveInputs?.length ?? 0, drafts, message.timestamp);
|
|
236
|
+
// 作答/超时记录落盘的**同一次 save 内**原子清 pendingAsk(生命周期不变量的清除点,
|
|
237
|
+
// 见 RoundPendingAsk 注释):提问已被消费(回答回填或超时收场),残留即脏数据——
|
|
238
|
+
// 若留到收场才清,「已答 + 未答」双记录窗口会打开(appendInterrupted 会把残留
|
|
239
|
+
// pendingAsk 再转化成重复的 unanswered 条目)。supplement 与提问无关,不动该字段。
|
|
240
|
+
// 判据含 unanswered(ASK-MULTI-Q-1):批次内全跳过(逐题均 unanswered)同样代表
|
|
241
|
+
// 快照已被 takeAnsweredAsk 消费——不清则收场转化二次记账。
|
|
242
|
+
// ⚠️ 勿在此点之前另加「提前清盘」层:答案此刻**尚未落盘**(`AgentLoop.appendToolMessage`
|
|
243
|
+
// 只 push 进程内 `loop.messages`,`Round` 结构不承载 tool 消息),二者之间不存在
|
|
244
|
+
// 「答案已持久而 pendingAsk 未清」的中间态 ⇒ 提前清的唯一净效应是把崩溃后一条
|
|
245
|
+
// 诚实的 unanswered 记录换成「该题在盘面彻底消失」(2026-10-31 实测,据此回滚)。
|
|
246
|
+
const askConsumed = drafts.some((d) => d.kind === 'question-answer' || d.kind === 'timeout' || d.kind === 'unanswered');
|
|
213
247
|
const updated = {
|
|
214
248
|
...existing,
|
|
215
|
-
interactiveInputs: [...(existing.interactiveInputs ?? []),
|
|
249
|
+
interactiveInputs: [...(existing.interactiveInputs ?? []), ...inputs],
|
|
250
|
+
...(askConsumed ? { pendingAsk: undefined } : {}),
|
|
216
251
|
};
|
|
217
252
|
this.roundStore.save(updated);
|
|
218
253
|
this.pendingRounds.set(roundId, updated);
|
|
219
|
-
logger.debug({ roundId,
|
|
254
|
+
logger.debug({ roundId, count: inputs.length, firstKind: inputs[0]?.kind }, 'appendUser: 交互输入归属问答闭环');
|
|
220
255
|
return;
|
|
221
256
|
}
|
|
222
257
|
// 轮不存在(异常兜底):降级为普通 pending Round(内容不丢失,GC 兜底)
|
|
223
258
|
logger.warn({ roundId }, 'appendUser: 交互输入对应轮缺失,降级为新建 pending Round');
|
|
224
259
|
}
|
|
260
|
+
// 重复 appendUser 到同一 roundId(异常路径)不重复登记,防 roundIds 堆叠
|
|
261
|
+
const preexisting = this.roundStore.getById(roundId) ?? this.pendingRounds.get(roundId);
|
|
225
262
|
const pendingRound = {
|
|
226
263
|
id: roundId,
|
|
227
264
|
userMessage: {
|
|
@@ -229,15 +266,21 @@ export class MessageHistory {
|
|
|
229
266
|
role: 'user',
|
|
230
267
|
content,
|
|
231
268
|
timestamp: message.timestamp,
|
|
269
|
+
...(opts?.attachments ? { attachments: opts.attachments } : {}),
|
|
232
270
|
},
|
|
233
271
|
status: 'pending',
|
|
234
272
|
createdAt: message.timestamp,
|
|
235
|
-
//
|
|
236
|
-
refCount
|
|
273
|
+
// 🔴 ADR-044 建轮即入会话:存在性由「创建」决定,不由「收场」决定。
|
|
274
|
+
// 创建它的会话立即持有引用(refCount 1)⇒ 会话视图立即可见、可删、可续跑。
|
|
275
|
+
// `save()` 会用 round.refCount 重建索引条目(workspaceRoundStore.save),故此处**不再**调
|
|
276
|
+
// incrementRef——那既二次落盘,又让「建轮」与「收场」两处并改写同一个计数(两个真相源)。
|
|
277
|
+
refCount: 1,
|
|
237
278
|
};
|
|
238
279
|
this.roundStore.save(pendingRound);
|
|
239
280
|
this.pendingRounds.set(roundId, pendingRound);
|
|
240
|
-
|
|
281
|
+
if (!preexisting)
|
|
282
|
+
this.registerRoundInSession(roundId);
|
|
283
|
+
logger.debug({ roundId }, 'appendUser: pending Round created(已归属当前会话)');
|
|
241
284
|
}
|
|
242
285
|
catch (err) {
|
|
243
286
|
logger.warn({ err, roundId }, 'appendUser: Round-based 写入失败');
|
|
@@ -245,6 +288,48 @@ export class MessageHistory {
|
|
|
245
288
|
}
|
|
246
289
|
logger.debug({ role: message.role, session: this.currentSessionName }, 'appendUser');
|
|
247
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* 把轮登记进当前会话的 `roundIds`(**ADR-044 归属登记单点**)。
|
|
293
|
+
*
|
|
294
|
+
* 语义:**建轮即登记**,收场不再登记。失败只记日志、不抛出——不阻塞对话。
|
|
295
|
+
*
|
|
296
|
+
* ⚠️ 失败的后果(ADR-044 第四刀写清,勿再按旧口径理解「退回登记前行为」):
|
|
297
|
+
* 轮已按 `refCount: 1` 落盘、但会话 `roundIds` 里没有它 ⇒ **不可见且不会被 GC 回收**
|
|
298
|
+
* (孤儿判据是 `refCount === 0`,它不满足)——即「静默保留、用户看不见也删不掉」。
|
|
299
|
+
* 这是**有意的取舍**:宁可留一个看不见的文件,也不静默删掉用户刚输入的内容;
|
|
300
|
+
* 触发条件 = 会话存储写入抛错(已 warn 日志可查)。
|
|
301
|
+
*
|
|
302
|
+
* 调用面只有三处,判据各不相同(勿扩张):
|
|
303
|
+
* - `appendUser` 建轮后(新轮必登记);
|
|
304
|
+
* - `appendAssistant` 兜底分支(轮原本不存在,新建即归属);
|
|
305
|
+
* - `attachUnownedRound`(存量 `refCount 0` 轮补登记,见其注释)。
|
|
306
|
+
*/
|
|
307
|
+
registerRoundInSession(roundId) {
|
|
308
|
+
const sessionId = this.currentSessionName;
|
|
309
|
+
try {
|
|
310
|
+
this.sessionStore?.appendRoundId(sessionId, roundId);
|
|
311
|
+
logger.debug({ roundId, sessionId }, 'appendRoundId: 轮已归属当前会话');
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
logger.warn({ err, roundId, sessionId }, 'appendRoundId: 归属登记失败(轮不可见,不阻断)');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 归属兜底(ADR-044 兼容面):`refCount === 0` 的轮 = **ADR-044 之前**落盘的存量
|
|
319
|
+
* (当时建轮写 0、收场才 +1),收场时补到 1 并登记进会话。
|
|
320
|
+
*
|
|
321
|
+
* 为什么不能直接删掉收场侧登记:存量半截轮(pending + refCount 0)在升级后仍必须可见,
|
|
322
|
+
* 删掉登记会让它们**永久不可见**——比「被 GC 回收」更糟(用户连删都删不到)。
|
|
323
|
+
* 新数据建轮即 refCount 1,本方法是 no-op。
|
|
324
|
+
*
|
|
325
|
+
* @returns 需要落盘的 Round(refCount 可能被补到 1);调用方负责 save。
|
|
326
|
+
*/
|
|
327
|
+
attachUnownedRound(round) {
|
|
328
|
+
if (round.refCount > 0)
|
|
329
|
+
return round;
|
|
330
|
+
this.registerRoundInSession(round.id);
|
|
331
|
+
return { ...round, refCount: 1 };
|
|
332
|
+
}
|
|
248
333
|
/**
|
|
249
334
|
* 追加裁决证据到在途 Round(悬案取证轨;持久化失败不抛出,不阻塞对话)。
|
|
250
335
|
*
|
|
@@ -274,13 +359,50 @@ export class MessageHistory {
|
|
|
274
359
|
logger.warn({ err, roundId }, 'appendEvidence: 写入失败');
|
|
275
360
|
}
|
|
276
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* 登记在途提问到当前轮(E2E-ASK-LOSS-1 写点;持久化失败不抛出)。
|
|
364
|
+
*
|
|
365
|
+
* 调用时机 = 提问挂起(yield paused)**之前**(装配层经 loop.onPendingQuestion 接线),
|
|
366
|
+
* 保证「AI 已提问」这一事实先于挂起落盘——挂起后崩溃,问题文本仍可从盘上恢复,
|
|
367
|
+
* 不再只活在进程内存(loop.pendingAsk)。
|
|
368
|
+
*
|
|
369
|
+
* 形状收窄:入参直取内核 `AskQuestion`,落盘只留 `question/options` 两个重放必需面
|
|
370
|
+
* (见 `RoundPendingAsk`——slot/allowCustom 是交互期参数,历史重放不消费)。
|
|
371
|
+
* 轮缺失(异常兜底):warn 后丢弃——挂起轮由 appendUser 建出,缺失即更上游已异常,
|
|
372
|
+
* 此处不伪造第二轨道补写(与 appendEvidence 同款降级语义)。
|
|
373
|
+
*
|
|
374
|
+
* @param roundId 闭环节点轮次 ID(loop.getCurrentRoundId(),与 appendUser 同源同值)
|
|
375
|
+
* @param questions 本批挂起提问(ask_user 解析产物,可多题)
|
|
376
|
+
*/
|
|
377
|
+
async markPendingAsk(roundId, questions) {
|
|
378
|
+
if (!this.roundStore || !roundId || questions.length === 0)
|
|
379
|
+
return;
|
|
380
|
+
try {
|
|
381
|
+
const existing = this.roundStore.getById(roundId) ?? this.pendingRounds.get(roundId);
|
|
382
|
+
if (!existing) {
|
|
383
|
+
logger.warn({ roundId }, 'markPendingAsk: 对应轮缺失,在途提问不落盘');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const snapshot = questions.map((q) => ({
|
|
387
|
+
question: q.question,
|
|
388
|
+
...(q.options && q.options.length > 0 ? { options: [...q.options] } : {}),
|
|
389
|
+
}));
|
|
390
|
+
const updated = { ...existing, pendingAsk: snapshot };
|
|
391
|
+
this.roundStore.save(updated);
|
|
392
|
+
this.pendingRounds.set(roundId, updated);
|
|
393
|
+
logger.debug({ roundId, count: snapshot.length }, 'markPendingAsk: 在途提问已落盘');
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
logger.warn({ err, roundId }, 'markPendingAsk: 写入失败');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
277
399
|
/**
|
|
278
400
|
* 追加 assistant 消息到当前会话(持久化失败不抛出)。
|
|
279
401
|
*
|
|
280
|
-
* Round-based 路径:完成 pending Round
|
|
281
|
-
* incrementRef
|
|
282
|
-
* RoundStore 取回补全;Round 不存在(异常兜底)时创建独立
|
|
283
|
-
*
|
|
402
|
+
* Round-based 路径:完成 pending Round 时**只改状态**——归属已在建轮时登记(ADR-044),
|
|
403
|
+
* 此处不再 incrementRef / appendRoundId;`refCount === 0` 的存量轮由 attachUnownedRound 补登记。
|
|
404
|
+
* pending 缓存丢失(跨重启)时从 RoundStore 取回补全;Round 不存在(异常兜底)时创建独立
|
|
405
|
+
* complete Round 并登记,保证用户消息不丢失。
|
|
284
406
|
*
|
|
285
407
|
* 闭环节点续写:同一 roundId 已存在 assistantMessage(跨暂停-续跑:
|
|
286
408
|
* 暂停轮已把 ask_user 提问/中断半截落为 assistantMessage)时,旧段入 assistantLog,
|
|
@@ -324,6 +446,9 @@ export class MessageHistory {
|
|
|
324
446
|
assistantLog: assistantLog && assistantLog.length > 0 ? assistantLog : undefined,
|
|
325
447
|
status: 'complete',
|
|
326
448
|
completedAt: message.timestamp,
|
|
449
|
+
// 防御清残留(正常完成轮不该有在途提问):作答记录落盘时已原子清(appendUser
|
|
450
|
+
// 交互分支),此处清异常窗口残留;终态轮不携带 pendingAsk(RoundPendingAsk 生命周期不变量)
|
|
451
|
+
pendingAsk: undefined,
|
|
327
452
|
};
|
|
328
453
|
this.pendingRounds.delete(roundId);
|
|
329
454
|
}
|
|
@@ -331,25 +456,12 @@ export class MessageHistory {
|
|
|
331
456
|
logger.warn({ roundId }, 'appendAssistant: Round not found in RoundStore');
|
|
332
457
|
}
|
|
333
458
|
if (completed) {
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
if (!isReappend) {
|
|
341
|
-
this.roundStore.incrementRef(roundId);
|
|
342
|
-
const sessionId = this.currentSessionName;
|
|
343
|
-
try {
|
|
344
|
-
this.sessionStore?.appendRoundId(sessionId, roundId);
|
|
345
|
-
}
|
|
346
|
-
catch (err) {
|
|
347
|
-
logger.warn({ err, sessionId, roundId }, 'appendAssistant: appendRoundId 失败');
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
logger.debug({ roundId, status: existing?.status }, 'appendAssistant: 闭环节点续写段但不重复登记会话引用');
|
|
352
|
-
}
|
|
459
|
+
// 🔴 ADR-044:归属已在**建轮**时登记(appendUser),收场只改状态——不再登记、不再加计数。
|
|
460
|
+
// 仅当轮是「存量无归属」(refCount 0,ADR-044 之前落盘)时由 attachUnownedRound 补登记;
|
|
461
|
+
// 直接删掉收场侧登记会让存量半截轮永久不可见,比「不可回收」更糟。
|
|
462
|
+
// 由此 `isRoundSettled` 不再承担「防重复登记」职责(收场已不登记),但保留给
|
|
463
|
+
// appendInterrupted 的「已收场即跳过」幂等判据——两处语义不同,勿合并。
|
|
464
|
+
this.roundStore.save(this.attachUnownedRound(completed));
|
|
353
465
|
logger.debug({ roundId }, 'appendAssistant: Round-based Round completed');
|
|
354
466
|
}
|
|
355
467
|
else {
|
|
@@ -374,12 +486,8 @@ export class MessageHistory {
|
|
|
374
486
|
refCount: 1,
|
|
375
487
|
};
|
|
376
488
|
this.roundStore.save(fallback);
|
|
377
|
-
try
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
catch (err) {
|
|
381
|
-
logger.warn({ err, roundId }, 'appendAssistant: 兜底 appendRoundId 失败');
|
|
382
|
-
}
|
|
489
|
+
// 兜底轮是「新建即归属」:走 ADR-044 单点(不再手写 try/catch 与 sessionStore 断言)。
|
|
490
|
+
this.registerRoundInSession(roundId);
|
|
383
491
|
}
|
|
384
492
|
}
|
|
385
493
|
logger.debug({ role: message.role, session: this.currentSessionName }, 'appendAssistant');
|
|
@@ -387,10 +495,12 @@ export class MessageHistory {
|
|
|
387
495
|
/**
|
|
388
496
|
* 中断轮收场:把「未正常完成」的轮升级为正常 stop turn 并入会话。
|
|
389
497
|
*
|
|
390
|
-
* 语义定案(step-atomic-persistence.md
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
498
|
+
* 语义定案(step-atomic-persistence.md §一·五):崩溃残留轮 = 等同于用户点「停止」的正常 turn
|
|
499
|
+
* ——可删、入会话 roundIds、作后续上下文,**不是**半成品草稿/孤儿。
|
|
500
|
+
* ⚠️ ADR-044 起:新建轮**建轮即入会话**,故本方法的「入会话」动作只对**存量无归属轮**
|
|
501
|
+
* (`refCount === 0`,ADR-044 之前落盘或建轮时登记失败)生效,见 attachUnownedRound。
|
|
502
|
+
* 宿主经 `IRoundStore.listInterruptedRecent` 打捞后再调本方法完成收场,恢复为普通 turn
|
|
503
|
+
* 渲染无需特殊草稿卡。
|
|
394
504
|
*
|
|
395
505
|
* **两个调用时机**:
|
|
396
506
|
* 1. 崩溃/断电后重启——宿主 `chatPanel.upgradeInterruptedRounds` 打捞后调用;
|
|
@@ -399,9 +509,8 @@ export class MessageHistory {
|
|
|
399
509
|
* 留下 pending 孤儿轮、须等下次重启才打捞(LLM 4xx 中断的长任务轮即此类)。
|
|
400
510
|
* 依据 = `memory/roundStore.ts` RoundStatus 文档「运行时失败不翻状态机,一律按中断处理」。
|
|
401
511
|
*
|
|
402
|
-
* 收场约定与 appendAssistant 同一真理源(
|
|
403
|
-
*
|
|
404
|
-
* 存储格式不变;
|
|
512
|
+
* 收场约定与 appendAssistant 同一真理源(status interrupted + completedAt + 存量无归属轮补登记
|
|
513
|
+
* 同走 attachUnownedRound;「已收场即跳过」仍用 `isRoundSettled`),Round schema / 存储格式不变;
|
|
405
514
|
* **有意不直接复用 appendAssistant**,两处差异:
|
|
406
515
|
* - appendAssistant 顶层跳过空内容 → 无文本中断轮无法收场;本方法允许多段「即使无
|
|
407
516
|
* assistantMessage 总结也按 stop 语义收场」(§一·五验收口径)。
|
|
@@ -435,6 +544,22 @@ export class MessageHistory {
|
|
|
435
544
|
}
|
|
436
545
|
// 有恢复文本才写 assistantMessage(§一·五:无 assistantMessage 总结也按 stop 语义收场)
|
|
437
546
|
const content = opts?.content?.trim() ?? '';
|
|
547
|
+
// 在途提问收场转化(E2E-ASK-LOSS-1):轮上残留 pendingAsk = 「问了但未答且作答记录
|
|
548
|
+
// 未落盘」的窗口仍开着——收场即窗口关闭,转化成 kind:'unanswered' 交互输入并清字段,
|
|
549
|
+
// 问题文本与候选选项从「进程内存」归位到「盘上重放面」(重放渲染复用 timeout 同型「未回答」行)。
|
|
550
|
+
// 消费点统一在本方法(打捞收场 / 运行期失败收场共用),宿主打捞链零改动。
|
|
551
|
+
const pendingAsk = existing.pendingAsk;
|
|
552
|
+
// 多题整组转化(每题一条 unanswered):pendingAsk 是整组快照,逐题保留 question/options——
|
|
553
|
+
// 崩溃轮的问题文本只在收场这一刻归档进 interactiveInputs,取首问即丢 Q2..N。
|
|
554
|
+
// 装配走 buildInteractiveInputs 单点(与 appendUser 作答/超时整组同宽,ASK-MULTI-Q-1)。
|
|
555
|
+
const unansweredInputs = pendingAsk?.length
|
|
556
|
+
? buildInteractiveInputs(roundId, existing.interactiveInputs?.length ?? 0, pendingAsk.map((q) => ({
|
|
557
|
+
content: LOOP_CONSTANTS.ASK_UNANSWERED_NOTICE,
|
|
558
|
+
kind: 'unanswered',
|
|
559
|
+
question: q.question,
|
|
560
|
+
options: q.options,
|
|
561
|
+
})), nowIso())
|
|
562
|
+
: [];
|
|
438
563
|
const completed = {
|
|
439
564
|
// 展开保留 userMessage / interactiveInputs / processEvents 等原字段
|
|
440
565
|
//(existing 取自 RoundStore 时含宿主检查点已落盘的 processEvents——store-first 优先保证)
|
|
@@ -449,20 +574,21 @@ export class MessageHistory {
|
|
|
449
574
|
},
|
|
450
575
|
}
|
|
451
576
|
: {}),
|
|
577
|
+
...(unansweredInputs.length
|
|
578
|
+
? {
|
|
579
|
+
interactiveInputs: [...(existing.interactiveInputs ?? []), ...unansweredInputs],
|
|
580
|
+
}
|
|
581
|
+
: {}),
|
|
452
582
|
status: 'interrupted',
|
|
453
583
|
completedAt: nowIso(),
|
|
584
|
+
// 生命周期不变量收口:转化后清字段(终态轮不残留 pendingAsk)
|
|
585
|
+
pendingAsk: undefined,
|
|
454
586
|
};
|
|
455
587
|
this.pendingRounds.delete(roundId);
|
|
456
|
-
|
|
457
|
-
//
|
|
458
|
-
this.roundStore.
|
|
588
|
+
// 🔴 ADR-044:归属已在建轮时登记,收场只改状态。**但存量无归属轮(refCount 0)必须补登记**——
|
|
589
|
+
// 跨重启打捞的残留轮正是这一类,省掉它会让打捞出来的轮在会话视图里看不见。
|
|
590
|
+
this.roundStore.save(this.attachUnownedRound(completed));
|
|
459
591
|
const sessionId = this.currentSessionName;
|
|
460
|
-
try {
|
|
461
|
-
this.sessionStore?.appendRoundId(sessionId, roundId);
|
|
462
|
-
}
|
|
463
|
-
catch (err) {
|
|
464
|
-
logger.warn({ err, sessionId, roundId }, 'appendInterrupted: appendRoundId 失败');
|
|
465
|
-
}
|
|
466
592
|
logger.info({ roundId, sessionId, hasAssistant: content.length > 0 }, 'appendInterrupted: 崩溃残留轮升级为正常 stop turn');
|
|
467
593
|
}
|
|
468
594
|
catch (err) {
|
|
@@ -502,18 +628,30 @@ export class MessageHistory {
|
|
|
502
628
|
if (!this.roundStore || !roundIds.length)
|
|
503
629
|
return [];
|
|
504
630
|
const messages = [];
|
|
631
|
+
const currentIdx = roundIds.length - 1;
|
|
505
632
|
for (const roundId of roundIds) {
|
|
506
633
|
const round = this.roundStore.getById(roundId);
|
|
507
634
|
if (!round) {
|
|
508
635
|
logger.warn({ roundId }, 'loadRoundBasedMessages: Round 不存在,跳过');
|
|
509
636
|
continue;
|
|
510
637
|
}
|
|
638
|
+
// 历史图片窗口衰减(方案 §5.12 / D22 / D23):只有最近 RETAIN_IMAGE_ROUNDS 轮内的
|
|
639
|
+
// attachments 带上模型上下文;更早轮次不带(content 不变,仍是那句占位)。
|
|
640
|
+
// 窗口 = `当前索引 − 消息索引`,纯计算、零状态。
|
|
641
|
+
// 注意该常量是**四个消费点同值**(见 LOOP_CONSTANTS.RETAIN_IMAGE_ROUNDS 的注释):
|
|
642
|
+
// 此处窗口衰减 + 两个 `loadMessages` 实现的同判据衰减 + 宿主会话加载期预读
|
|
643
|
+
// (`hosts/memora-vscode/.../imageCache.ts` 的 `preloadSession`),
|
|
644
|
+
// 拆分条件的完整论证见该常量注释。
|
|
645
|
+
const idx = roundIds.indexOf(roundId);
|
|
646
|
+
const withImages = currentIdx - idx < LOOP_CONSTANTS.RETAIN_IMAGE_ROUNDS;
|
|
647
|
+
const userAttachments = withImages ? round.userMessage.attachments : undefined;
|
|
511
648
|
// User message
|
|
512
649
|
messages.push({
|
|
513
650
|
role: round.userMessage.role,
|
|
514
651
|
content: round.userMessage.content,
|
|
515
652
|
roundId: round.id,
|
|
516
653
|
timestamp: round.userMessage.timestamp,
|
|
654
|
+
...(userAttachments ? { attachments: userAttachments } : {}),
|
|
517
655
|
});
|
|
518
656
|
// Assistant message(如果存在)
|
|
519
657
|
if (round.assistantMessage) {
|