@yeaft/webchat-agent 0.1.797 → 0.1.799
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/dream-v2/schedule.js +5 -1
- package/unify/web-bridge.js +80 -34
package/package.json
CHANGED
|
@@ -50,7 +50,11 @@ export function createDreamScheduler({ run, intervalMs = DEFAULT_INTERVAL_MS, ke
|
|
|
50
50
|
async function fire(opts) {
|
|
51
51
|
if (inflight) {
|
|
52
52
|
log.warn?.('[dream] tick dropped — previous run still in progress');
|
|
53
|
-
return
|
|
53
|
+
return {
|
|
54
|
+
skipped: true,
|
|
55
|
+
skippedReason: 'already-running',
|
|
56
|
+
trigger: opts?.manual ? 'manual' : 'auto',
|
|
57
|
+
};
|
|
54
58
|
}
|
|
55
59
|
inflight = (async () => {
|
|
56
60
|
try {
|
package/unify/web-bridge.js
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
} from './history-compact.js';
|
|
53
53
|
import { persistUnifyAttachments, attachmentsForPersistence } from './attachments.js';
|
|
54
54
|
import { parseSeqFromId } from './conversation/persist.js';
|
|
55
|
+
import { sliceLastNTurns } from './turn-utils.js';
|
|
55
56
|
import { createVpStatusBroker } from './vp-status-broker.js';
|
|
56
57
|
import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
|
|
57
58
|
|
|
@@ -76,8 +77,8 @@ export function __testSetThreadClassifier(fn) {
|
|
|
76
77
|
* the first's inflight promise and dropped its own scope filter. So
|
|
77
78
|
* "B during A's run" doesn't actually produce a separate scoped pass
|
|
78
79
|
* for B — letting B install a second sink wrapper would only mis-stamp
|
|
79
|
-
* A's events with B's groupId.
|
|
80
|
-
* the honest answer; the user can re-click after A settles.
|
|
80
|
+
* A's events with B's groupId. Reporting B as an explicit skipped
|
|
81
|
+
* result is the honest answer; the user can re-click after A settles.
|
|
81
82
|
* @type {Set<string>}
|
|
82
83
|
*/
|
|
83
84
|
const inflightScopedDreamGroups = new Set();
|
|
@@ -523,6 +524,54 @@ function projectPersistedToHistoryEntry(m) {
|
|
|
523
524
|
return entry;
|
|
524
525
|
}
|
|
525
526
|
|
|
527
|
+
function projectPersistedToVisibleHistoryEntry(m) {
|
|
528
|
+
const entry = projectPersistedToHistoryEntry(m);
|
|
529
|
+
return entry && (entry.role === 'user' || entry.role === 'assistant') ? entry : null;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function loadVisibleGroupHistoryPage(store, groupId, limit, beforeSeq = null) {
|
|
533
|
+
if (!store || !groupId || !(limit > 0)) return { messages: [], oldestSeq: null, hasMore: false };
|
|
534
|
+
|
|
535
|
+
let rows = [];
|
|
536
|
+
try {
|
|
537
|
+
if (typeof store.loadOlderByGroup === 'function') {
|
|
538
|
+
// Use an unbounded raw prefix, then project/slice visible rows below.
|
|
539
|
+
// This preserves loadOlderByGroup's hot+cold scan without letting raw
|
|
540
|
+
// reflection/internal rows consume the UI-visible page window.
|
|
541
|
+
rows = store.loadOlderByGroup(groupId, beforeSeq, Infinity).messages || [];
|
|
542
|
+
} else if (Number.isFinite(beforeSeq)) {
|
|
543
|
+
const all = typeof store.loadAllByGroup === 'function'
|
|
544
|
+
? store.loadAllByGroup(groupId)
|
|
545
|
+
: store.loadRecentByGroup(groupId, Infinity);
|
|
546
|
+
rows = all.filter(m => parseSeqFromId(m?.id) < beforeSeq);
|
|
547
|
+
} else if (typeof store.loadAllByGroup === 'function') {
|
|
548
|
+
rows = store.loadAllByGroup(groupId);
|
|
549
|
+
} else {
|
|
550
|
+
rows = store.loadRecentByGroup(groupId, Infinity);
|
|
551
|
+
}
|
|
552
|
+
} catch (err) {
|
|
553
|
+
console.error('[Unify] visible history page load failed:', err?.message || err);
|
|
554
|
+
return { messages: [], oldestSeq: null, hasMore: false };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const visible = rows
|
|
558
|
+
.map(projectPersistedToVisibleHistoryEntry)
|
|
559
|
+
.filter(Boolean);
|
|
560
|
+
const messages = sliceLastNTurns(visible, limit);
|
|
561
|
+
const oldestSeq = messages.length ? parseSeqFromId(messages[0].id) : null;
|
|
562
|
+
const firstVisibleSeq = visible.length ? parseSeqFromId(visible[0].id) : null;
|
|
563
|
+
const hasMore = messages.length > 0
|
|
564
|
+
&& Number.isFinite(oldestSeq)
|
|
565
|
+
&& Number.isFinite(firstVisibleSeq)
|
|
566
|
+
&& oldestSeq > firstVisibleSeq;
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
messages,
|
|
570
|
+
oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
|
|
571
|
+
hasMore,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
526
575
|
/**
|
|
527
576
|
* Hydrate a freshly-created GroupContext's history from the on-disk
|
|
528
577
|
* conversation store. Returns an empty array if the session isn't
|
|
@@ -2955,10 +3004,12 @@ export function normalizeDreamResult(result) {
|
|
|
2955
3004
|
.filter(t => t && t.status === 'error')
|
|
2956
3005
|
.map(t => ({ target: t.target || null, error: t.error || 'unknown' }));
|
|
2957
3006
|
const hardError = result?.error || null;
|
|
2958
|
-
const
|
|
3007
|
+
const explicitSkipped = result?.skipped === true;
|
|
3008
|
+
const skipped = !hardError && (explicitSkipped || (groupsProcessed === 0 && targetsApplied === 0));
|
|
2959
3009
|
const skippedReason = skipped
|
|
2960
|
-
? (skippedGroups[0]?.reason || 'no-targets-applied')
|
|
3010
|
+
? (result?.skippedReason || skippedGroups[0]?.reason || 'no-targets-applied')
|
|
2961
3011
|
: null;
|
|
3012
|
+
const trigger = result?.trigger || null;
|
|
2962
3013
|
const success = !hardError && targetErrors.length === 0 && !skipped && targetsApplied > 0;
|
|
2963
3014
|
|
|
2964
3015
|
return {
|
|
@@ -2971,6 +3022,7 @@ export function normalizeDreamResult(result) {
|
|
|
2971
3022
|
targetErrors,
|
|
2972
3023
|
entriesCreated: targetsApplied,
|
|
2973
3024
|
lastDreamAt: result?.startedAt || new Date().toISOString(),
|
|
3025
|
+
trigger,
|
|
2974
3026
|
error: hardError || (targetErrors[0]?.error || null),
|
|
2975
3027
|
};
|
|
2976
3028
|
}
|
|
@@ -3007,11 +3059,16 @@ export async function handleUnifyDreamTrigger(msg = {}) {
|
|
|
3007
3059
|
// dream-v2/schedule.js inflight reuse), so the user-facing semantics
|
|
3008
3060
|
// are unchanged ("you already asked").
|
|
3009
3061
|
if (groupId && inflightScopedDreamGroups.size > 0) {
|
|
3010
|
-
const
|
|
3062
|
+
const skippedResult = {
|
|
3063
|
+
skipped: true,
|
|
3064
|
+
skippedReason: 'already-running',
|
|
3065
|
+
trigger: msg.manual === false ? 'auto' : 'manual',
|
|
3066
|
+
};
|
|
3011
3067
|
sendToServer({
|
|
3012
3068
|
type: 'unify_dream_result',
|
|
3013
3069
|
...tag,
|
|
3014
|
-
...
|
|
3070
|
+
...skippedResult,
|
|
3071
|
+
...normalizeDreamResult(skippedResult),
|
|
3015
3072
|
});
|
|
3016
3073
|
return;
|
|
3017
3074
|
}
|
|
@@ -3283,12 +3340,17 @@ export async function handleUnifyLoadHistory(msg) {
|
|
|
3283
3340
|
// ~20–25 turns; in the turn-count world 50 turns of UI scrollback is
|
|
3284
3341
|
// still cheap and matches what the frontend already passes through.
|
|
3285
3342
|
const limit = (typeof msg.limit === 'number') ? msg.limit : 50;
|
|
3286
|
-
const
|
|
3343
|
+
const visiblePage = groupId
|
|
3344
|
+
? loadVisibleGroupHistoryPage(session.conversationStore, groupId, limit)
|
|
3345
|
+
: { messages: limit > 0 ? pickRecent(session.conversationStore, limit) : [], oldestSeq: null, hasMore: false };
|
|
3287
3346
|
const compactSummary = session.conversationStore.readCompactSummary();
|
|
3347
|
+
const replayEntries = groupId
|
|
3348
|
+
? visiblePage.messages
|
|
3349
|
+
: visiblePage.messages
|
|
3350
|
+
.map(projectPersistedToVisibleHistoryEntry)
|
|
3351
|
+
.filter(Boolean);
|
|
3288
3352
|
|
|
3289
|
-
for (const
|
|
3290
|
-
const entry = projectPersistedToHistoryEntry(m);
|
|
3291
|
-
if (!entry) continue;
|
|
3353
|
+
for (const entry of replayEntries) {
|
|
3292
3354
|
if (entry.role === 'user') {
|
|
3293
3355
|
sendUnifyOutput({ type: 'user', message: { content: entry.content, id: entry.id || null } }, { groupId: entry.groupId || null });
|
|
3294
3356
|
} else if (entry.role === 'assistant') {
|
|
@@ -3310,33 +3372,19 @@ export async function handleUnifyLoadHistory(msg) {
|
|
|
3310
3372
|
|
|
3311
3373
|
// Compute the pagination cursor for the bootstrap load so the frontend
|
|
3312
3374
|
// knows whether a "Load older messages" hint should be shown and where
|
|
3313
|
-
// to start the next page.
|
|
3314
|
-
//
|
|
3315
|
-
//
|
|
3375
|
+
// to start the next page. For group history, this is computed from the
|
|
3376
|
+
// visible projected page, not raw persisted rows, so reflection/internal
|
|
3377
|
+
// tail rows cannot consume the bootstrap window or create false hasMore.
|
|
3316
3378
|
let hasMore = false;
|
|
3317
3379
|
let oldestSeq = null;
|
|
3318
|
-
if (groupId
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
// Defend against malformed ids: a NaN cursor would round-trip back as
|
|
3322
|
-
// a poison `beforeSeq` and degrade subsequent paginations to "give me
|
|
3323
|
-
// the newest page again". Surface as null instead.
|
|
3324
|
-
oldestSeq = Number.isFinite(seq) ? seq : null;
|
|
3325
|
-
if (oldestSeq != null) {
|
|
3326
|
-
// Consult the store for whether anything older exists in the same
|
|
3327
|
-
// group. Cheap: a single extra `loadOlderByGroup` with turns=1.
|
|
3328
|
-
try {
|
|
3329
|
-
const probe = session.conversationStore.loadOlderByGroup(groupId, oldestSeq, 1);
|
|
3330
|
-
hasMore = probe.messages.length > 0;
|
|
3331
|
-
} catch (err) {
|
|
3332
|
-
console.error('[Unify] history-load probe failed:', err.message);
|
|
3333
|
-
}
|
|
3334
|
-
}
|
|
3380
|
+
if (groupId) {
|
|
3381
|
+
hasMore = visiblePage.hasMore;
|
|
3382
|
+
oldestSeq = visiblePage.oldestSeq;
|
|
3335
3383
|
}
|
|
3336
3384
|
|
|
3337
3385
|
sendUnifyEvent({
|
|
3338
3386
|
type: 'history_loaded',
|
|
3339
|
-
count:
|
|
3387
|
+
count: replayEntries.length,
|
|
3340
3388
|
hasCompactSummary: !!compactSummary,
|
|
3341
3389
|
totalHot: session.conversationStore.countHot(),
|
|
3342
3390
|
totalCold: session.conversationStore.countCold(),
|
|
@@ -3378,7 +3426,7 @@ export async function handleUnifyLoadMoreHistory(msg) {
|
|
|
3378
3426
|
|
|
3379
3427
|
let result;
|
|
3380
3428
|
try {
|
|
3381
|
-
result = session.conversationStore
|
|
3429
|
+
result = loadVisibleGroupHistoryPage(session.conversationStore, groupId, turns, beforeSeq);
|
|
3382
3430
|
} catch (err) {
|
|
3383
3431
|
console.error('[Unify] loadOlderByGroup failed:', err.message);
|
|
3384
3432
|
result = { messages: [], oldestSeq: null, hasMore: false };
|
|
@@ -3389,8 +3437,6 @@ export async function handleUnifyLoadMoreHistory(msg) {
|
|
|
3389
3437
|
// server-side, and stable ids + speaker attribution ride with each row
|
|
3390
3438
|
// so older-history prepend renders exactly like refresh replay.
|
|
3391
3439
|
const projected = (result.messages || [])
|
|
3392
|
-
.map(projectPersistedToHistoryEntry)
|
|
3393
|
-
.filter(m => m && (m.role === 'user' || m.role === 'assistant'))
|
|
3394
3440
|
.map(m => ({
|
|
3395
3441
|
...(m.id ? { id: m.id } : {}),
|
|
3396
3442
|
role: m.role,
|