@adhdev/daemon-core 0.9.5 → 0.9.7
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/cli-adapters/provider-cli-adapter.d.ts +7 -8
- package/dist/cli-adapters/provider-cli-shared.d.ts +0 -1
- package/dist/index.js +438 -496
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +438 -496
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +7 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +302 -448
- package/src/cli-adapters/provider-cli-parse.ts +47 -2
- package/src/cli-adapters/provider-cli-shared.ts +51 -5
- package/src/commands/chat-commands.ts +11 -13
- package/src/providers/cli-provider-instance.ts +59 -15
- package/src/providers/provider-schema.ts +1 -0
|
@@ -94,6 +94,51 @@ export function hydrateCliParsedMessages(
|
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
function chooseMoreComparableCliMessage(left: CliChatMessage, right: CliChatMessage): CliChatMessage {
|
|
98
|
+
const leftComparable = normalizeComparableMessageContent(left.content || '');
|
|
99
|
+
const rightComparable = normalizeComparableMessageContent(right.content || '');
|
|
100
|
+
|
|
101
|
+
if (leftComparable && leftComparable === rightComparable) {
|
|
102
|
+
const leftNewlines = String(left.content || '').split(/\r\n|\n|\r/g).length - 1;
|
|
103
|
+
const rightNewlines = String(right.content || '').split(/\r\n|\n|\r/g).length - 1;
|
|
104
|
+
return rightNewlines < leftNewlines ? right : left;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return rightComparable.length > leftComparable.length ? right : left;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function dedupeConsecutiveComparableCliMessages(messages: CliChatMessage[]): CliChatMessage[] {
|
|
111
|
+
const deduped: CliChatMessage[] = [];
|
|
112
|
+
|
|
113
|
+
for (const message of messages) {
|
|
114
|
+
const current = {
|
|
115
|
+
...message,
|
|
116
|
+
content: typeof message.content === 'string' ? message.content : String(message.content || ''),
|
|
117
|
+
} as CliChatMessage;
|
|
118
|
+
const previous = deduped[deduped.length - 1];
|
|
119
|
+
if (!previous) {
|
|
120
|
+
deduped.push(current);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const previousComparable = normalizeComparableMessageContent(previous.content || '');
|
|
125
|
+
const currentComparable = normalizeComparableMessageContent(current.content || '');
|
|
126
|
+
const sameRole = previous.role === current.role;
|
|
127
|
+
const sameKind = (previous.kind || 'standard') === (current.kind || 'standard');
|
|
128
|
+
const sameSender = (previous.senderName || '') === (current.senderName || '');
|
|
129
|
+
const comparableMatch = previousComparable && previousComparable === currentComparable;
|
|
130
|
+
|
|
131
|
+
if (sameRole && sameKind && sameSender && comparableMatch) {
|
|
132
|
+
deduped[deduped.length - 1] = chooseMoreComparableCliMessage(previous, current);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
deduped.push(current);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return deduped;
|
|
140
|
+
}
|
|
141
|
+
|
|
97
142
|
export function normalizeCliParsedMessages(
|
|
98
143
|
parsedMessages: any[],
|
|
99
144
|
options: {
|
|
@@ -103,7 +148,7 @@ export function normalizeCliParsedMessages(
|
|
|
103
148
|
now?: number;
|
|
104
149
|
},
|
|
105
150
|
): CliChatMessage[] {
|
|
106
|
-
return hydrateCliParsedMessages(parsedMessages, options).map((message) => ({
|
|
151
|
+
return dedupeConsecutiveComparableCliMessages(hydrateCliParsedMessages(parsedMessages, options).map((message) => ({
|
|
107
152
|
role: message.role,
|
|
108
153
|
content: message.content,
|
|
109
154
|
timestamp: message.timestamp,
|
|
@@ -113,7 +158,7 @@ export function normalizeCliParsedMessages(
|
|
|
113
158
|
index: message.index,
|
|
114
159
|
meta: message.meta,
|
|
115
160
|
senderName: message.senderName,
|
|
116
|
-
}));
|
|
161
|
+
})));
|
|
117
162
|
}
|
|
118
163
|
|
|
119
164
|
export function buildCliParseInput(options: {
|
|
@@ -373,8 +373,58 @@ export function normalizeScreenSnapshot(text: string): string {
|
|
|
373
373
|
.trim();
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
const COMMON_COMPARABLE_WRAP_WORDS = new Set([
|
|
377
|
+
'a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'from', 'in', 'into', 'is', 'it', 'of', 'on', 'or', 'that', 'the', 'their', 'then', 'this', 'to', 'was', 'with',
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
function shouldReflowComparableMessageLines(lines: string[]): boolean {
|
|
381
|
+
return Array.isArray(lines)
|
|
382
|
+
&& lines.length > 1
|
|
383
|
+
&& lines.slice(0, -1).every((line) => String(line || '').trim().length >= 48)
|
|
384
|
+
&& !lines.some((line) => /^```/.test(line))
|
|
385
|
+
&& !lines.some((line) => /^\|/.test(line))
|
|
386
|
+
&& !lines.some((line) => /^\s*(?:[-*+] |\d+\.\s)/.test(line));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function joinComparableMessageLines(lines: string[]): string {
|
|
390
|
+
return lines.reduce((acc, line) => {
|
|
391
|
+
const next = String(line || '').trim();
|
|
392
|
+
if (!next) return acc;
|
|
393
|
+
if (!acc) return next;
|
|
394
|
+
|
|
395
|
+
if (/[,\d]$/.test(acc) && /^\d/.test(next)) {
|
|
396
|
+
return `${acc}${next}`;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (/[A-Za-z]$/.test(acc) && /^\d/.test(next)) {
|
|
400
|
+
return `${acc}${next}`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const fragmentMatch = acc.match(/([A-Za-z]{1,4})$/);
|
|
404
|
+
const fragment = fragmentMatch ? fragmentMatch[1].toLowerCase() : '';
|
|
405
|
+
if (/^[a-z]/.test(next) && fragment && !COMMON_COMPARABLE_WRAP_WORDS.has(fragment)) {
|
|
406
|
+
return `${acc}${next}`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return `${acc} ${next}`;
|
|
410
|
+
}, '')
|
|
411
|
+
.replace(/\s+([,.;:!?])/g, '$1')
|
|
412
|
+
.replace(/(\d)\s+,/g, '$1,')
|
|
413
|
+
.replace(/\s+/g, ' ')
|
|
414
|
+
.trim();
|
|
415
|
+
}
|
|
416
|
+
|
|
376
417
|
export function normalizeComparableMessageContent(text: string): string {
|
|
377
|
-
|
|
418
|
+
const lines = String(text || '')
|
|
419
|
+
.split(/\r\n|\n|\r/g)
|
|
420
|
+
.map((line) => line.trim())
|
|
421
|
+
.filter(Boolean);
|
|
422
|
+
|
|
423
|
+
if (lines.length === 0) return '';
|
|
424
|
+
if (shouldReflowComparableMessageLines(lines)) {
|
|
425
|
+
return joinComparableMessageLines(lines);
|
|
426
|
+
}
|
|
427
|
+
return lines.join(' ')
|
|
378
428
|
.replace(/\s+/g, ' ')
|
|
379
429
|
.trim();
|
|
380
430
|
}
|
|
@@ -414,10 +464,6 @@ export function getLastUserPromptText(messages: Array<{ role?: string; content?:
|
|
|
414
464
|
return '';
|
|
415
465
|
}
|
|
416
466
|
|
|
417
|
-
export function looksLikeConfirmOnlyLabel(label: string): boolean {
|
|
418
|
-
return /^(?:continue|confirm|ok|yes|trust|proceed|enter)$/i.test(String(label || '').trim());
|
|
419
|
-
}
|
|
420
|
-
|
|
421
467
|
function parsePatternEntry(x: unknown): RegExp | null {
|
|
422
468
|
if (x instanceof RegExp) return x;
|
|
423
469
|
if (x && typeof x === 'object' && typeof (x as { source?: string }).source === 'string') {
|
|
@@ -1255,26 +1255,24 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1255
1255
|
? 'waiting_approval'
|
|
1256
1256
|
: status?.status;
|
|
1257
1257
|
LOG.info('Command', `[resolveAction] CLI PTY gate target=${String(args?.targetSessionId || '')} rawStatus=${String(status?.status || '')} effectiveStatus=${String(effectiveStatus || '')} statusModal=${statusModal ? 'yes' : 'no'} surfacedModal=${surfacedModal ? 'yes' : 'no'} instance=${targetInstance ? 'yes' : 'no'}`);
|
|
1258
|
-
if (
|
|
1258
|
+
if (!effectiveModal) {
|
|
1259
1259
|
return { success: false, error: 'Not in approval state' };
|
|
1260
1260
|
}
|
|
1261
|
-
const buttons: string[] = effectiveModal
|
|
1262
|
-
// Resolve button index: explicit buttonIndex arg →
|
|
1261
|
+
const buttons: string[] = Array.isArray(effectiveModal.buttons) ? effectiveModal.buttons : [];
|
|
1262
|
+
// Resolve button index: explicit buttonIndex arg → exact text match → explicit action mapping
|
|
1263
1263
|
let buttonIndex = typeof args?.buttonIndex === 'number' ? args.buttonIndex : -1;
|
|
1264
|
-
if (buttonIndex < 0) {
|
|
1264
|
+
if (buttonIndex < 0 && button) {
|
|
1265
1265
|
const btnLower = button.toLowerCase();
|
|
1266
1266
|
buttonIndex = buttons.findIndex(b => b.toLowerCase().includes(btnLower));
|
|
1267
1267
|
}
|
|
1268
|
+
if (buttonIndex < 0 && (action === 'reject' || action === 'deny')) {
|
|
1269
|
+
buttonIndex = buttons.findIndex(b => /deny|reject|no/i.test(b));
|
|
1270
|
+
}
|
|
1271
|
+
if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
|
|
1272
|
+
buttonIndex = buttons.findIndex(b => /always/i.test(b));
|
|
1273
|
+
}
|
|
1268
1274
|
if (buttonIndex < 0) {
|
|
1269
|
-
|
|
1270
|
-
buttonIndex = buttons.findIndex(b => /deny|reject|no/i.test(b));
|
|
1271
|
-
if (buttonIndex < 0) buttonIndex = buttons.length - 1;
|
|
1272
|
-
} else if (action === 'always' || /always/i.test(button)) {
|
|
1273
|
-
buttonIndex = buttons.findIndex(b => /always/i.test(b));
|
|
1274
|
-
if (buttonIndex < 0) buttonIndex = 1;
|
|
1275
|
-
} else {
|
|
1276
|
-
buttonIndex = 0; // approve → first option (default selected)
|
|
1277
|
-
}
|
|
1275
|
+
return { success: false, error: 'Approval action did not match any visible button' };
|
|
1278
1276
|
}
|
|
1279
1277
|
if (typeof adapter.resolveModal === 'function') {
|
|
1280
1278
|
adapter.resolveModal(buttonIndex);
|
|
@@ -161,6 +161,16 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
161
161
|
private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
|
|
162
162
|
private lastPersistedHistoryMessages: PersistableCliHistoryMessage[] = [];
|
|
163
163
|
private lastCanonicalHermesSyncMtimeMs = 0;
|
|
164
|
+
private lastCanonicalHermesExistCheckAt = 0;
|
|
165
|
+
private lastCanonicalHermesWatchPath: string | undefined = undefined;
|
|
166
|
+
private lastCanonicalClaudeRebuildMtimeMs = 0;
|
|
167
|
+
private lastCanonicalClaudeCheckAt = 0;
|
|
168
|
+
private cachedSqliteDb: {
|
|
169
|
+
prepare(sql: string): { get(...values: Array<string | number>): unknown };
|
|
170
|
+
close(): void;
|
|
171
|
+
} | null = null;
|
|
172
|
+
private cachedSqliteDbPath: string | null = null;
|
|
173
|
+
private cachedSqliteDbMissingUntil = 0;
|
|
164
174
|
readonly instanceId: string;
|
|
165
175
|
private suppressIdleHistoryReplay = false;
|
|
166
176
|
private errorMessage: string | undefined = undefined;
|
|
@@ -281,7 +291,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
281
291
|
timestampFormat?: 'unix_ms' | 'unix_s' | 'iso';
|
|
282
292
|
}): string | null {
|
|
283
293
|
const resolvedDbPath = probe.dbPath.replace(/^~/, os.homedir());
|
|
284
|
-
if (
|
|
294
|
+
// Skip existsSync if we already confirmed DB is missing (cache for 10s)
|
|
295
|
+
const now = Date.now();
|
|
296
|
+
if (this.cachedSqliteDbMissingUntil > now) return null;
|
|
297
|
+
if (!fs.existsSync(resolvedDbPath)) {
|
|
298
|
+
this.cachedSqliteDbMissingUntil = now + 10_000;
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
285
301
|
|
|
286
302
|
const directories = this.getProbeDirectories();
|
|
287
303
|
const minCreatedAt = Math.max(0, this.startedAt - 60_000);
|
|
@@ -486,6 +502,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
486
502
|
this.adapter.shutdown();
|
|
487
503
|
this.monitor.reset();
|
|
488
504
|
this.appliedEffectKeys.clear();
|
|
505
|
+
try { this.cachedSqliteDb?.close(); } catch { /* noop */ }
|
|
506
|
+
this.cachedSqliteDb = null;
|
|
507
|
+
this.cachedSqliteDbPath = null;
|
|
489
508
|
}
|
|
490
509
|
|
|
491
510
|
private completedDebounceTimer: NodeJS.Timeout | null = null;
|
|
@@ -944,13 +963,38 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
944
963
|
const watchPath = canonicalHistory.watchPath
|
|
945
964
|
.replace(/^~/, os.homedir())
|
|
946
965
|
.replace('{{sessionId}}', this.providerSessionId);
|
|
947
|
-
|
|
966
|
+
// Throttle existsSync: check file existence at most once per 2s
|
|
967
|
+
const now = Date.now();
|
|
968
|
+
if (watchPath !== this.lastCanonicalHermesWatchPath || now - this.lastCanonicalHermesExistCheckAt >= 2_000) {
|
|
969
|
+
this.lastCanonicalHermesWatchPath = watchPath;
|
|
970
|
+
this.lastCanonicalHermesExistCheckAt = now;
|
|
971
|
+
if (!fs.existsSync(watchPath)) return false;
|
|
972
|
+
} else if (this.lastCanonicalHermesSyncMtimeMs === 0) {
|
|
973
|
+
// First check: file existence not yet confirmed, must verify
|
|
974
|
+
if (!fs.existsSync(watchPath)) return false;
|
|
975
|
+
}
|
|
948
976
|
const stat = fs.statSync(watchPath);
|
|
949
977
|
if (stat.mtimeMs <= this.lastCanonicalHermesSyncMtimeMs) return true;
|
|
950
978
|
rebuilt = rebuildHermesSavedHistoryFromCanonicalSession(this.providerSessionId);
|
|
951
979
|
if (rebuilt) this.lastCanonicalHermesSyncMtimeMs = stat.mtimeMs;
|
|
952
980
|
} else if (canonicalHistory.format === 'claude-jsonl') {
|
|
981
|
+
// Throttle: only check for changes at most once per 2s
|
|
982
|
+
const now = Date.now();
|
|
983
|
+
if (now - this.lastCanonicalClaudeCheckAt < 2_000 && this.lastCanonicalClaudeRebuildMtimeMs !== 0) {
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
986
|
+
this.lastCanonicalClaudeCheckAt = now;
|
|
987
|
+
// Only rebuild if the transcript file has changed since last rebuild
|
|
988
|
+
const claudeProjectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
989
|
+
const workspaceSegment = typeof this.workingDir === 'string'
|
|
990
|
+
? this.workingDir.replace(/[\\/]/g, '-').replace(/^-+/, '')
|
|
991
|
+
: '';
|
|
992
|
+
const transcriptFile = path.join(claudeProjectsDir, workspaceSegment, `${this.providerSessionId}.jsonl`);
|
|
993
|
+
let transcriptMtime = 0;
|
|
994
|
+
try { transcriptMtime = fs.statSync(transcriptFile).mtimeMs; } catch { /* not found yet */ }
|
|
995
|
+
if (transcriptMtime > 0 && transcriptMtime <= this.lastCanonicalClaudeRebuildMtimeMs) return true;
|
|
953
996
|
rebuilt = rebuildClaudeSavedHistoryFromNativeProject(this.providerSessionId, this.workingDir);
|
|
997
|
+
if (rebuilt) this.lastCanonicalClaudeRebuildMtimeMs = transcriptMtime || Date.now();
|
|
954
998
|
}
|
|
955
999
|
if (!rebuilt) return false;
|
|
956
1000
|
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
@@ -1023,24 +1067,24 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1023
1067
|
}
|
|
1024
1068
|
|
|
1025
1069
|
private querySqliteText(dbPath: string, query: string, params: Array<string | number>): string | null {
|
|
1026
|
-
let db: {
|
|
1027
|
-
prepare(sql: string): { get(...values: Array<string | number>): unknown };
|
|
1028
|
-
close(): void;
|
|
1029
|
-
} | null = null;
|
|
1030
1070
|
try {
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1071
|
+
if (this.cachedSqliteDb === null || this.cachedSqliteDbPath !== dbPath) {
|
|
1072
|
+
try { this.cachedSqliteDb?.close(); } catch { /* noop */ }
|
|
1073
|
+
this.cachedSqliteDb = null;
|
|
1074
|
+
this.cachedSqliteDbPath = null;
|
|
1075
|
+
const DatabaseSync = getDatabaseSync();
|
|
1076
|
+
this.cachedSqliteDb = new DatabaseSync(dbPath, { readOnly: true });
|
|
1077
|
+
this.cachedSqliteDbPath = dbPath;
|
|
1078
|
+
}
|
|
1079
|
+
const row = this.cachedSqliteDb!.prepare(query).get(...params) as { id?: unknown } | undefined;
|
|
1034
1080
|
const sessionId = typeof row?.id === 'string' ? row.id.trim() : '';
|
|
1035
1081
|
return sessionId || null;
|
|
1036
1082
|
} catch {
|
|
1083
|
+
// Close cached connection on error so we retry fresh next tick
|
|
1084
|
+
try { this.cachedSqliteDb?.close(); } catch { /* noop */ }
|
|
1085
|
+
this.cachedSqliteDb = null;
|
|
1086
|
+
this.cachedSqliteDbPath = null;
|
|
1037
1087
|
return null;
|
|
1038
|
-
} finally {
|
|
1039
|
-
try {
|
|
1040
|
-
db?.close();
|
|
1041
|
-
} catch {
|
|
1042
|
-
// noop
|
|
1043
|
-
}
|
|
1044
1088
|
}
|
|
1045
1089
|
}
|
|
1046
1090
|
}
|