@zooique/memora 5.2.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/agent/agent.d.ts +37 -0
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +53 -0
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/assembler.d.ts.map +1 -1
  7. package/dist/agent/assembler.js +7 -0
  8. package/dist/agent/assembler.js.map +1 -1
  9. package/dist/agent/constants.d.ts +22 -0
  10. package/dist/agent/constants.d.ts.map +1 -1
  11. package/dist/agent/constants.js +22 -0
  12. package/dist/agent/constants.js.map +1 -1
  13. package/dist/agent/loop.d.ts +11 -4
  14. package/dist/agent/loop.d.ts.map +1 -1
  15. package/dist/agent/loop.js +37 -13
  16. package/dist/agent/loop.js.map +1 -1
  17. package/dist/agent/managers/toolCallHelpers.d.ts.map +1 -1
  18. package/dist/agent/managers/toolCallHelpers.js +3 -0
  19. package/dist/agent/managers/toolCallHelpers.js.map +1 -1
  20. package/dist/agent/messageHistory.d.ts +83 -16
  21. package/dist/agent/messageHistory.d.ts.map +1 -1
  22. package/dist/agent/messageHistory.js +189 -65
  23. package/dist/agent/messageHistory.js.map +1 -1
  24. package/dist/agent/seed/orchestrator.d.ts.map +1 -1
  25. package/dist/agent/seed/orchestrator.js +45 -14
  26. package/dist/agent/seed/orchestrator.js.map +1 -1
  27. package/dist/agent/types.d.ts +9 -0
  28. package/dist/agent/types.d.ts.map +1 -1
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +6 -4
  32. package/dist/index.js.map +1 -1
  33. package/dist/memory/gcService.d.ts +8 -4
  34. package/dist/memory/gcService.d.ts.map +1 -1
  35. package/dist/memory/gcService.js.map +1 -1
  36. package/dist/memory/inMemoryRoundStore.d.ts +8 -3
  37. package/dist/memory/inMemoryRoundStore.d.ts.map +1 -1
  38. package/dist/memory/inMemoryRoundStore.js +19 -10
  39. package/dist/memory/inMemoryRoundStore.js.map +1 -1
  40. package/dist/memory/roundStore.d.ts +77 -11
  41. package/dist/memory/roundStore.d.ts.map +1 -1
  42. package/dist/memory/roundStore.js +18 -0
  43. package/dist/memory/roundStore.js.map +1 -1
  44. package/dist/utils/time.d.ts +24 -4
  45. package/dist/utils/time.d.ts.map +1 -1
  46. package/dist/utils/time.js +32 -4
  47. package/dist/utils/time.js.map +1 -1
  48. package/docs/memora-api-reference.md +1 -0
  49. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"toolCallHelpers.d.ts","sourceRoot":"","sources":["../../../src/agent/managers/toolCallHelpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"toolCallHelpers.d.ts","sourceRoot":"","sources":["../../../src/agent/managers/toolCallHelpers.ts"],"names":[],"mappings":"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"}
@@ -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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"}
@@ -17,6 +17,19 @@ export interface ForkResult {
17
17
  /** 新会话的 Round ID 列表(已复制的指针) */
18
18
  roundIds: string[];
19
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
+ }
20
33
  /** 消息历史类 */
21
34
  export declare class MessageHistory {
22
35
  /** 会话存储(可选);注入则持久化,否则仅在内存保存(AgentLoop.messages[]) */
@@ -80,26 +93,59 @@ export declare class MessageHistory {
80
93
  /**
81
94
  * 追加 user 消息到当前会话(持久化失败不抛出,不阻塞对话)。
82
95
  *
83
- * Round-based 路径:roundStore 注入且 roundId 存在时,创建 pending Round
84
- * 写入 RoundStore(refCount=0,未完成不登记会话)。**仅 complete 才 appendRoundId**——
85
- * 崩溃残留的 pending 轮是孤儿(refCount=0),可由 GC 清理,不污染会话视图。
96
+ * Round-based 路径:roundStore 注入且 roundId 存在时,创建 pending Round 并**立即**归属当前会话
97
+ * (refCount 1 + appendRoundId,ADR-044「建轮即入会话」)。存在性由**创建**决定、不由**收场**决定——
98
+ * 未收场的轮同样可见、可删、可续跑;崩溃残留的 pending 轮不再是「GC 可清孤儿」,而是用户可见的
99
+ * 未完成轮(GC 只回收「连归属都没写成功」的残留,判据仍是 `refCount === 0`)。
86
100
  *
87
101
  * 交互输入:opts.interactive 为真且 roundId 对应轮已存在(prepare 建 pending 或
88
102
  * 暂停已完成轮)时,不再创建/覆盖新轮——按序追加到该轮 interactiveInputs,保问答闭环不分裂。
89
103
  *
90
104
  * @param content 用户输入内容
91
105
  * @param roundId 闭环节点轮次 ID(可选,交互输入必须携带 = appendUser 的 head roundId)
92
- * @param opts 交互输入选项(interactive=是否交互归属;kind=折叠块类型文案;
93
- * question/options=该回答所对的 ask_user 提问原文与候选选项,随回答一并持久化)
106
+ * @param opts 交互输入选项(interactive=是否交互归属;kind/question/options=单条形态的
107
+ * 折叠块类型与所对 ask_user 提问原文/候选选项;group=整组形态,ASK-MULTI-Q-1:
108
+ * 作答/超时多题批次逐题落档,与崩溃收场转化同宽——提供时按序展开为 N 条,
109
+ * 单条形态等价于 1 元素组,同时提供以 group 为准)
94
110
  */
95
111
  appendUser(content: string, roundId?: string, opts?: {
96
112
  interactive?: boolean;
97
113
  kind?: InteractiveInputKind;
98
114
  question?: string;
99
115
  options?: string[];
116
+ /** 整组交互输入(多题批次逐题落档);元素结构见 InteractiveInputDraft */
117
+ group?: ReadonlyArray<InteractiveInputDraft>;
100
118
  /** 图片附件(可选,多模态输入);须显式透传到 `userMessage` 白名单与 SessionMessage,否则重启 / 切会话后图静默消失(方案 §7 P0-12) */
101
119
  attachments?: readonly ImageRef[];
102
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;
103
149
  /**
104
150
  * 追加裁决证据到在途 Round(悬案取证轨;持久化失败不抛出,不阻塞对话)。
105
151
  *
@@ -112,13 +158,32 @@ export declare class MessageHistory {
112
158
  * @param ev 裁决证据事件(空响应 / 台账替身回显个案证据)
113
159
  */
114
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>;
115
180
  /**
116
181
  * 追加 assistant 消息到当前会话(持久化失败不抛出)。
117
182
  *
118
- * Round-based 路径:完成 pending Round 时**才登记会话引用**——
119
- * incrementRef(refCount 0→1)+ appendRoundId。pending 缓存丢失(跨重启)时从
120
- * RoundStore 取回补全;Round 不存在(异常兜底)时创建独立 complete Round 并登记,
121
- * 保证用户消息不丢失。
183
+ * Round-based 路径:完成 pending Round 时**只改状态**——归属已在建轮时登记(ADR-044),
184
+ * 此处不再 incrementRef / appendRoundId;`refCount === 0` 的存量轮由 attachUnownedRound 补登记。
185
+ * pending 缓存丢失(跨重启)时从 RoundStore 取回补全;Round 不存在(异常兜底)时创建独立
186
+ * complete Round 并登记,保证用户消息不丢失。
122
187
  *
123
188
  * 闭环节点续写:同一 roundId 已存在 assistantMessage(跨暂停-续跑:
124
189
  * 暂停轮已把 ask_user 提问/中断半截落为 assistantMessage)时,旧段入 assistantLog,
@@ -129,10 +194,12 @@ export declare class MessageHistory {
129
194
  /**
130
195
  * 中断轮收场:把「未正常完成」的轮升级为正常 stop turn 并入会话。
131
196
  *
132
- * 语义定案(step-atomic-persistence.md §一·五):崩溃残留轮(pending/error + refCount=0 + 有已
133
- * 落盘 processEvents)= 等同于用户点「停止」的正常 turn——可删、入会话 roundIds、作后续上下文,
134
- * **不是**半成品草稿/孤儿。宿主经 `IRoundStore.listInterruptedRecent` 打捞后再调本方法完成
135
- * 「升级登记」,恢复为普通 turn 渲染无需特殊草稿卡。
197
+ * 语义定案(step-atomic-persistence.md §一·五):崩溃残留轮 = 等同于用户点「停止」的正常 turn
198
+ * ——可删、入会话 roundIds、作后续上下文,**不是**半成品草稿/孤儿。
199
+ * ⚠️ ADR-044 起:新建轮**建轮即入会话**,故本方法的「入会话」动作只对**存量无归属轮**
200
+ * (`refCount === 0`,ADR-044 之前落盘或建轮时登记失败)生效,见 attachUnownedRound。
201
+ * 宿主经 `IRoundStore.listInterruptedRecent` 打捞后再调本方法完成收场,恢复为普通 turn
202
+ * 渲染无需特殊草稿卡。
136
203
  *
137
204
  * **两个调用时机**:
138
205
  * 1. 崩溃/断电后重启——宿主 `chatPanel.upgradeInterruptedRounds` 打捞后调用;
@@ -141,9 +208,8 @@ export declare class MessageHistory {
141
208
  * 留下 pending 孤儿轮、须等下次重启才打捞(LLM 4xx 中断的长任务轮即此类)。
142
209
  * 依据 = `memory/roundStore.ts` RoundStatus 文档「运行时失败不翻状态机,一律按中断处理」。
143
210
  *
144
- * 收场约定与 appendAssistant 同一真理源(refCount 0→1 + appendRoundId + status interrupted +
145
- * completedAt + isReappend 防重复登记,两处终态判据同为 `isRoundSettled`),Round schema /
146
- * 存储格式不变;
211
+ * 收场约定与 appendAssistant 同一真理源(status interrupted + completedAt + 存量无归属轮补登记
212
+ * 同走 attachUnownedRound;「已收场即跳过」仍用 `isRoundSettled`),Round schema / 存储格式不变;
147
213
  * **有意不直接复用 appendAssistant**,两处差异:
148
214
  * - appendAssistant 顶层跳过空内容 → 无文本中断轮无法收场;本方法允许多段「即使无
149
215
  * assistantMessage 总结也按 stop 语义收场」(§一·五验收口径)。
@@ -186,4 +252,5 @@ export declare class MessageHistory {
186
252
  */
187
253
  awaitPendingArchives(timeoutMs?: number): Promise<boolean>;
188
254
  }
255
+ export {};
189
256
  //# sourceMappingURL=messageHistory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messageHistory.d.ts","sourceRoot":"","sources":["../../src/agent/messageHistory.ts"],"names":[],"mappings":"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"}
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
- * 写入 RoundStore(refCount=0,未完成不登记会话)。**仅 complete 才 appendRoundId**——
178
- * 崩溃残留的 pending 轮是孤儿(refCount=0),可由 GC 清理,不污染会话视图。
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
- * question/options=该回答所对的 ask_user 提问原文与候选选项,随回答一并持久化)
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 = {
@@ -201,28 +223,42 @@ export class MessageHistory {
201
223
  if (opts?.interactive) {
202
224
  const existing = this.roundStore.getById(roundId) ?? this.pendingRounds.get(roundId);
203
225
  if (existing) {
204
- const input = {
205
- id: `msg-${roundId}-input-${(existing.interactiveInputs?.length ?? 0) + 1}`,
206
- role: 'user',
207
- content,
208
- timestamp: message.timestamp,
209
- kind: opts.kind ?? 'supplement',
210
- // 提问原文/选项随回答落盘(question-answer 携带;supplement/缺省不落,旧数据向后兼容)
211
- ...(opts.question ? { question: opts.question } : {}),
212
- ...(opts.options && opts.options.length > 0 ? { options: opts.options } : {}),
213
- };
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');
214
247
  const updated = {
215
248
  ...existing,
216
- interactiveInputs: [...(existing.interactiveInputs ?? []), input],
249
+ interactiveInputs: [...(existing.interactiveInputs ?? []), ...inputs],
250
+ ...(askConsumed ? { pendingAsk: undefined } : {}),
217
251
  };
218
252
  this.roundStore.save(updated);
219
253
  this.pendingRounds.set(roundId, updated);
220
- logger.debug({ roundId, kind: input.kind }, 'appendUser: 交互输入归属问答闭环');
254
+ logger.debug({ roundId, count: inputs.length, firstKind: inputs[0]?.kind }, 'appendUser: 交互输入归属问答闭环');
221
255
  return;
222
256
  }
223
257
  // 轮不存在(异常兜底):降级为普通 pending Round(内容不丢失,GC 兜底)
224
258
  logger.warn({ roundId }, 'appendUser: 交互输入对应轮缺失,降级为新建 pending Round');
225
259
  }
260
+ // 重复 appendUser 到同一 roundId(异常路径)不重复登记,防 roundIds 堆叠
261
+ const preexisting = this.roundStore.getById(roundId) ?? this.pendingRounds.get(roundId);
226
262
  const pendingRound = {
227
263
  id: roundId,
228
264
  userMessage: {
@@ -234,12 +270,17 @@ export class MessageHistory {
234
270
  },
235
271
  status: 'pending',
236
272
  createdAt: message.timestamp,
237
- // 未完成轮不被任何会话引用(complete 时 incrementRef + appendRoundId)
238
- refCount: 0,
273
+ // 🔴 ADR-044 建轮即入会话:存在性由「创建」决定,不由「收场」决定。
274
+ // 创建它的会话立即持有引用(refCount 1)⇒ 会话视图立即可见、可删、可续跑。
275
+ // `save()` 会用 round.refCount 重建索引条目(workspaceRoundStore.save),故此处**不再**调
276
+ // incrementRef——那既二次落盘,又让「建轮」与「收场」两处并改写同一个计数(两个真相源)。
277
+ refCount: 1,
239
278
  };
240
279
  this.roundStore.save(pendingRound);
241
280
  this.pendingRounds.set(roundId, pendingRound);
242
- logger.debug({ roundId }, 'appendUser: Round-based pending Round created');
281
+ if (!preexisting)
282
+ this.registerRoundInSession(roundId);
283
+ logger.debug({ roundId }, 'appendUser: pending Round created(已归属当前会话)');
243
284
  }
244
285
  catch (err) {
245
286
  logger.warn({ err, roundId }, 'appendUser: Round-based 写入失败');
@@ -247,6 +288,48 @@ export class MessageHistory {
247
288
  }
248
289
  logger.debug({ role: message.role, session: this.currentSessionName }, 'appendUser');
249
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
+ }
250
333
  /**
251
334
  * 追加裁决证据到在途 Round(悬案取证轨;持久化失败不抛出,不阻塞对话)。
252
335
  *
@@ -276,13 +359,50 @@ export class MessageHistory {
276
359
  logger.warn({ err, roundId }, 'appendEvidence: 写入失败');
277
360
  }
278
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
+ }
279
399
  /**
280
400
  * 追加 assistant 消息到当前会话(持久化失败不抛出)。
281
401
  *
282
- * Round-based 路径:完成 pending Round 时**才登记会话引用**——
283
- * incrementRef(refCount 0→1)+ appendRoundId。pending 缓存丢失(跨重启)时从
284
- * RoundStore 取回补全;Round 不存在(异常兜底)时创建独立 complete Round 并登记,
285
- * 保证用户消息不丢失。
402
+ * Round-based 路径:完成 pending Round 时**只改状态**——归属已在建轮时登记(ADR-044),
403
+ * 此处不再 incrementRef / appendRoundId;`refCount === 0` 的存量轮由 attachUnownedRound 补登记。
404
+ * pending 缓存丢失(跨重启)时从 RoundStore 取回补全;Round 不存在(异常兜底)时创建独立
405
+ * complete Round 并登记,保证用户消息不丢失。
286
406
  *
287
407
  * 闭环节点续写:同一 roundId 已存在 assistantMessage(跨暂停-续跑:
288
408
  * 暂停轮已把 ask_user 提问/中断半截落为 assistantMessage)时,旧段入 assistantLog,
@@ -326,6 +446,9 @@ export class MessageHistory {
326
446
  assistantLog: assistantLog && assistantLog.length > 0 ? assistantLog : undefined,
327
447
  status: 'complete',
328
448
  completedAt: message.timestamp,
449
+ // 防御清残留(正常完成轮不该有在途提问):作答记录落盘时已原子清(appendUser
450
+ // 交互分支),此处清异常窗口残留;终态轮不携带 pendingAsk(RoundPendingAsk 生命周期不变量)
451
+ pendingAsk: undefined,
329
452
  };
330
453
  this.pendingRounds.delete(roundId);
331
454
  }
@@ -333,25 +456,12 @@ export class MessageHistory {
333
456
  logger.warn({ roundId }, 'appendAssistant: Round not found in RoundStore');
334
457
  }
335
458
  if (completed) {
336
- this.roundStore.save(completed);
337
- // 首次收场才登记会话引用(refCount 0→1 + 列表登记),崩溃残留 pending 保持孤儿可由 GC 清理。
338
- // 续写同一闭环节点(assistantLog:跨暂停-续跑多次 appendAssistant 到同一 roundId)不重复登记——
339
- // 否则 roundIds 同 id 重复堆叠、refCount 虚增,会话视图出现「一个问答闭环多次登记」
340
- // 判据 = isRoundSettled(非自写 'complete'):已是终态(含 interrupted)就不得再登记一次
341
- const isReappend = existing !== null && existing !== undefined && isRoundSettled(existing.status);
342
- if (!isReappend) {
343
- this.roundStore.incrementRef(roundId);
344
- const sessionId = this.currentSessionName;
345
- try {
346
- this.sessionStore?.appendRoundId(sessionId, roundId);
347
- }
348
- catch (err) {
349
- logger.warn({ err, sessionId, roundId }, 'appendAssistant: appendRoundId 失败');
350
- }
351
- }
352
- else {
353
- logger.debug({ roundId, status: existing?.status }, 'appendAssistant: 闭环节点续写段但不重复登记会话引用');
354
- }
459
+ // 🔴 ADR-044:归属已在**建轮**时登记(appendUser),收场只改状态——不再登记、不再加计数。
460
+ // 仅当轮是「存量无归属」(refCount 0,ADR-044 之前落盘)时由 attachUnownedRound 补登记;
461
+ // 直接删掉收场侧登记会让存量半截轮永久不可见,比「不可回收」更糟。
462
+ // 由此 `isRoundSettled` 不再承担「防重复登记」职责(收场已不登记),但保留给
463
+ // appendInterrupted 的「已收场即跳过」幂等判据——两处语义不同,勿合并。
464
+ this.roundStore.save(this.attachUnownedRound(completed));
355
465
  logger.debug({ roundId }, 'appendAssistant: Round-based Round completed');
356
466
  }
357
467
  else {
@@ -376,12 +486,8 @@ export class MessageHistory {
376
486
  refCount: 1,
377
487
  };
378
488
  this.roundStore.save(fallback);
379
- try {
380
- this.sessionStore.appendRoundId(this.currentSessionName, roundId);
381
- }
382
- catch (err) {
383
- logger.warn({ err, roundId }, 'appendAssistant: 兜底 appendRoundId 失败');
384
- }
489
+ // 兜底轮是「新建即归属」:走 ADR-044 单点(不再手写 try/catch 与 sessionStore 断言)。
490
+ this.registerRoundInSession(roundId);
385
491
  }
386
492
  }
387
493
  logger.debug({ role: message.role, session: this.currentSessionName }, 'appendAssistant');
@@ -389,10 +495,12 @@ export class MessageHistory {
389
495
  /**
390
496
  * 中断轮收场:把「未正常完成」的轮升级为正常 stop turn 并入会话。
391
497
  *
392
- * 语义定案(step-atomic-persistence.md §一·五):崩溃残留轮(pending/error + refCount=0 + 有已
393
- * 落盘 processEvents)= 等同于用户点「停止」的正常 turn——可删、入会话 roundIds、作后续上下文,
394
- * **不是**半成品草稿/孤儿。宿主经 `IRoundStore.listInterruptedRecent` 打捞后再调本方法完成
395
- * 「升级登记」,恢复为普通 turn 渲染无需特殊草稿卡。
498
+ * 语义定案(step-atomic-persistence.md §一·五):崩溃残留轮 = 等同于用户点「停止」的正常 turn
499
+ * ——可删、入会话 roundIds、作后续上下文,**不是**半成品草稿/孤儿。
500
+ * ⚠️ ADR-044 起:新建轮**建轮即入会话**,故本方法的「入会话」动作只对**存量无归属轮**
501
+ * (`refCount === 0`,ADR-044 之前落盘或建轮时登记失败)生效,见 attachUnownedRound。
502
+ * 宿主经 `IRoundStore.listInterruptedRecent` 打捞后再调本方法完成收场,恢复为普通 turn
503
+ * 渲染无需特殊草稿卡。
396
504
  *
397
505
  * **两个调用时机**:
398
506
  * 1. 崩溃/断电后重启——宿主 `chatPanel.upgradeInterruptedRounds` 打捞后调用;
@@ -401,9 +509,8 @@ export class MessageHistory {
401
509
  * 留下 pending 孤儿轮、须等下次重启才打捞(LLM 4xx 中断的长任务轮即此类)。
402
510
  * 依据 = `memory/roundStore.ts` RoundStatus 文档「运行时失败不翻状态机,一律按中断处理」。
403
511
  *
404
- * 收场约定与 appendAssistant 同一真理源(refCount 0→1 + appendRoundId + status interrupted +
405
- * completedAt + isReappend 防重复登记,两处终态判据同为 `isRoundSettled`),Round schema /
406
- * 存储格式不变;
512
+ * 收场约定与 appendAssistant 同一真理源(status interrupted + completedAt + 存量无归属轮补登记
513
+ * 同走 attachUnownedRound;「已收场即跳过」仍用 `isRoundSettled`),Round schema / 存储格式不变;
407
514
  * **有意不直接复用 appendAssistant**,两处差异:
408
515
  * - appendAssistant 顶层跳过空内容 → 无文本中断轮无法收场;本方法允许多段「即使无
409
516
  * assistantMessage 总结也按 stop 语义收场」(§一·五验收口径)。
@@ -437,6 +544,22 @@ export class MessageHistory {
437
544
  }
438
545
  // 有恢复文本才写 assistantMessage(§一·五:无 assistantMessage 总结也按 stop 语义收场)
439
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
+ : [];
440
563
  const completed = {
441
564
  // 展开保留 userMessage / interactiveInputs / processEvents 等原字段
442
565
  //(existing 取自 RoundStore 时含宿主检查点已落盘的 processEvents——store-first 优先保证)
@@ -451,20 +574,21 @@ export class MessageHistory {
451
574
  },
452
575
  }
453
576
  : {}),
577
+ ...(unansweredInputs.length
578
+ ? {
579
+ interactiveInputs: [...(existing.interactiveInputs ?? []), ...unansweredInputs],
580
+ }
581
+ : {}),
454
582
  status: 'interrupted',
455
583
  completedAt: nowIso(),
584
+ // 生命周期不变量收口:转化后清字段(终态轮不残留 pendingAsk)
585
+ pendingAsk: undefined,
456
586
  };
457
587
  this.pendingRounds.delete(roundId);
458
- this.roundStore.save(completed);
459
- // 收场登记:与 appendAssistant 同一约定——refCount 0→1 + 会话 roundIds 追加
460
- this.roundStore.incrementRef(roundId);
588
+ // 🔴 ADR-044:归属已在建轮时登记,收场只改状态。**但存量无归属轮(refCount 0)必须补登记**——
589
+ // 跨重启打捞的残留轮正是这一类,省掉它会让打捞出来的轮在会话视图里看不见。
590
+ this.roundStore.save(this.attachUnownedRound(completed));
461
591
  const sessionId = this.currentSessionName;
462
- try {
463
- this.sessionStore?.appendRoundId(sessionId, roundId);
464
- }
465
- catch (err) {
466
- logger.warn({ err, sessionId, roundId }, 'appendInterrupted: appendRoundId 失败');
467
- }
468
592
  logger.info({ roundId, sessionId, hasAssistant: content.length > 0 }, 'appendInterrupted: 崩溃残留轮升级为正常 stop turn');
469
593
  }
470
594
  catch (err) {