@yeaft/webchat-agent 1.0.69 → 1.0.70

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": "@yeaft/webchat-agent",
3
- "version": "1.0.69",
3
+ "version": "1.0.70",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -47,6 +47,67 @@ function applySystem(language) {
47
47
  : 'You are the Apply stage of a dream pipeline. You rewrite a single scope\'s memory.md and summary.md based on recent session conversations. Reply with strict JSON only — no prose, no fences.';
48
48
  }
49
49
 
50
+ function isPrimarySessionTarget(target) {
51
+ return /^sessions\/[^/]+$/.test(String(target || ''));
52
+ }
53
+
54
+ function ensurePrimarySessionOutput({ target, memoryMd, summaryMd, sources, language }) {
55
+ if (!isPrimarySessionTarget(target)) return { memoryMd, summaryMd };
56
+ let memory = typeof memoryMd === 'string' ? memoryMd : '';
57
+ let summary = typeof summaryMd === 'string' ? summaryMd : '';
58
+ if (memory.trim() && summary.trim()) return { memoryMd: memory, summaryMd: summary };
59
+
60
+ const zh = String(language || '').toLowerCase().startsWith('zh');
61
+ const lines = sourceMessageLines(sources).slice(0, 6);
62
+ const fallbackSummary = summaryFromMemory(memory) || summaryFromLines(lines, zh);
63
+ if (!memory.trim()) {
64
+ memory = fallbackMemory(lines, zh);
65
+ }
66
+ if (!summary.trim()) {
67
+ summary = fallbackSummary;
68
+ }
69
+ return { memoryMd: memory, summaryMd: summary };
70
+ }
71
+
72
+ function sourceMessageLines(sources) {
73
+ const out = [];
74
+ for (const src of Array.isArray(sources) ? sources : []) {
75
+ for (const m of Array.isArray(src?.diff) ? src.diff : []) {
76
+ const body = truncateMessage(m?.body || m?.content || '');
77
+ if (!body) continue;
78
+ out.push(`${m?.role || 'message'}${m?.vpId ? `/${m.vpId}` : ''}: ${body}`);
79
+ }
80
+ }
81
+ return out;
82
+ }
83
+
84
+ function fallbackMemory(lines, zh) {
85
+ const title = zh ? '# 记忆' : '# Memory';
86
+ const lead = zh
87
+ ? '最近一次 Dream 运行保留了当前 session 上下文:'
88
+ : 'Latest Dream pass preserved current session context:';
89
+ const body = lines.length > 0
90
+ ? lines.map(line => `- ${line}`).join('\n')
91
+ : (zh ? '- 最近一次 Dream 运行没有可展开的消息内容。' : '- Latest Dream pass had no expandable message content.');
92
+ return `${title}\n\n${lead}\n${body}\n`;
93
+ }
94
+
95
+ function summaryFromMemory(memory) {
96
+ const line = String(memory || '')
97
+ .split(/\r?\n/)
98
+ .map(s => s.replace(/^#+\s*/, '').replace(/^[-*]\s*/, '').trim())
99
+ .find(Boolean);
100
+ return line ? line.slice(0, 500) : '';
101
+ }
102
+
103
+ function summaryFromLines(lines, zh) {
104
+ const first = lines.find(Boolean);
105
+ if (!first) return zh
106
+ ? '最近一次 Dream 运行已记录当前 session 上下文。'
107
+ : 'Latest Dream pass recorded the current session context.';
108
+ return (zh ? '最近一次 Dream 运行记录了当前 session 上下文:' : 'Latest Dream pass recorded current session context: ') + first.slice(0, 360);
109
+ }
110
+
50
111
  /**
51
112
  * Build the UPDATE prompt body. Accepts the current scope state +
52
113
  * one or more `(sessionId, diff)` source blocks.
@@ -219,8 +280,15 @@ export async function applyMergedTarget(merged, opts) {
219
280
  if (!parsed || typeof parsed.memory_md !== 'string') {
220
281
  throw malformedJsonError(`apply: CREATE returned malformed JSON for ${merged.target}`, raw);
221
282
  }
222
- memoryMd = parsed.memory_md;
223
- summaryMd = typeof parsed.summary_md === 'string' ? parsed.summary_md : '';
283
+ const ensured = ensurePrimarySessionOutput({
284
+ target: merged.target,
285
+ memoryMd: parsed.memory_md,
286
+ summaryMd: typeof parsed.summary_md === 'string' ? parsed.summary_md : '',
287
+ sources: merged.sources,
288
+ language: opts.language,
289
+ });
290
+ memoryMd = ensured.memoryMd;
291
+ summaryMd = ensured.summaryMd;
224
292
  batchesUsed = 1;
225
293
  } else {
226
294
  // UPDATE — possibly batched.
@@ -248,8 +316,15 @@ export async function applyMergedTarget(merged, opts) {
248
316
  if (!parsed || typeof parsed.memory_md !== 'string') {
249
317
  throw malformedJsonError(`apply: UPDATE batch ${i} returned malformed JSON for ${merged.target}`, raw);
250
318
  }
251
- memoryMd = parsed.memory_md;
252
- if (typeof parsed.summary_md === 'string') summaryMd = parsed.summary_md;
319
+ const ensured = ensurePrimarySessionOutput({
320
+ target: merged.target,
321
+ memoryMd: parsed.memory_md,
322
+ summaryMd: typeof parsed.summary_md === 'string' ? parsed.summary_md : summaryMd,
323
+ sources: batch,
324
+ language: opts.language,
325
+ });
326
+ memoryMd = ensured.memoryMd;
327
+ summaryMd = ensured.summaryMd;
253
328
  }
254
329
  batchesUsed = batches.length;
255
330
  }
@@ -263,15 +263,24 @@ export function parseJsonSafe(raw) {
263
263
  if (typeof raw !== 'string') return null;
264
264
  let s = raw.trim();
265
265
  // Strip markdown fences if present.
266
- const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/.exec(s);
266
+ const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(s);
267
267
  if (fenced) s = fenced[1].trim();
268
268
  try { return JSON.parse(s); }
269
- catch { /* try to recover the first {...} block */ }
270
- const start = s.indexOf('{');
271
- const end = s.lastIndexOf('}');
272
- if (start >= 0 && end > start) {
273
- try { return JSON.parse(s.slice(start, end + 1)); }
274
- catch { return null; }
269
+ catch { /* try to recover the first JSON block */ }
270
+
271
+ const objectStart = s.indexOf('{');
272
+ const objectEnd = s.lastIndexOf('}');
273
+ const arrayStart = s.indexOf('[');
274
+ const arrayEnd = s.lastIndexOf(']');
275
+ const candidates = [
276
+ { start: objectStart, end: objectEnd },
277
+ { start: arrayStart, end: arrayEnd },
278
+ ].filter(c => c.start >= 0 && c.end > c.start)
279
+ .sort((a, b) => a.start - b.start);
280
+
281
+ for (const c of candidates) {
282
+ try { return JSON.parse(s.slice(c.start, c.end + 1)); }
283
+ catch { /* try next candidate */ }
275
284
  }
276
285
  return null;
277
286
  }