@adhdev/daemon-core 0.9.43 → 0.9.45
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 +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +6 -0
- package/dist/config/chat-history.d.ts +30 -9
- package/dist/index.js +724 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +724 -39
- package/dist/index.mjs.map +1 -1
- package/dist/launch/macos-app-process.d.ts +2 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -0
- package/dist/providers/contracts.d.ts +14 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +105 -6
- package/src/cli-adapters/provider-cli-shared.ts +6 -0
- package/src/commands/chat-commands.ts +27 -3
- package/src/commands/router.ts +9 -3
- package/src/config/chat-history.ts +541 -24
- package/src/launch/macos-app-process.ts +37 -0
- package/src/launch.ts +43 -5
- package/src/providers/cli-provider-instance.ts +52 -3
- package/src/providers/contracts.ts +15 -2
- package/src/providers/read-chat-contract.ts +2 -0
|
@@ -55,6 +55,8 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
55
55
|
private lastCanonicalHermesWatchPath;
|
|
56
56
|
private lastCanonicalClaudeRebuildMtimeMs;
|
|
57
57
|
private lastCanonicalClaudeCheckAt;
|
|
58
|
+
private lastCanonicalCodexRebuildMtimeMs;
|
|
59
|
+
private lastCanonicalCodexCheckAt;
|
|
58
60
|
private cachedSqliteDb;
|
|
59
61
|
private cachedSqliteDbPath;
|
|
60
62
|
private cachedSqliteDbMissingUntil;
|
|
@@ -31,6 +31,9 @@ export interface ReadChatResult {
|
|
|
31
31
|
controlValues?: Record<string, string | number | boolean>;
|
|
32
32
|
/** Flexible always-visible metadata for compact/live surfaces. */
|
|
33
33
|
summaryMetadata?: ProviderSummaryMetadata;
|
|
34
|
+
/** Provider-owned transcript authority/coverage hints for daemon/dashboard sync. */
|
|
35
|
+
transcriptAuthority?: 'provider' | 'daemon';
|
|
36
|
+
coverage?: 'full' | 'tail' | 'current-turn';
|
|
34
37
|
/** Provider-driven UI effects derived from chat state */
|
|
35
38
|
effects?: ProviderEffect[];
|
|
36
39
|
}
|
|
@@ -506,13 +509,23 @@ export interface ProviderCanonicalHistoryConfig {
|
|
|
506
509
|
* Native history format.
|
|
507
510
|
* - 'hermes-json': single JSON file per session (~/.hermes/sessions/session_{{sessionId}}.json)
|
|
508
511
|
* - 'claude-jsonl': JSONL transcript under ~/.claude/projects/
|
|
512
|
+
* - 'codex-jsonl': rollout JSONL transcript under ~/.codex/sessions/YYYY/MM/DD/
|
|
509
513
|
*/
|
|
510
|
-
format: 'hermes-json' | 'claude-jsonl';
|
|
514
|
+
format: 'hermes-json' | 'claude-jsonl' | 'codex-jsonl';
|
|
511
515
|
/**
|
|
512
516
|
* Path to the native history file. Supports ~ and {{sessionId}} placeholder.
|
|
513
517
|
* e.g. "~/.hermes/sessions/session_{{sessionId}}.json"
|
|
514
518
|
*/
|
|
515
519
|
watchPath: string;
|
|
520
|
+
/**
|
|
521
|
+
* How ADHDev should use native history.
|
|
522
|
+
* - 'native-source': provider-native files are canonical; ADHDev reads them directly and keeps only in-memory/thin projections.
|
|
523
|
+
* - 'materialized-mirror': transitional compatibility mode; native files are rewritten into ~/.adhdev/history before read/list.
|
|
524
|
+
* - 'disabled': ignore native history and use ADHDev mirror only.
|
|
525
|
+
*
|
|
526
|
+
* Omitted mode defaults to 'native-source'.
|
|
527
|
+
*/
|
|
528
|
+
mode?: 'native-source' | 'materialized-mirror' | 'disabled';
|
|
516
529
|
}
|
|
517
530
|
/**
|
|
518
531
|
* Auto-implement spawn config — controls how the provider is spawned for autonomous AI-driven
|
package/package.json
CHANGED
|
@@ -171,6 +171,88 @@ export function appendBoundedText(current: string, chunk: string, maxChars: numb
|
|
|
171
171
|
return current.slice(-keepFromCurrent) + chunk;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
const COMMITTED_ACTIVITY_PREFIX_BLOCK_RE = /^(?:📖|💻|🔎|📚|📋|✏️|📝|🔧|🛠️|⚙️)\s+(.+)$/;
|
|
175
|
+
|
|
176
|
+
function isLikelyCommittedActivityPrefixContinuation(line: string): boolean {
|
|
177
|
+
const trimmed = String(line || '').trim();
|
|
178
|
+
if (!trimmed) return false;
|
|
179
|
+
if (COMMITTED_ACTIVITY_PREFIX_BLOCK_RE.test(trimmed)) return false;
|
|
180
|
+
if (/\s/.test(trimmed)) return false;
|
|
181
|
+
if (/[가-힣]/.test(trimmed)) return false;
|
|
182
|
+
if (trimmed.length > 96) return false;
|
|
183
|
+
return /^[A-Za-z0-9_./:@+%=-]+$/.test(trimmed);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseCommittedActivityPrefixBlock(lines: string[], index: number): { label: string; nextIndex: number } | null {
|
|
187
|
+
const first = String(lines[index] || '').trim();
|
|
188
|
+
if (!COMMITTED_ACTIVITY_PREFIX_BLOCK_RE.test(first)) return null;
|
|
189
|
+
const parts = [first];
|
|
190
|
+
let nextIndex = index + 1;
|
|
191
|
+
while (nextIndex < lines.length && isLikelyCommittedActivityPrefixContinuation(lines[nextIndex])) {
|
|
192
|
+
parts.push(String(lines[nextIndex] || '').trim());
|
|
193
|
+
nextIndex += 1;
|
|
194
|
+
}
|
|
195
|
+
return { label: parts.join(''), nextIndex };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function sanitizeCliStandardMessageContent(content: unknown): string {
|
|
199
|
+
const source = String(content || '').trim();
|
|
200
|
+
if (!source) return '';
|
|
201
|
+
const lines = source.split(/\r?\n/);
|
|
202
|
+
if (lines.length < 4) return source;
|
|
203
|
+
|
|
204
|
+
const counts = new Map<string, number>();
|
|
205
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
206
|
+
const block = parseCommittedActivityPrefixBlock(lines, index);
|
|
207
|
+
if (!block) continue;
|
|
208
|
+
counts.set(block.label, (counts.get(block.label) || 0) + 1);
|
|
209
|
+
index = block.nextIndex - 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const repeatedLabels = new Set(
|
|
213
|
+
Array.from(counts.entries())
|
|
214
|
+
.filter(([, count]) => count >= 3)
|
|
215
|
+
.map(([label]) => label),
|
|
216
|
+
);
|
|
217
|
+
if (repeatedLabels.size === 0) return source;
|
|
218
|
+
|
|
219
|
+
const stripped: string[] = [];
|
|
220
|
+
let removed = 0;
|
|
221
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
222
|
+
const block = parseCommittedActivityPrefixBlock(lines, index);
|
|
223
|
+
if (block && repeatedLabels.has(block.label)) {
|
|
224
|
+
removed += 1;
|
|
225
|
+
index = block.nextIndex - 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
stripped.push(lines[index]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const next = stripped.join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
232
|
+
return removed >= 3 && next.length >= 80 ? next : source;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function sanitizeCommittedMessageForDisplay<T extends { role?: string; kind?: string; content?: unknown }>(message: T): T {
|
|
236
|
+
if (!message || message.role !== 'assistant' || (message.kind || 'standard') !== 'standard') return message;
|
|
237
|
+
const content = sanitizeCliStandardMessageContent(message.content);
|
|
238
|
+
if (content === message.content) return message;
|
|
239
|
+
return { ...message, content };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function trimLastAssistantEchoForCliMessages(messages: CliChatMessage[], prompt: string | undefined): void {
|
|
243
|
+
if (!prompt) return;
|
|
244
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
245
|
+
const message = messages[index];
|
|
246
|
+
if (!message || message.role !== 'assistant' || typeof message.content !== 'string') continue;
|
|
247
|
+
if ((message.kind || 'standard') !== 'standard') continue;
|
|
248
|
+
message.content = trimPromptEchoPrefix(message.content, prompt);
|
|
249
|
+
if (!message.content.trim()) {
|
|
250
|
+
messages.splice(index, 1);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
174
256
|
// ─── Adapter ────────────────────────────────────────
|
|
175
257
|
|
|
176
258
|
export class ProviderCliAdapter implements CliAdapter {
|
|
@@ -360,7 +442,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
360
442
|
return null;
|
|
361
443
|
}
|
|
362
444
|
|
|
445
|
+
private providerOwnsTranscript(): boolean {
|
|
446
|
+
return this.provider.transcriptAuthority === 'provider';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private shouldUseFullProviderTranscriptContext(): boolean {
|
|
450
|
+
return this.providerOwnsTranscript() && this.provider.transcriptContext === 'full';
|
|
451
|
+
}
|
|
452
|
+
|
|
363
453
|
private selectParseBaseMessages(baseMessages: CliChatMessage[]): CliChatMessage[] {
|
|
454
|
+
if (this.shouldUseFullProviderTranscriptContext()) return baseMessages;
|
|
364
455
|
if (baseMessages.length <= ProviderCliAdapter.PARSE_MESSAGE_TAIL_LIMIT) return baseMessages;
|
|
365
456
|
return baseMessages.slice(-ProviderCliAdapter.PARSE_MESSAGE_TAIL_LIMIT);
|
|
366
457
|
}
|
|
@@ -400,7 +491,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
400
491
|
const tailFirst = parseBaseMessages[0];
|
|
401
492
|
if (tailFirst && this.messagesComparable(parsedFirst, tailFirst)) {
|
|
402
493
|
const prefixLength = fullBaseMessages.length - parseBaseMessages.length;
|
|
403
|
-
|
|
494
|
+
const prefix = fullBaseMessages.slice(0, prefixLength);
|
|
495
|
+
const shouldSanitizePrefix = !!this.currentTurnScope || this.currentStatus !== 'idle' || !!this.activeModal;
|
|
496
|
+
const nextPrefix = shouldSanitizePrefix
|
|
497
|
+
? prefix.map((message) => sanitizeCommittedMessageForDisplay(message))
|
|
498
|
+
: prefix;
|
|
499
|
+
return [...nextPrefix, ...parsedMessages];
|
|
404
500
|
}
|
|
405
501
|
|
|
406
502
|
return [...fullBaseMessages, ...parsedMessages];
|
|
@@ -971,9 +1067,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
971
1067
|
}
|
|
972
1068
|
|
|
973
1069
|
private trimLastAssistantEcho(messages: CliChatMessage[], prompt: string | undefined): void {
|
|
974
|
-
|
|
975
|
-
const last = [...messages].reverse().find((m) => m.role === 'assistant' && typeof m.content === 'string');
|
|
976
|
-
if (last) last.content = trimPromptEchoPrefix(last.content, prompt);
|
|
1070
|
+
trimLastAssistantEchoForCliMessages(messages, prompt);
|
|
977
1071
|
}
|
|
978
1072
|
|
|
979
1073
|
private clearAllTimers(): void {
|
|
@@ -1808,10 +1902,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1808
1902
|
|
|
1809
1903
|
private buildCommittedChatMessages(): any[] {
|
|
1810
1904
|
return this.committedMessages.map((message, index) => {
|
|
1811
|
-
const
|
|
1905
|
+
const rawContentValue = message.content;
|
|
1906
|
+
const rawContent = typeof rawContentValue === 'string' ? rawContentValue : String(rawContentValue || '');
|
|
1907
|
+
const content = message.role === 'assistant' && (message.kind || 'standard') === 'standard'
|
|
1908
|
+
? sanitizeCliStandardMessageContent(rawContent)
|
|
1909
|
+
: rawContent;
|
|
1812
1910
|
return buildChatMessage({
|
|
1813
1911
|
role: message.role,
|
|
1814
|
-
content
|
|
1912
|
+
content,
|
|
1815
1913
|
timestamp: message.timestamp,
|
|
1816
1914
|
kind: message.kind,
|
|
1817
1915
|
meta: message.meta,
|
|
@@ -1956,6 +2054,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1956
2054
|
messages: hydratedMessages,
|
|
1957
2055
|
activeModal: parsed.activeModal ?? this.activeModal,
|
|
1958
2056
|
providerSessionId: typeof parsed.providerSessionId === 'string' ? parsed.providerSessionId : undefined,
|
|
2057
|
+
...(this.providerOwnsTranscript() ? { transcriptAuthority: 'provider', coverage: this.shouldUseFullProviderTranscriptContext() ? 'full' : 'tail' } : {}),
|
|
1959
2058
|
};
|
|
1960
2059
|
} else {
|
|
1961
2060
|
const messages = [...this.committedMessages];
|
|
@@ -31,6 +31,8 @@ export interface ParsedSession {
|
|
|
31
31
|
messages: any[];
|
|
32
32
|
modal: { message: string; buttons: string[] } | null;
|
|
33
33
|
parsedStatus: string | null;
|
|
34
|
+
transcriptAuthority?: 'provider' | 'daemon';
|
|
35
|
+
coverage?: 'full' | 'tail' | 'current-turn';
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export interface CliScripts {
|
|
@@ -122,6 +124,10 @@ export interface CliProviderModule {
|
|
|
122
124
|
submitStrategy?: 'wait_for_echo' | 'immediate';
|
|
123
125
|
/** Allow sending another prompt while the CLI is still generating so users can intervene mid-turn. */
|
|
124
126
|
allowInputDuringGeneration?: boolean;
|
|
127
|
+
/** When provider-owned, daemon treats provider parser output as canonical transcript authority. */
|
|
128
|
+
transcriptAuthority?: 'provider' | 'daemon';
|
|
129
|
+
/** Full context lets provider-owned parsers canonicalize retained history instead of daemon prefix stitching. */
|
|
130
|
+
transcriptContext?: 'full' | 'tail';
|
|
125
131
|
scripts?: CliScripts;
|
|
126
132
|
spawn: {
|
|
127
133
|
command: string;
|
|
@@ -9,7 +9,7 @@ import { flattenContent, normalizeInputEnvelope, type InputEnvelope, type Provid
|
|
|
9
9
|
import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
|
|
10
10
|
import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
|
|
11
11
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
12
|
-
import {
|
|
12
|
+
import { readProviderChatHistory } from '../config/chat-history.js';
|
|
13
13
|
import { LOG } from '../logging/logger.js';
|
|
14
14
|
import { recordDebugTrace } from '../logging/debug-trace.js';
|
|
15
15
|
import { buildChatMessageSignature } from '../chat/chat-signatures.js';
|
|
@@ -564,7 +564,20 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
564
564
|
const visibleCount = Array.isArray(status?.messages) ? status.messages.length : 0;
|
|
565
565
|
if (visibleCount > excludeRecentCount) excludeRecentCount = visibleCount;
|
|
566
566
|
}
|
|
567
|
-
const
|
|
567
|
+
const workspace = typeof args?.workspace === 'string'
|
|
568
|
+
? args.workspace
|
|
569
|
+
: typeof (h.currentSession as any)?.workspace === 'string'
|
|
570
|
+
? (h.currentSession as any).workspace
|
|
571
|
+
: undefined;
|
|
572
|
+
const result = readProviderChatHistory(agentStr, {
|
|
573
|
+
canonicalHistory: provider?.canonicalHistory,
|
|
574
|
+
historySessionId,
|
|
575
|
+
workspace,
|
|
576
|
+
offset: offset || 0,
|
|
577
|
+
limit: limit || 30,
|
|
578
|
+
excludeRecentCount,
|
|
579
|
+
historyBehavior: provider?.historyBehavior,
|
|
580
|
+
});
|
|
568
581
|
return { success: true, ...result, agent: agentStr };
|
|
569
582
|
} catch (e: any) {
|
|
570
583
|
return { success: false, error: e.message };
|
|
@@ -595,8 +608,11 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
595
608
|
? parsedStatus as Record<string, any>
|
|
596
609
|
: null;
|
|
597
610
|
const adapterStatus = adapter.getStatus();
|
|
611
|
+
const parsedIsProviderAuthoritative = parsedRecord?.transcriptAuthority === 'provider'
|
|
612
|
+
|| parsedRecord?.coverage === 'full';
|
|
598
613
|
const shouldPreferAdapterMessages =
|
|
599
|
-
|
|
614
|
+
!parsedIsProviderAuthoritative
|
|
615
|
+
&& Array.isArray(adapterStatus.messages)
|
|
600
616
|
&& adapterStatus.messages.length > 0
|
|
601
617
|
&& Array.isArray(parsedRecord?.messages)
|
|
602
618
|
&& adapterStatus.messages.length > parsedRecord.messages.length;
|
|
@@ -619,6 +635,12 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
619
635
|
const providerSessionId = typeof parsedRecord?.providerSessionId === 'string'
|
|
620
636
|
? parsedRecord.providerSessionId
|
|
621
637
|
: undefined;
|
|
638
|
+
const transcriptAuthority = parsedRecord?.transcriptAuthority === 'provider' || parsedRecord?.transcriptAuthority === 'daemon'
|
|
639
|
+
? parsedRecord.transcriptAuthority
|
|
640
|
+
: undefined;
|
|
641
|
+
const coverage = parsedRecord?.coverage === 'full' || parsedRecord?.coverage === 'tail' || parsedRecord?.coverage === 'current-turn'
|
|
642
|
+
? parsedRecord.coverage
|
|
643
|
+
: undefined;
|
|
622
644
|
if (status) {
|
|
623
645
|
LOG.debug('Command', `[read_chat] cli-like resolved provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord?.status || '')} shouldPreferAdapterMessages=${String(shouldPreferAdapterMessages)} adapterMsgCount=${Array.isArray(adapterStatus.messages) ? adapterStatus.messages.length : 0} parsedMsgCount=${Array.isArray(parsedRecord?.messages) ? parsedRecord.messages.length : 0} returnedMsgCount=${Array.isArray((status as any).messages) ? (status as any).messages.length : 0}`);
|
|
624
646
|
return buildReadChatCommandResult({
|
|
@@ -638,6 +660,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
638
660
|
},
|
|
639
661
|
...(title ? { title } : {}),
|
|
640
662
|
...(providerSessionId ? { providerSessionId } : {}),
|
|
663
|
+
...(transcriptAuthority ? { transcriptAuthority } : {}),
|
|
664
|
+
...(coverage ? { coverage } : {}),
|
|
641
665
|
}, args);
|
|
642
666
|
}
|
|
643
667
|
}
|
package/src/commands/router.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { loadState, saveState } from '../config/state-store.js';
|
|
|
23
23
|
import { resolveIdeLaunchWorkspace } from '../config/workspaces.js';
|
|
24
24
|
import { appendRecentActivity, getRecentActivity, markSessionSeen, dismissSessionNotification, markSessionNotificationUnread } from '../config/recent-activity.js';
|
|
25
25
|
import { getSavedProviderSessions } from '../config/saved-sessions.js';
|
|
26
|
-
import {
|
|
26
|
+
import { listProviderHistorySessions } from '../config/chat-history.js';
|
|
27
27
|
import { detectIDEs } from '../detection/ide-detector.js';
|
|
28
28
|
import { detectCLI } from '../detection/cli-detector.js';
|
|
29
29
|
import { SessionRegistry } from '../sessions/registry.js';
|
|
@@ -525,14 +525,19 @@ export class DaemonCommandRouter {
|
|
|
525
525
|
const wantsAll = args?.all === true;
|
|
526
526
|
const offset = wantsAll ? 0 : Math.max(0, Number(args?.offset) || 0);
|
|
527
527
|
const limit = wantsAll ? Number.MAX_SAFE_INTEGER : Math.max(1, Math.min(100, Number(args?.limit) || 30));
|
|
528
|
-
const
|
|
528
|
+
const providerMeta = this.deps.providerLoader.getMeta(providerType);
|
|
529
|
+
const { sessions: historySessions, hasMore, source } = listProviderHistorySessions(providerType, {
|
|
530
|
+
canonicalHistory: providerMeta?.canonicalHistory,
|
|
531
|
+
offset,
|
|
532
|
+
limit,
|
|
533
|
+
historyBehavior: providerMeta?.historyBehavior,
|
|
534
|
+
});
|
|
529
535
|
const state = loadState();
|
|
530
536
|
const savedSessions = getSavedProviderSessions(state, { providerType, kind });
|
|
531
537
|
const recentSessions = getRecentActivity(state, 200)
|
|
532
538
|
.filter(entry => entry.providerType === providerType && entry.kind === kind && entry.providerSessionId);
|
|
533
539
|
const savedSessionById = new Map(savedSessions.map(entry => [entry.providerSessionId, entry]));
|
|
534
540
|
const recentSessionById = new Map(recentSessions.map(entry => [entry.providerSessionId!, entry]));
|
|
535
|
-
const providerMeta = this.deps.providerLoader.getMeta(providerType);
|
|
536
541
|
const canResumeById = supportsExplicitSessionResume(providerMeta?.resume);
|
|
537
542
|
|
|
538
543
|
return {
|
|
@@ -557,6 +562,7 @@ export class DaemonCommandRouter {
|
|
|
557
562
|
};
|
|
558
563
|
}),
|
|
559
564
|
hasMore,
|
|
565
|
+
source,
|
|
560
566
|
};
|
|
561
567
|
}
|
|
562
568
|
|