@yeaft/webchat-agent 0.1.852 → 0.1.853
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 +1 -1
- package/unify/conversation/persist.js +18 -23
- package/unify/engine.js +21 -7
package/package.json
CHANGED
|
@@ -450,20 +450,22 @@ export class ConversationStore {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
/**
|
|
453
|
-
*
|
|
453
|
+
* Rewrite the compact summary in place.
|
|
454
454
|
*
|
|
455
|
-
*
|
|
455
|
+
* Compact's semantics are "the running summary of everything older than
|
|
456
|
+
* the hot window". Each compact pass already received the previous
|
|
457
|
+
* summary text as input and produced a *new* cumulative summary — so
|
|
458
|
+
* we overwrite, we never append. Appending was the original behaviour
|
|
459
|
+
* (kept around as a diary), but the engine reads the whole file back
|
|
460
|
+
* into `<conversation_summary>` on every turn, so appending grows the
|
|
461
|
+
* per-turn prompt without bound until it defeats compaction itself.
|
|
462
|
+
*
|
|
463
|
+
* @param {string} summary — the new, complete summary to persist
|
|
456
464
|
*/
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (existsSync(this.#compactPath)) {
|
|
460
|
-
existing = readFileSync(this.#compactPath, 'utf8');
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const date = new Date().toISOString().split('T')[0];
|
|
464
|
-
const entry = `\n## ${date}\n\n${summary}\n`;
|
|
465
|
+
replaceCompactSummary(summary) {
|
|
466
|
+
if (typeof summary !== 'string' || !summary) return;
|
|
465
467
|
try {
|
|
466
|
-
writeFileSync(this.#compactPath,
|
|
468
|
+
writeFileSync(this.#compactPath, summary, { encoding: 'utf8', mode: 0o644 });
|
|
467
469
|
} catch (err) {
|
|
468
470
|
if (isPermissionError(err)) {
|
|
469
471
|
if (!_permissionWarned) {
|
|
@@ -539,26 +541,19 @@ export class ConversationStore {
|
|
|
539
541
|
}
|
|
540
542
|
|
|
541
543
|
/**
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* but isolated to one file per (group, vp).
|
|
544
|
+
* Rewrite a per-(groupId, vpId) compact summary in place. See
|
|
545
|
+
* `replaceCompactSummary` for the rationale — same reason, scoped file.
|
|
545
546
|
*
|
|
546
547
|
* @param {string} groupId
|
|
547
548
|
* @param {string} vpId
|
|
548
549
|
* @param {string} summary
|
|
549
550
|
*/
|
|
550
|
-
|
|
551
|
+
replaceCompactSummaryFor(groupId, vpId, summary) {
|
|
552
|
+
if (typeof summary !== 'string' || !summary) return;
|
|
551
553
|
const path = this.#scopedCompactPath(groupId, vpId);
|
|
552
554
|
if (!path) return;
|
|
553
|
-
let existing = '';
|
|
554
|
-
if (existsSync(path)) {
|
|
555
|
-
try { existing = readFileSync(path, 'utf8'); }
|
|
556
|
-
catch { existing = ''; }
|
|
557
|
-
}
|
|
558
|
-
const date = new Date().toISOString().split('T')[0];
|
|
559
|
-
const entry = `\n## ${date}\n\n${summary}\n`;
|
|
560
555
|
try {
|
|
561
|
-
writeFileSync(path,
|
|
556
|
+
writeFileSync(path, summary, { encoding: 'utf8', mode: 0o644 });
|
|
562
557
|
} catch (err) {
|
|
563
558
|
if (isPermissionError(err)) {
|
|
564
559
|
if (!_permissionWarned) {
|
package/unify/engine.js
CHANGED
|
@@ -1149,18 +1149,32 @@ export class Engine {
|
|
|
1149
1149
|
// a jarring locale flip mid-context.
|
|
1150
1150
|
const isZh = String(this.#config.language || '').toLowerCase().startsWith('zh');
|
|
1151
1151
|
const summariserSystem = isZh
|
|
1152
|
-
? '
|
|
1153
|
-
: 'You are a conversation summarizer.
|
|
1152
|
+
? '你是对话摘要器。下面包含「先前累计摘要」(可能为空)与「新待压缩对话」。请融合两者,输出一份「重写后的累计摘要」——不要分段罗列日期、不要保留 "## 2026-..." 等历史分节,直接产出一份连贯、可被下一轮直接重新注入 prompt 的摘要。保留关键决策、事实、上下文与人物意图。'
|
|
1153
|
+
: 'You are a conversation summarizer. The input contains a "previous cumulative summary" (may be empty) plus a "new conversation to absorb". Merge them into ONE rewritten cumulative summary — do NOT keep dated section headers or any historical log structure. Output a single coherent summary suitable to be re-injected into the next turn\'s prompt as-is. Preserve key decisions, facts, context, and intent.';
|
|
1154
1154
|
const summariserPromptPrefix = isZh ? '请概括:\n\n' : 'Summarize:\n\n';
|
|
1155
1155
|
|
|
1156
1156
|
const hooks = {
|
|
1157
1157
|
summarise: async () => {
|
|
1158
1158
|
try {
|
|
1159
|
+
// Read prior summary at call time, not at orchestrator setup,
|
|
1160
|
+
// so the merge always sees the freshest on-disk state even if
|
|
1161
|
+
// future orchestrator changes invoke summarise more than once.
|
|
1162
|
+
const priorSummary = this.#getCompactSummary() || '';
|
|
1163
|
+
const priorBlock = priorSummary
|
|
1164
|
+
? (isZh
|
|
1165
|
+
? `【先前累计摘要】\n${priorSummary}\n\n【新待压缩对话】\n`
|
|
1166
|
+
: `[Previous cumulative summary]\n${priorSummary}\n\n[New conversation to absorb]\n`)
|
|
1167
|
+
: '';
|
|
1159
1168
|
const result = await adapter.call({
|
|
1160
1169
|
model: fastConfig.model,
|
|
1161
1170
|
system: summariserSystem,
|
|
1162
|
-
messages: [{ role: 'user', content: `${summariserPromptPrefix}${toArchive.map(m => `[${m.role}] ${(m.content || '').slice(0, 500)}`).join('\n\n')}` }],
|
|
1163
|
-
|
|
1171
|
+
messages: [{ role: 'user', content: `${summariserPromptPrefix}${priorBlock}${toArchive.map(m => `[${m.role}] ${(m.content || '').slice(0, 500)}`).join('\n\n')}` }],
|
|
1172
|
+
// 10k output budget: the running summary is the engine's
|
|
1173
|
+
// long-term memory of cold turns, so it deserves room to
|
|
1174
|
+
// actually preserve detail. We rewrite-in-place each round,
|
|
1175
|
+
// so size stays bounded by maxTokens regardless of how many
|
|
1176
|
+
// compact passes have run.
|
|
1177
|
+
maxTokens: 10240,
|
|
1164
1178
|
});
|
|
1165
1179
|
return (result.text || '').trim();
|
|
1166
1180
|
} catch {
|
|
@@ -1204,10 +1218,10 @@ export class Engine {
|
|
|
1204
1218
|
conversationStore.moveToColdBatch(archiveIds);
|
|
1205
1219
|
}
|
|
1206
1220
|
if (out.compactSummary) {
|
|
1207
|
-
if (scoped && typeof conversationStore.
|
|
1208
|
-
conversationStore.
|
|
1221
|
+
if (scoped && typeof conversationStore.replaceCompactSummaryFor === 'function') {
|
|
1222
|
+
conversationStore.replaceCompactSummaryFor(this.#groupId, this.#vpId, out.compactSummary);
|
|
1209
1223
|
} else {
|
|
1210
|
-
conversationStore.
|
|
1224
|
+
conversationStore.replaceCompactSummary(out.compactSummary);
|
|
1211
1225
|
}
|
|
1212
1226
|
}
|
|
1213
1227
|
// Index update only makes sense for the legacy path that actually
|