@adhdev/daemon-core 0.8.83 → 0.8.84
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/dist/config/chat-history.d.ts +1 -0
- package/dist/index.js +44 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +44 -2
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +10 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/config/chat-history.ts +137 -0
- package/src/providers/cli-provider-instance.ts +65 -2
|
@@ -8,6 +8,14 @@ import { type ProviderModule } from './contracts.js';
|
|
|
8
8
|
import type { ProviderInstance, ProviderState, InstanceContext } from './provider-instance.js';
|
|
9
9
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
10
10
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
11
|
+
type PersistableCliHistoryMessage = {
|
|
12
|
+
role: string;
|
|
13
|
+
content: string;
|
|
14
|
+
kind?: string;
|
|
15
|
+
senderName?: string;
|
|
16
|
+
receivedAt?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function buildIncrementalHistoryAppendMessages(previousMessages: PersistableCliHistoryMessage[], currentMessages: PersistableCliHistoryMessage[]): PersistableCliHistoryMessage[];
|
|
11
19
|
export declare function getForcedNewSessionScriptName(provider: ProviderModule | undefined, launchMode: 'new' | 'resume' | 'manual'): string | null;
|
|
12
20
|
export declare function waitForCliAdapterReady(adapter: {
|
|
13
21
|
isReady?: () => boolean;
|
|
@@ -39,6 +47,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
39
47
|
private appliedEffectKeys;
|
|
40
48
|
private historyWriter;
|
|
41
49
|
private runtimeMessages;
|
|
50
|
+
private lastPersistedHistoryMessages;
|
|
42
51
|
readonly instanceId: string;
|
|
43
52
|
private suppressIdleHistoryReplay;
|
|
44
53
|
private errorMessage;
|
|
@@ -99,3 +108,4 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
99
108
|
private buildSqlPlaceholderList;
|
|
100
109
|
private querySqliteText;
|
|
101
110
|
}
|
|
111
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1274,3 +1274,140 @@ export function listSavedHistorySessions(
|
|
|
1274
1274
|
return { sessions: [], hasMore: false };
|
|
1275
1275
|
}
|
|
1276
1276
|
}
|
|
1277
|
+
|
|
1278
|
+
function normalizeCanonicalHermesMessageContent(content: unknown): string {
|
|
1279
|
+
if (typeof content === 'string') return content.trim();
|
|
1280
|
+
if (content == null) return '';
|
|
1281
|
+
try {
|
|
1282
|
+
return JSON.stringify(content).trim();
|
|
1283
|
+
} catch {
|
|
1284
|
+
return String(content).trim();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function extractCanonicalHermesMessageTimestamp(message: Record<string, unknown>, fallbackTs: number): number {
|
|
1289
|
+
const numericTimestamp = Number(message.receivedAt || message.timestamp || message.ts || 0);
|
|
1290
|
+
if (Number.isFinite(numericTimestamp) && numericTimestamp > 0) return numericTimestamp;
|
|
1291
|
+
const stringTimestamp = typeof message.ts === 'string'
|
|
1292
|
+
? Date.parse(message.ts)
|
|
1293
|
+
: (typeof message.timestamp === 'string' ? Date.parse(message.timestamp) : NaN);
|
|
1294
|
+
if (Number.isFinite(stringTimestamp) && stringTimestamp > 0) return stringTimestamp;
|
|
1295
|
+
return fallbackTs;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function readExistingHermesSessionStartRecord(historySessionId: string): HistoryMessage | null {
|
|
1299
|
+
try {
|
|
1300
|
+
const dir = path.join(HISTORY_DIR, 'hermes-cli');
|
|
1301
|
+
if (!fs.existsSync(dir)) return null;
|
|
1302
|
+
const files = listHistoryFiles(dir, historySessionId).sort();
|
|
1303
|
+
for (const file of files) {
|
|
1304
|
+
const lines = fs.readFileSync(path.join(dir, file), 'utf-8').split('\n').filter(Boolean);
|
|
1305
|
+
for (const line of lines) {
|
|
1306
|
+
try {
|
|
1307
|
+
const parsed = JSON.parse(line) as HistoryMessage;
|
|
1308
|
+
if (parsed.historySessionId !== historySessionId) continue;
|
|
1309
|
+
if (parsed.kind === 'session_start' && parsed.role === 'system') {
|
|
1310
|
+
return parsed;
|
|
1311
|
+
}
|
|
1312
|
+
} catch {
|
|
1313
|
+
// Ignore malformed lines while probing for the original session_start marker.
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return null;
|
|
1318
|
+
} catch {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
export function rebuildHermesSavedHistoryFromCanonicalSession(historySessionId: string): boolean {
|
|
1324
|
+
const normalizedSessionId = normalizeSavedHistorySessionId('hermes-cli', historySessionId);
|
|
1325
|
+
if (!normalizedSessionId) return false;
|
|
1326
|
+
|
|
1327
|
+
try {
|
|
1328
|
+
const sessionFilePath = path.join(os.homedir(), '.hermes', 'sessions', `session_${normalizedSessionId}.json`);
|
|
1329
|
+
if (!fs.existsSync(sessionFilePath)) return false;
|
|
1330
|
+
const raw = JSON.parse(fs.readFileSync(sessionFilePath, 'utf-8')) as {
|
|
1331
|
+
session_start?: string;
|
|
1332
|
+
last_updated?: string;
|
|
1333
|
+
messages?: Array<Record<string, unknown>>;
|
|
1334
|
+
};
|
|
1335
|
+
const canonicalMessages = Array.isArray(raw.messages) ? raw.messages : [];
|
|
1336
|
+
const dir = path.join(HISTORY_DIR, 'hermes-cli');
|
|
1337
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1338
|
+
const existingSessionStart = readExistingHermesSessionStartRecord(normalizedSessionId);
|
|
1339
|
+
const records: HistoryMessage[] = [];
|
|
1340
|
+
if (existingSessionStart) {
|
|
1341
|
+
records.push({
|
|
1342
|
+
...existingSessionStart,
|
|
1343
|
+
historySessionId: normalizedSessionId,
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
let fallbackTs = Date.parse(raw.session_start || raw.last_updated || '') || Date.now();
|
|
1348
|
+
for (const message of canonicalMessages) {
|
|
1349
|
+
const role = String(message.role || '').trim();
|
|
1350
|
+
const content = normalizeCanonicalHermesMessageContent(message.content);
|
|
1351
|
+
if (!content) continue;
|
|
1352
|
+
const receivedAt = extractCanonicalHermesMessageTimestamp(message, fallbackTs);
|
|
1353
|
+
fallbackTs = receivedAt + 1;
|
|
1354
|
+
|
|
1355
|
+
if (role === 'user') {
|
|
1356
|
+
records.push({
|
|
1357
|
+
ts: new Date(receivedAt).toISOString(),
|
|
1358
|
+
receivedAt,
|
|
1359
|
+
role: 'user',
|
|
1360
|
+
content,
|
|
1361
|
+
kind: 'standard',
|
|
1362
|
+
agent: 'hermes-cli',
|
|
1363
|
+
historySessionId: normalizedSessionId,
|
|
1364
|
+
});
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
if (role === 'assistant') {
|
|
1369
|
+
records.push({
|
|
1370
|
+
ts: new Date(receivedAt).toISOString(),
|
|
1371
|
+
receivedAt,
|
|
1372
|
+
role: 'assistant',
|
|
1373
|
+
content,
|
|
1374
|
+
kind: 'standard',
|
|
1375
|
+
agent: 'hermes-cli',
|
|
1376
|
+
historySessionId: normalizedSessionId,
|
|
1377
|
+
});
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (role === 'tool') {
|
|
1382
|
+
records.push({
|
|
1383
|
+
ts: new Date(receivedAt).toISOString(),
|
|
1384
|
+
receivedAt,
|
|
1385
|
+
role: 'assistant',
|
|
1386
|
+
content,
|
|
1387
|
+
kind: 'tool',
|
|
1388
|
+
senderName: 'Tool',
|
|
1389
|
+
agent: 'hermes-cli',
|
|
1390
|
+
historySessionId: normalizedSessionId,
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
if (records.length === 0) return false;
|
|
1396
|
+
|
|
1397
|
+
const prefix = `${normalizedSessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}_`;
|
|
1398
|
+
for (const file of fs.readdirSync(dir)) {
|
|
1399
|
+
if (file.startsWith(prefix) && file.endsWith('.jsonl')) {
|
|
1400
|
+
fs.unlinkSync(path.join(dir, file));
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const targetDate = new Date(records[records.length - 1].receivedAt || Date.now()).toISOString().slice(0, 10);
|
|
1405
|
+
const filePath = path.join(dir, `${prefix}${targetDate}.jsonl`);
|
|
1406
|
+
fs.writeFileSync(filePath, `${records.map((record) => JSON.stringify(record)).join('\n')}\n`, 'utf-8');
|
|
1407
|
+
invalidatePersistedSavedHistoryIndex('hermes-cli', dir);
|
|
1408
|
+
savedHistorySessionCache.delete('hermes-cli');
|
|
1409
|
+
return true;
|
|
1410
|
+
} catch {
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
@@ -27,6 +27,51 @@ import { mergeProviderPatchState, resolveProviderStateSurface } from './provider
|
|
|
27
27
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
28
28
|
import { buildChatMessage, buildRuntimeSystemChatMessage, normalizeChatMessages } from './chat-message-normalization.js';
|
|
29
29
|
|
|
30
|
+
type PersistableCliHistoryMessage = {
|
|
31
|
+
role: string;
|
|
32
|
+
content: string;
|
|
33
|
+
kind?: string;
|
|
34
|
+
senderName?: string;
|
|
35
|
+
receivedAt?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function normalizePersistableCliHistoryContent(content: unknown): string {
|
|
39
|
+
return flattenContent(content as any).replace(/\s+/g, ' ').trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildPersistableCliHistorySignature(message: PersistableCliHistoryMessage): string {
|
|
43
|
+
return [
|
|
44
|
+
String(message.role || ''),
|
|
45
|
+
String(message.kind || ''),
|
|
46
|
+
String(message.senderName || ''),
|
|
47
|
+
normalizePersistableCliHistoryContent(message.content),
|
|
48
|
+
].join('|');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildIncrementalHistoryAppendMessages(
|
|
52
|
+
previousMessages: PersistableCliHistoryMessage[],
|
|
53
|
+
currentMessages: PersistableCliHistoryMessage[],
|
|
54
|
+
): PersistableCliHistoryMessage[] {
|
|
55
|
+
if (!Array.isArray(currentMessages) || currentMessages.length === 0) return [];
|
|
56
|
+
if (!Array.isArray(previousMessages) || previousMessages.length === 0) return currentMessages;
|
|
57
|
+
|
|
58
|
+
const previousSignatures = previousMessages.map(buildPersistableCliHistorySignature);
|
|
59
|
+
const currentSignatures = currentMessages.map(buildPersistableCliHistorySignature);
|
|
60
|
+
|
|
61
|
+
let sharedPrefixLength = 0;
|
|
62
|
+
while (
|
|
63
|
+
sharedPrefixLength < previousSignatures.length
|
|
64
|
+
&& sharedPrefixLength < currentSignatures.length
|
|
65
|
+
&& previousSignatures[sharedPrefixLength] === currentSignatures[sharedPrefixLength]
|
|
66
|
+
) {
|
|
67
|
+
sharedPrefixLength += 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (sharedPrefixLength === currentSignatures.length) return [];
|
|
71
|
+
if (sharedPrefixLength === previousSignatures.length) return currentMessages.slice(sharedPrefixLength);
|
|
72
|
+
return currentMessages;
|
|
73
|
+
}
|
|
74
|
+
|
|
30
75
|
let CachedDatabaseSync: (new (path: string, options?: { readOnly?: boolean }) => {
|
|
31
76
|
prepare(sql: string): { get(...params: Array<string | number>): unknown };
|
|
32
77
|
close(): void;
|
|
@@ -112,6 +157,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
112
157
|
private appliedEffectKeys = new Set<string>();
|
|
113
158
|
private historyWriter: ChatHistoryWriter;
|
|
114
159
|
private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
|
|
160
|
+
private lastPersistedHistoryMessages: PersistableCliHistoryMessage[] = [];
|
|
115
161
|
readonly instanceId: string;
|
|
116
162
|
private suppressIdleHistoryReplay = false;
|
|
117
163
|
private errorMessage: string | undefined = undefined;
|
|
@@ -200,6 +246,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
200
246
|
this.providerSessionId,
|
|
201
247
|
this.instanceId,
|
|
202
248
|
);
|
|
249
|
+
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
250
|
+
role: message.role,
|
|
251
|
+
content: message.content,
|
|
252
|
+
kind: message.kind,
|
|
253
|
+
senderName: message.senderName,
|
|
254
|
+
receivedAt: message.receivedAt,
|
|
255
|
+
}));
|
|
203
256
|
this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
|
|
204
257
|
if (restoredHistory.messages.length > 0) {
|
|
205
258
|
this.adapter.seedCommittedMessages(
|
|
@@ -363,15 +416,24 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
363
416
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
364
417
|
}
|
|
365
418
|
}
|
|
366
|
-
|
|
419
|
+
const normalizedMessagesToSave = messagesToSave.map((message: PersistableCliHistoryMessage & { timestamp?: number }) => ({
|
|
420
|
+
role: message.role,
|
|
421
|
+
content: flattenContent(message.content),
|
|
422
|
+
kind: typeof message.kind === 'string' ? message.kind : undefined,
|
|
423
|
+
senderName: typeof message.senderName === 'string' ? message.senderName : undefined,
|
|
424
|
+
receivedAt: typeof message.receivedAt === 'number' ? message.receivedAt : message.timestamp,
|
|
425
|
+
}));
|
|
426
|
+
if (!shouldSkipReplayPersist && normalizedMessagesToSave.length > 0) {
|
|
427
|
+
const incrementalMessages = buildIncrementalHistoryAppendMessages(this.lastPersistedHistoryMessages, normalizedMessagesToSave);
|
|
367
428
|
this.historyWriter.appendNewMessages(
|
|
368
429
|
this.type,
|
|
369
|
-
|
|
430
|
+
incrementalMessages,
|
|
370
431
|
parsedStatus?.title || dirName,
|
|
371
432
|
this.instanceId,
|
|
372
433
|
this.providerSessionId,
|
|
373
434
|
);
|
|
374
435
|
}
|
|
436
|
+
this.lastPersistedHistoryMessages = normalizedMessagesToSave;
|
|
375
437
|
}
|
|
376
438
|
|
|
377
439
|
this.applyProviderResponse(parsedStatus, { phase: 'immediate' });
|
|
@@ -640,6 +702,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
640
702
|
|
|
641
703
|
if (data.sessionEvent === 'new_session') {
|
|
642
704
|
this.runtimeMessages = [];
|
|
705
|
+
this.lastPersistedHistoryMessages = [];
|
|
643
706
|
this.suppressIdleHistoryReplay = false;
|
|
644
707
|
this.adapter.clearHistory();
|
|
645
708
|
}
|