@adhdev/daemon-core 0.9.4 → 0.9.5
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 +4 -3
- package/dist/daemon/dev-auto-implement.d.ts +6 -0
- package/dist/index.js +137 -179
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +137 -179
- package/dist/index.mjs.map +1 -1
- package/dist/providers/contracts.d.ts +111 -0
- package/dist/providers/provider-session-id.d.ts +6 -2
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/commands/cli-manager.ts +19 -10
- package/src/commands/stream-commands.ts +2 -3
- package/src/config/chat-history.ts +33 -32
- package/src/config/state-store.ts +6 -14
- package/src/daemon/dev-auto-implement.ts +36 -45
- package/src/providers/cli-provider-instance.ts +34 -69
- package/src/providers/contracts.ts +115 -0
- package/src/providers/provider-schema.ts +5 -0
- package/src/providers/provider-session-id.ts +17 -15
|
@@ -260,37 +260,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
260
260
|
|
|
261
261
|
async onTick(): Promise<void> {
|
|
262
262
|
if (this.providerSessionId) return;
|
|
263
|
-
if (this.
|
|
263
|
+
if (this.provider.resume?.skipProbeOnNewSession && this.launchMode === 'new') return;
|
|
264
264
|
|
|
265
|
-
let probedSessionId: string | null = null;
|
|
266
|
-
|
|
267
|
-
// Prefer declarative probe from provider.json schema
|
|
268
265
|
const probeConfig = this.provider.sessionProbe;
|
|
269
|
-
if (probeConfig)
|
|
270
|
-
probedSessionId = this.probeSessionIdFromConfig(probeConfig);
|
|
271
|
-
} else {
|
|
272
|
-
// Legacy hardcoded probes (backward compat until providers migrate)
|
|
273
|
-
if (this.type === 'opencode-cli') {
|
|
274
|
-
probedSessionId = this.probeSessionIdFromConfig({
|
|
275
|
-
dbPath: '~/.local/share/opencode/opencode.db',
|
|
276
|
-
query: 'select id from session where directory in ({dirs}) and time_created >= ? and time_archived is null order by time_updated desc limit 1',
|
|
277
|
-
timestampFormat: 'unix_ms',
|
|
278
|
-
});
|
|
279
|
-
} else if (this.type === 'codex-cli') {
|
|
280
|
-
probedSessionId = this.probeSessionIdFromConfig({
|
|
281
|
-
dbPath: '~/.codex/state_5.sqlite',
|
|
282
|
-
query: 'select id from threads where cwd in ({dirs}) and updated_at >= ? and archived = 0 order by updated_at desc limit 1',
|
|
283
|
-
timestampFormat: 'unix_s',
|
|
284
|
-
});
|
|
285
|
-
} else if (this.type === 'goose-cli') {
|
|
286
|
-
probedSessionId = this.probeSessionIdFromConfig({
|
|
287
|
-
dbPath: '~/.local/share/goose/sessions/sessions.db',
|
|
288
|
-
query: 'select id from sessions where working_dir in ({dirs}) and created_at >= ? order by updated_at desc limit 1',
|
|
289
|
-
timestampFormat: 'iso',
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
266
|
+
if (!probeConfig) return;
|
|
293
267
|
|
|
268
|
+
const probedSessionId = this.probeSessionIdFromConfig(probeConfig);
|
|
294
269
|
if (probedSessionId) {
|
|
295
270
|
this.promoteProviderSessionId(probedSessionId);
|
|
296
271
|
}
|
|
@@ -355,7 +330,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
355
330
|
? 'error'
|
|
356
331
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
357
332
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
358
|
-
this.
|
|
333
|
+
this.provider,
|
|
359
334
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
360
335
|
);
|
|
361
336
|
if (parsedProviderSessionId) {
|
|
@@ -684,7 +659,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
684
659
|
if (!data || typeof data !== 'object') return;
|
|
685
660
|
|
|
686
661
|
const patchedProviderSessionId = normalizeProviderSessionId(
|
|
687
|
-
this.
|
|
662
|
+
this.provider,
|
|
688
663
|
typeof data.providerSessionId === 'string' ? data.providerSessionId : '',
|
|
689
664
|
);
|
|
690
665
|
if (patchedProviderSessionId) {
|
|
@@ -960,53 +935,43 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
960
935
|
|
|
961
936
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
962
937
|
if (!this.providerSessionId) return false;
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
938
|
+
const canonicalHistory = this.provider.canonicalHistory;
|
|
939
|
+
if (!canonicalHistory) return false;
|
|
940
|
+
|
|
941
|
+
try {
|
|
942
|
+
let rebuilt = false;
|
|
943
|
+
if (canonicalHistory.format === 'hermes-json') {
|
|
944
|
+
const watchPath = canonicalHistory.watchPath
|
|
945
|
+
.replace(/^~/, os.homedir())
|
|
946
|
+
.replace('{{sessionId}}', this.providerSessionId);
|
|
947
|
+
if (!fs.existsSync(watchPath)) return false;
|
|
948
|
+
const stat = fs.statSync(watchPath);
|
|
968
949
|
if (stat.mtimeMs <= this.lastCanonicalHermesSyncMtimeMs) return true;
|
|
969
|
-
|
|
970
|
-
if (
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
974
|
-
role: message.role,
|
|
975
|
-
content: message.content,
|
|
976
|
-
kind: message.kind,
|
|
977
|
-
senderName: message.senderName,
|
|
978
|
-
receivedAt: message.receivedAt,
|
|
979
|
-
}));
|
|
980
|
-
return true;
|
|
981
|
-
} catch {
|
|
982
|
-
return false;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
if (this.type === 'claude-cli') {
|
|
986
|
-
try {
|
|
987
|
-
const rebuilt = rebuildClaudeSavedHistoryFromNativeProject(this.providerSessionId, this.workingDir);
|
|
988
|
-
if (!rebuilt) return false;
|
|
989
|
-
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId);
|
|
990
|
-
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
991
|
-
role: message.role,
|
|
992
|
-
content: message.content,
|
|
993
|
-
kind: message.kind,
|
|
994
|
-
senderName: message.senderName,
|
|
995
|
-
receivedAt: message.receivedAt,
|
|
996
|
-
}));
|
|
997
|
-
return true;
|
|
998
|
-
} catch {
|
|
999
|
-
return false;
|
|
950
|
+
rebuilt = rebuildHermesSavedHistoryFromCanonicalSession(this.providerSessionId);
|
|
951
|
+
if (rebuilt) this.lastCanonicalHermesSyncMtimeMs = stat.mtimeMs;
|
|
952
|
+
} else if (canonicalHistory.format === 'claude-jsonl') {
|
|
953
|
+
rebuilt = rebuildClaudeSavedHistoryFromNativeProject(this.providerSessionId, this.workingDir);
|
|
1000
954
|
}
|
|
955
|
+
if (!rebuilt) return false;
|
|
956
|
+
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
957
|
+
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
958
|
+
role: message.role,
|
|
959
|
+
content: message.content,
|
|
960
|
+
kind: message.kind,
|
|
961
|
+
senderName: message.senderName,
|
|
962
|
+
receivedAt: message.receivedAt,
|
|
963
|
+
}));
|
|
964
|
+
return true;
|
|
965
|
+
} catch {
|
|
966
|
+
return false;
|
|
1001
967
|
}
|
|
1002
|
-
return false;
|
|
1003
968
|
}
|
|
1004
969
|
|
|
1005
970
|
private restorePersistedHistoryFromCurrentSession(): void {
|
|
1006
971
|
if (!this.providerSessionId) return;
|
|
1007
972
|
this.syncCanonicalSavedHistoryIfNeeded();
|
|
1008
|
-
this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
|
|
1009
|
-
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId);
|
|
973
|
+
this.historyWriter.compactHistorySession(this.type, this.providerSessionId, this.provider.historyBehavior);
|
|
974
|
+
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
1010
975
|
this.historyWriter.seedSessionHistory(
|
|
1011
976
|
this.type,
|
|
1012
977
|
restoredHistory.messages,
|
|
@@ -418,6 +418,13 @@ export interface ProviderModule {
|
|
|
418
418
|
extensionIdPattern_flags?: string;
|
|
419
419
|
compatibility?: ProviderCompatibilityEntry[];
|
|
420
420
|
defaultScriptDir?: string;
|
|
421
|
+
/**
|
|
422
|
+
* Scripts that can run at the IDE main-page level (not just inside the extension webview session frame).
|
|
423
|
+
* Default: ['listModes', 'setMode', 'listModels', 'setModel'].
|
|
424
|
+
* Add extra scripts here if the provider supports them at the IDE level (e.g. 'setModelGui').
|
|
425
|
+
* Replaces hardcoded claude-code-vscode special-case in stream-commands.ts.
|
|
426
|
+
*/
|
|
427
|
+
ideLevelScripts?: string[];
|
|
421
428
|
|
|
422
429
|
// ─── CLI category only ───
|
|
423
430
|
binary?: string;
|
|
@@ -426,6 +433,8 @@ export interface ProviderModule {
|
|
|
426
433
|
args?: string[];
|
|
427
434
|
shell?: boolean;
|
|
428
435
|
env?: Record<string, string>;
|
|
436
|
+
/** Auto-implement spawn config — controls how this provider is invoked for autonomous script generation */
|
|
437
|
+
autoImpl?: ProviderAutoImplSpawnConfig;
|
|
429
438
|
};
|
|
430
439
|
/** Delay before submitting typed CLI input (provider-specific TUI tuning) */
|
|
431
440
|
sendDelayMs?: number;
|
|
@@ -451,6 +460,24 @@ export interface ProviderModule {
|
|
|
451
460
|
allowInputDuringGeneration?: boolean;
|
|
452
461
|
/** Approval button priority hints used when auto-approve must pick a positive action */
|
|
453
462
|
approvalPositiveHints?: string[];
|
|
463
|
+
/**
|
|
464
|
+
* Regex pattern (as string) that a valid provider session ID must match.
|
|
465
|
+
* If set and the ID doesn't match, it is rejected (treated as invalid).
|
|
466
|
+
* Replaces hardcoded HERMES_SESSION_ID_RE / CLAUDE_SESSION_ID_RE checks.
|
|
467
|
+
*/
|
|
468
|
+
sessionIdPattern?: string;
|
|
469
|
+
/** History behavior config — controls message filtering and collapse during replay */
|
|
470
|
+
historyBehavior?: ProviderHistoryBehavior;
|
|
471
|
+
/**
|
|
472
|
+
* Canonical history sync config — for providers that maintain native history files.
|
|
473
|
+
* When set, daemon syncs from native format into ADHDev JSONL store on each tick.
|
|
474
|
+
*/
|
|
475
|
+
canonicalHistory?: ProviderCanonicalHistoryConfig;
|
|
476
|
+
/**
|
|
477
|
+
* Auto-fix verification profile — provider-specific test expectations for `provider fix`.
|
|
478
|
+
* If not set, provider fix runs without pre/post verification.
|
|
479
|
+
*/
|
|
480
|
+
autoFixProfile?: ProviderAutoFixProfile;
|
|
454
481
|
|
|
455
482
|
// ─── CDP scripts (ide/extension category) ───
|
|
456
483
|
scripts?: ProviderScripts;
|
|
@@ -538,6 +565,94 @@ export interface ProviderResumeCapability {
|
|
|
538
565
|
resumeSessionArgs?: string[];
|
|
539
566
|
newSessionArgs?: string[];
|
|
540
567
|
sessionIdFormat?: 'uuid' | 'string';
|
|
568
|
+
/** Skip session ID probing when launchMode is 'new' — for providers that manage their own session IDs on new sessions */
|
|
569
|
+
skipProbeOnNewSession?: boolean;
|
|
570
|
+
/**
|
|
571
|
+
* Subcommands that carry a session ID as their next positional argument.
|
|
572
|
+
* e.g. ['resume', 'fork'] for codex-cli (codex resume <id> / codex fork <id>).
|
|
573
|
+
* Replaces the hardcoded readCodexResumeSessionId check in cli-manager.ts.
|
|
574
|
+
*/
|
|
575
|
+
sessionIdFromSubcommand?: string[];
|
|
576
|
+
/**
|
|
577
|
+
* When --session-id is present without an explicit resume flag, treat as 'new' rather than 'resume'.
|
|
578
|
+
* e.g. goose-cli passes --session-id on new sessions but requires --resume/-r to actually resume.
|
|
579
|
+
* Replaces the hardcoded goose-cli check in cli-manager.ts.
|
|
580
|
+
*/
|
|
581
|
+
sessionIdIsNewByDefault?: boolean;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* History behavior config — controls how history messages are processed for this provider.
|
|
586
|
+
* Replaces hardcoded agentType checks in chat-history.ts.
|
|
587
|
+
*/
|
|
588
|
+
export interface ProviderHistoryBehavior {
|
|
589
|
+
/** Collapse consecutive assistant turns during history replay (e.g. codex-cli shows replayed intermediate turns) */
|
|
590
|
+
collapseConsecutiveAssistantTurns?: boolean;
|
|
591
|
+
/** Regex patterns (as strings) to filter out from assistant messages — e.g. CLI starter prompt suggestions */
|
|
592
|
+
filterAssistantPatterns?: string[];
|
|
593
|
+
/** If true, session ID must match sessionIdPattern exactly — reject and return '' if it doesn't match */
|
|
594
|
+
requireStrictSessionIdFormat?: boolean;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Canonical history sync config — for providers that maintain their own native history files.
|
|
599
|
+
* When set, daemon syncs from the provider's native format into the ADHDev JSONL store.
|
|
600
|
+
* Replaces hardcoded hermes-cli / claude-cli checks in cli-provider-instance.ts.
|
|
601
|
+
*/
|
|
602
|
+
export interface ProviderCanonicalHistoryConfig {
|
|
603
|
+
/**
|
|
604
|
+
* Native history format.
|
|
605
|
+
* - 'hermes-json': single JSON file per session (~/.hermes/sessions/session_{{sessionId}}.json)
|
|
606
|
+
* - 'claude-jsonl': JSONL transcript under ~/.claude/projects/
|
|
607
|
+
*/
|
|
608
|
+
format: 'hermes-json' | 'claude-jsonl';
|
|
609
|
+
/**
|
|
610
|
+
* Path to the native history file. Supports ~ and {{sessionId}} placeholder.
|
|
611
|
+
* e.g. "~/.hermes/sessions/session_{{sessionId}}.json"
|
|
612
|
+
*/
|
|
613
|
+
watchPath: string;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Auto-implement spawn config — controls how the provider is spawned for autonomous AI-driven
|
|
618
|
+
* provider script implementation (dev-auto-implement.ts).
|
|
619
|
+
* Replaces hardcoded per-command branching.
|
|
620
|
+
*/
|
|
621
|
+
export interface ProviderAutoImplSpawnConfig {
|
|
622
|
+
/**
|
|
623
|
+
* How the meta-prompt is passed to the agent.
|
|
624
|
+
* - 'flag': passed via a CLI flag (e.g. `claude -p "..."`)
|
|
625
|
+
* - 'stdin': piped via stdin (generic fallback)
|
|
626
|
+
* - 'subcommand': prepended as a subcommand (e.g. `codex exec "..."`)
|
|
627
|
+
*/
|
|
628
|
+
promptMode: 'flag' | 'stdin' | 'subcommand';
|
|
629
|
+
/** CLI flag used to pass the prompt (promptMode: 'flag') — e.g. '-p' */
|
|
630
|
+
promptFlag?: string;
|
|
631
|
+
/** Subcommand prepended before the prompt (promptMode: 'subcommand') — e.g. 'exec' */
|
|
632
|
+
subcommand?: string;
|
|
633
|
+
/** Extra args appended in auto-impl mode — e.g. ['--dangerously-skip-permissions'] */
|
|
634
|
+
extraArgs?: string[];
|
|
635
|
+
/** Custom meta-prompt template; use {{promptFile}} placeholder. If omitted, generic prompt is used. */
|
|
636
|
+
metaPrompt?: string;
|
|
637
|
+
/**
|
|
638
|
+
* If true, schedule an auto-stop timer when the agent output goes quiet during verification.
|
|
639
|
+
* Replaces the hardcoded `command !== 'codex'` check in dev-auto-implement.ts.
|
|
640
|
+
*/
|
|
641
|
+
autoStopOnQuiet?: boolean;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Auto-fix verification profile — provider-specific test expectations for `provider fix`.
|
|
646
|
+
* Replaces the hardcoded CLI_AUTO_FIX_VERIFICATION_PROFILES record in provider-commands.ts.
|
|
647
|
+
*/
|
|
648
|
+
export interface ProviderAutoFixProfile {
|
|
649
|
+
fixtureName: string;
|
|
650
|
+
description: string;
|
|
651
|
+
inspectFields?: string[];
|
|
652
|
+
focusAreas?: string[];
|
|
653
|
+
lastAssistantMustContainAny?: string[];
|
|
654
|
+
lastAssistantMustNotContainAny?: string[];
|
|
655
|
+
timeoutMs?: number;
|
|
541
656
|
}
|
|
542
657
|
|
|
543
658
|
/**
|
|
@@ -32,6 +32,11 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
|
|
|
32
32
|
'resume',
|
|
33
33
|
'sessionProbe',
|
|
34
34
|
'approvalPositiveHints',
|
|
35
|
+
'sessionIdPattern',
|
|
36
|
+
'historyBehavior',
|
|
37
|
+
'canonicalHistory',
|
|
38
|
+
'autoFixProfile',
|
|
39
|
+
'ideLevelScripts',
|
|
35
40
|
'scripts',
|
|
36
41
|
'vscodeCommands',
|
|
37
42
|
'inputMethod',
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
const CLAUDE_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
1
|
+
import type { ProviderModule } from './contracts.js'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Normalize and validate a provider session ID using the declarative `sessionIdPattern`
|
|
5
|
+
* from the provider's ProviderModule definition.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeProviderSessionId(
|
|
8
|
+
provider: ProviderModule | undefined,
|
|
9
|
+
providerSessionId: string | null | undefined,
|
|
10
|
+
): string {
|
|
6
11
|
const normalizedId = typeof providerSessionId === 'string' ? providerSessionId.trim() : ''
|
|
7
12
|
if (!normalizedId) return ''
|
|
8
13
|
|
|
9
14
|
const lowered = normalizedId.toLowerCase()
|
|
10
15
|
if (lowered === 'undefined' || lowered === 'null') return ''
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
const sessionIdPattern = provider?.sessionIdPattern
|
|
18
|
+
if (sessionIdPattern) {
|
|
19
|
+
try {
|
|
20
|
+
const re = new RegExp(sessionIdPattern, 'i')
|
|
21
|
+
if (!re.test(normalizedId)) return ''
|
|
22
|
+
} catch {
|
|
23
|
+
// Invalid regex in provider.json — skip validation
|
|
24
|
+
}
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
return normalizedId
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
export function isLegacyVolatileSessionReadKey(key: string | null | undefined): boolean {
|
|
23
|
-
const normalizedKey = typeof key === 'string' ? key.trim() : ''
|
|
24
|
-
if (!normalizedKey) return false
|
|
25
|
-
return normalizedKey.startsWith('provider:codex:vscode-webview://')
|
|
26
|
-
}
|