dsh-whale-loli 0.5.4 → 0.5.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-whale-loli",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "鲸鱼娘 Agent 预设(三个)— 极简模式工具面 + 鲸鱼娘·小鱼人设 + 跨会话长期记忆 + 真正生效的 20 轮历史窗口。记忆分「固定记忆」(永久保留)/「滚动摘要」(每轮都记,最多 80 条 FIFO,超限每轮结束自动归档到 archive/<预设名>/)/「最近详细区」/「归档索引」四区,经提示变量注入(persona 为 complete:true,独立 section 会被宿主丢弃);history-window 用宿主表层替换真的裁掉 20 轮以前的历史,不调 LLM、不切断工具配对,并正确放行 agent/pre-step 的 waterfall。",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -156,6 +156,41 @@ export function shadowedNodes(session, startSeq, endSeq) {
156
156
  return nodes.filter((seq) => seq >= startSeq && seq <= endSeq);
157
157
  }
158
158
 
159
+ /** 提示条事件的唯一构造点。 */
160
+ function noticePayload(plan) {
161
+ return {
162
+ id: randomUUID(),
163
+ role: 'user',
164
+ content: [{ type: 'text', text: noticeText(plan.removedTurns, plan.keptTurns) }],
165
+ source: { kind: NOTICE_SOURCE_KIND },
166
+ };
167
+ }
168
+
169
+ /**
170
+ * 本地预检:把宿主 `dsh-session-format-v3-to-v4` 的规则在**写之前**自己先跑一遍。
171
+ *
172
+ * 为什么需要:连踩四次都是「写出去才被宿主拒收」,而拒收会直接打死整个回合
173
+ * (`turn/end {kind:'error'}`),用户看到的就是一句看不懂的校验错误。预检之后,
174
+ * 形状不对只会让我们**跳过这次裁剪**,对话继续。
175
+ *
176
+ * 校验项(对 `user/message`):
177
+ * 1. `assertOrdinaryMessage` —— `role !== 'developer'`、`content` 必须是数组
178
+ * 2. `assertV4MessageSources` —— `source` 是对象、`source.kind` 非空且 ≠ `'plugin'`
179
+ * 3. 本插件额外要求 `id` 非空(真实消息都有,便于溯源)
180
+ */
181
+ export function noticeIsValid(message) {
182
+ const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
183
+ if (!isObject(message)) return false;
184
+ if (typeof message.id !== 'string' || message.id.length === 0) return false;
185
+ if (message.role === 'developer') return false;
186
+ if (!Array.isArray(message.content)) return false;
187
+ const source = message.source;
188
+ if (!isObject(source)) return false;
189
+ if (typeof source.kind !== 'string' || source.kind.length === 0) return false;
190
+ if (source.kind === 'plugin') return false;
191
+ return true;
192
+ }
193
+
159
194
  /**
160
195
  * 执行裁剪。返回计划;无需裁剪返回 null。
161
196
  *
@@ -181,14 +216,13 @@ export function pruneHistory(agent, keepTurns = DEFAULT_KEEP_TURNS) {
181
216
  const shadowed = shadowedNodes(session, plan.startSeq, plan.endSeq);
182
217
  if (shadowed.length === 0) return null;
183
218
 
219
+ const message = noticePayload(plan);
220
+ // 预检不过就跳过:宁可这次不裁,也不写一个会被宿主拒收、拖垮整回合的事件。
221
+ if (!noticeIsValid(message)) return null;
222
+
184
223
  session.append(
185
224
  'user/message',
186
- {
187
- id: randomUUID(),
188
- role: 'user',
189
- content: [{ type: 'text', text: noticeText(plan.removedTurns, plan.keptTurns) }],
190
- source: { kind: NOTICE_SOURCE_KIND },
191
- },
225
+ message,
192
226
  {
193
227
  surfaceOp: { op: 'replace', startSeq: plan.startSeq, endSeq: plan.endSeq },
194
228
  sourceEventSeqs: shadowed,