dsh-session-title-summary 0.0.0-stage → 1.7.2
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/LICENSE +661 -0
- package/backfill.js +159 -0
- package/client.js +937 -0
- package/cordis.patch.yml +92 -0
- package/icon.svg +6 -0
- package/index.js +372 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/monitor.js +74 -0
- package/package.json +70 -4
- package/panel-api.js +40 -0
- package/pure.js +255 -0
- package/store.js +82 -0
- package/README.md +0 -3
package/backfill.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { SessionPreparation } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import { foldSessionTitle } from '@deepseek-ai/dsh-session-title';
|
|
3
|
+
import { parseTitleFormat } from './pure.js';
|
|
4
|
+
import { safeDiagnostic, safeRecord } from './monitor.js';
|
|
5
|
+
|
|
6
|
+
export function sleep(ms, signal) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
if (signal?.aborted) return reject(signal.reason ?? new Error('aborted'));
|
|
9
|
+
const onAbort = () => { clearTimeout(timer); reject(signal.reason ?? new Error('aborted')); };
|
|
10
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
|
|
11
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function backfillSkipDecision(folded, snapshot, options) {
|
|
15
|
+
// Conforming titles are final. Every NONCONFORMING title is renamed
|
|
16
|
+
// regardless of how it was set — manually renamed sessions included.
|
|
17
|
+
if ((options.backfillIncludeSubagents ?? options.includeSubagents) !== true && snapshot.header.origin === 'subagent') return { skip: true, reason: 'subagent' };
|
|
18
|
+
if (folded === undefined) return { skip: false, reason: '' };
|
|
19
|
+
if (parseTitleFormat(folded.title) !== null) return { skip: true, reason: 'valid-format' };
|
|
20
|
+
return { skip: false, reason: '' };
|
|
21
|
+
}
|
|
22
|
+
function notify(observer, method, value) {
|
|
23
|
+
try { observer?.[method]?.(value); } catch { /* Diagnostics cannot fail a rename. */ }
|
|
24
|
+
}
|
|
25
|
+
/** Cold title refresh with live diagnostics; success is reported only AFTER append. */
|
|
26
|
+
export async function backfillHistoricalSessions(ctx, resolved, signal, observer) {
|
|
27
|
+
const snapshots = (await ctx.sessionPersistence.list()).sort((a, b) => b.header.createdAt - a.header.createdAt);
|
|
28
|
+
const liveById = new Map(ctx.sessions.list().map((session) => [session.id, session]));
|
|
29
|
+
let selected = 0;
|
|
30
|
+
const inventory = snapshots.map((snapshot) => {
|
|
31
|
+
let excluded = '';
|
|
32
|
+
if (liveById.has(snapshot.header.id)) excluded = 'live-session';
|
|
33
|
+
else if (snapshot.header.origin === 'subagent' && !resolved.backfillIncludeSubagents) excluded = 'subagent';
|
|
34
|
+
else if (snapshot.eventCount !== undefined && snapshot.eventCount < resolved.backfillMinEvents) excluded = 'events-below-minimum';
|
|
35
|
+
else if (selected >= resolved.backfillMaxSessions) excluded = 'batch-limit';
|
|
36
|
+
else selected++;
|
|
37
|
+
return { snapshot, excluded };
|
|
38
|
+
});
|
|
39
|
+
const report = { discovered: snapshots.length, considered: selected, scanned: 0, renamed: 0, skipped: 0, failed: 0, titles: [], records: [] };
|
|
40
|
+
notify(observer, 'onInventory', { discovered: report.discovered, considered: selected });
|
|
41
|
+
for (const { snapshot, excluded } of inventory) {
|
|
42
|
+
signal.throwIfAborted();
|
|
43
|
+
const id = snapshot.header.id;
|
|
44
|
+
const record = { sessionId: id, createdAt: snapshot.header.createdAt, oldTitle: '', newTitle: '', provider: null, model: null, modelLabel: '', method: 'none', status: 'scanning', reason: '', startedAt: Date.now(), finishedAt: null };
|
|
45
|
+
let handle;
|
|
46
|
+
const update = (patch) => {
|
|
47
|
+
Object.assign(record, patch);
|
|
48
|
+
notify(observer, 'onUpdate', safeRecord(record));
|
|
49
|
+
};
|
|
50
|
+
const finish = (status, extra = {}) => {
|
|
51
|
+
Object.assign(record, { status, ...extra, finishedAt: Date.now() });
|
|
52
|
+
report.scanned++;
|
|
53
|
+
if (status === 'renamed') report.renamed++;
|
|
54
|
+
else if (status === 'failed') report.failed++;
|
|
55
|
+
else report.skipped++;
|
|
56
|
+
const safe = safeRecord(record);
|
|
57
|
+
report.records.push(safe);
|
|
58
|
+
notify(observer, 'onProgress', { record: safe, report: { discovered: report.discovered, considered: report.considered, scanned: report.scanned, renamed: report.renamed, skipped: report.skipped, failed: report.failed } });
|
|
59
|
+
};
|
|
60
|
+
notify(observer, 'onStart', safeRecord(record));
|
|
61
|
+
const liveSession = excluded === 'live-session' ? liveById.get(id) : undefined;
|
|
62
|
+
if (liveSession) {
|
|
63
|
+
// Open sessions are renamed through the LIVE refresh path: the owning
|
|
64
|
+
// agent loop bridges the title appends, so no cold write handle is
|
|
65
|
+
// needed and nonconforming titles must not be exempted.
|
|
66
|
+
try {
|
|
67
|
+
const current = foldSessionTitle(liveSession.snapshotEvents());
|
|
68
|
+
update({ oldTitle: current?.title ?? '' });
|
|
69
|
+
const decision = backfillSkipDecision(current, snapshot, resolved);
|
|
70
|
+
if (decision.skip) { finish('skipped', { reason: decision.reason }); continue; }
|
|
71
|
+
update({ status: 'generating' });
|
|
72
|
+
const accepted = await ctx.sessionTitle.refresh(liveSession, signal);
|
|
73
|
+
if (!accepted) finish('skipped', { reason: 'no-title-accepted' });
|
|
74
|
+
else if (accepted.title === (current?.title ?? '')) finish('skipped', { reason: 'unchanged', newTitle: accepted.title });
|
|
75
|
+
else {
|
|
76
|
+
record.newTitle = accepted.title;
|
|
77
|
+
if (report.titles.length < 20) report.titles.push(safeDiagnostic(accepted.title));
|
|
78
|
+
finish('renamed');
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = safeDiagnostic(error?.message ?? error);
|
|
82
|
+
finish('failed', { reason: signal.aborted ? 'cancelled' : message });
|
|
83
|
+
} finally {
|
|
84
|
+
try { await sleep(resolved.backfillDelayMs, signal); } catch { break; }
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (excluded) { finish('skipped', { reason: excluded }); continue; }
|
|
89
|
+
try {
|
|
90
|
+
// A session opened mid-scan loses the write race and is reported as
|
|
91
|
+
// session-in-use by the open() ownership check below — no exemption.
|
|
92
|
+
handle = await ctx.sessionPersistence.open(id, 'write', { signal });
|
|
93
|
+
const cold = await handle.read(0, undefined, { signal });
|
|
94
|
+
const current = foldSessionTitle(cold.events);
|
|
95
|
+
update({ oldTitle: current?.title ?? '' });
|
|
96
|
+
if (cold.events.length < resolved.backfillMinEvents) { finish('skipped', { reason: 'events-below-minimum' }); continue; }
|
|
97
|
+
const decision = backfillSkipDecision(current, snapshot, resolved);
|
|
98
|
+
if (decision.skip) { finish('skipped', { reason: decision.reason }); continue; }
|
|
99
|
+
const initial = cold.events.length;
|
|
100
|
+
const preparation = SessionPreparation.create(ctx.sessions.prepare(id, { seed: [...cold.events], meta: structuredClone(handle.header), inheritedEventCount: handle.inheritedEventCount, eventState: cold.eventState }));
|
|
101
|
+
let leave;
|
|
102
|
+
let storedCursor = initial;
|
|
103
|
+
const flushSuffix = async () => {
|
|
104
|
+
// Flush the constructor's non-published seed marker BEFORE entering.
|
|
105
|
+
// Use the immutable full snapshot and explicit sequence cursor, so the
|
|
106
|
+
// append cannot omit the first lifecycle event or repeat it later.
|
|
107
|
+
const persistedNow = await handle.read(0, undefined, { signal });
|
|
108
|
+
const storedEvents = persistedNow.events;
|
|
109
|
+
const nextSeq = storedEvents.length ? storedEvents.at(-1).seq + 1 : 0;
|
|
110
|
+
const localEvents = preparation.session.snapshotEvents();
|
|
111
|
+
// Another mounted persistence observer can already have stored live
|
|
112
|
+
// events. Verify the overlap instead of appending the same seq twice.
|
|
113
|
+
for (let seq = storedCursor; seq < nextSeq; seq++) {
|
|
114
|
+
const diskEvent = storedEvents.find((e) => e.seq === seq);
|
|
115
|
+
const localEvent = localEvents.find((e) => e.seq === seq);
|
|
116
|
+
if (!diskEvent || !localEvent || JSON.stringify(diskEvent) !== JSON.stringify(localEvent)) throw new Error(`backfill persisted-prefix conflict at seq ${seq}`);
|
|
117
|
+
}
|
|
118
|
+
storedCursor = nextSeq;
|
|
119
|
+
const suffix = localEvents.filter((event) => event.seq >= storedCursor);
|
|
120
|
+
if (suffix.length) {
|
|
121
|
+
if (suffix[0].seq !== storedCursor) throw new Error(`backfill event gap: expected ${storedCursor}, got ${suffix[0].seq}`);
|
|
122
|
+
await handle.append([...suffix]);
|
|
123
|
+
storedCursor = suffix.at(-1).seq + 1;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
await flushSuffix();
|
|
128
|
+
leave = ctx.sessions.enter(preparation.session);
|
|
129
|
+
update({ status: 'generating' });
|
|
130
|
+
let accepted;
|
|
131
|
+
try { accepted = await ctx.sessionTitle.refresh(preparation.session, signal); }
|
|
132
|
+
finally {
|
|
133
|
+
// The provider reports the actual effective LLM route when it dispatches.
|
|
134
|
+
// Copy that metadata back, never label a historical route as a call.
|
|
135
|
+
const actual = observer?.getExecution?.(id);
|
|
136
|
+
if (actual) Object.assign(record, actual);
|
|
137
|
+
update({ status: 'persisting' });
|
|
138
|
+
await flushSuffix();
|
|
139
|
+
await handle.flush?.();
|
|
140
|
+
}
|
|
141
|
+
if (!accepted) finish('skipped', { reason: 'no-title-accepted' });
|
|
142
|
+
else if (accepted.title === (current?.title ?? '')) finish('skipped', { reason: 'unchanged', newTitle: accepted.title });
|
|
143
|
+
else {
|
|
144
|
+
record.newTitle = accepted.title;
|
|
145
|
+
if (report.titles.length < 20) report.titles.push(safeDiagnostic(accepted.title));
|
|
146
|
+
finish('renamed');
|
|
147
|
+
}
|
|
148
|
+
} finally { leave?.(); preparation[Symbol.dispose](); }
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const message = safeDiagnostic(error?.message ?? error);
|
|
151
|
+
if (/already|owned|in use/i.test(message)) finish('skipped', { reason: 'session-in-use' });
|
|
152
|
+
else finish('failed', { reason: signal.aborted ? 'cancelled' : message });
|
|
153
|
+
} finally {
|
|
154
|
+
try { await handle?.close(); } catch { /* Append determined the result already. */ }
|
|
155
|
+
try { await sleep(resolved.backfillDelayMs, signal); } catch { break; }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return report;
|
|
159
|
+
}
|