@adhdev/daemon-core 0.9.4 → 0.9.6
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 +194 -186
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +194 -186
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +7 -0
- 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 +92 -83
- package/src/providers/contracts.ts +115 -0
- package/src/providers/provider-schema.ts +6 -0
- package/src/providers/provider-session-id.ts +17 -15
|
@@ -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;
|
|
@@ -260,37 +270,12 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
260
270
|
|
|
261
271
|
async onTick(): Promise<void> {
|
|
262
272
|
if (this.providerSessionId) return;
|
|
263
|
-
if (this.
|
|
273
|
+
if (this.provider.resume?.skipProbeOnNewSession && this.launchMode === 'new') return;
|
|
264
274
|
|
|
265
|
-
let probedSessionId: string | null = null;
|
|
266
|
-
|
|
267
|
-
// Prefer declarative probe from provider.json schema
|
|
268
275
|
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
|
-
}
|
|
276
|
+
if (!probeConfig) return;
|
|
293
277
|
|
|
278
|
+
const probedSessionId = this.probeSessionIdFromConfig(probeConfig);
|
|
294
279
|
if (probedSessionId) {
|
|
295
280
|
this.promoteProviderSessionId(probedSessionId);
|
|
296
281
|
}
|
|
@@ -306,7 +291,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
306
291
|
timestampFormat?: 'unix_ms' | 'unix_s' | 'iso';
|
|
307
292
|
}): string | null {
|
|
308
293
|
const resolvedDbPath = probe.dbPath.replace(/^~/, os.homedir());
|
|
309
|
-
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
|
+
}
|
|
310
301
|
|
|
311
302
|
const directories = this.getProbeDirectories();
|
|
312
303
|
const minCreatedAt = Math.max(0, this.startedAt - 60_000);
|
|
@@ -355,7 +346,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
355
346
|
? 'error'
|
|
356
347
|
: (autoApproveActive ? 'generating' : adapterStatus.status);
|
|
357
348
|
const parsedProviderSessionId = normalizeProviderSessionId(
|
|
358
|
-
this.
|
|
349
|
+
this.provider,
|
|
359
350
|
typeof parsedStatus?.providerSessionId === 'string' ? parsedStatus.providerSessionId : '',
|
|
360
351
|
);
|
|
361
352
|
if (parsedProviderSessionId) {
|
|
@@ -511,6 +502,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
511
502
|
this.adapter.shutdown();
|
|
512
503
|
this.monitor.reset();
|
|
513
504
|
this.appliedEffectKeys.clear();
|
|
505
|
+
try { this.cachedSqliteDb?.close(); } catch { /* noop */ }
|
|
506
|
+
this.cachedSqliteDb = null;
|
|
507
|
+
this.cachedSqliteDbPath = null;
|
|
514
508
|
}
|
|
515
509
|
|
|
516
510
|
private completedDebounceTimer: NodeJS.Timeout | null = null;
|
|
@@ -684,7 +678,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
684
678
|
if (!data || typeof data !== 'object') return;
|
|
685
679
|
|
|
686
680
|
const patchedProviderSessionId = normalizeProviderSessionId(
|
|
687
|
-
this.
|
|
681
|
+
this.provider,
|
|
688
682
|
typeof data.providerSessionId === 'string' ? data.providerSessionId : '',
|
|
689
683
|
);
|
|
690
684
|
if (patchedProviderSessionId) {
|
|
@@ -960,53 +954,68 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
960
954
|
|
|
961
955
|
private syncCanonicalSavedHistoryIfNeeded(): boolean {
|
|
962
956
|
if (!this.providerSessionId) return false;
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
957
|
+
const canonicalHistory = this.provider.canonicalHistory;
|
|
958
|
+
if (!canonicalHistory) return false;
|
|
959
|
+
|
|
960
|
+
try {
|
|
961
|
+
let rebuilt = false;
|
|
962
|
+
if (canonicalHistory.format === 'hermes-json') {
|
|
963
|
+
const watchPath = canonicalHistory.watchPath
|
|
964
|
+
.replace(/^~/, os.homedir())
|
|
965
|
+
.replace('{{sessionId}}', this.providerSessionId);
|
|
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
|
+
}
|
|
976
|
+
const stat = fs.statSync(watchPath);
|
|
968
977
|
if (stat.mtimeMs <= this.lastCanonicalHermesSyncMtimeMs) return true;
|
|
969
|
-
|
|
970
|
-
if (
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
if (
|
|
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;
|
|
978
|
+
rebuilt = rebuildHermesSavedHistoryFromCanonicalSession(this.providerSessionId);
|
|
979
|
+
if (rebuilt) this.lastCanonicalHermesSyncMtimeMs = stat.mtimeMs;
|
|
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;
|
|
996
|
+
rebuilt = rebuildClaudeSavedHistoryFromNativeProject(this.providerSessionId, this.workingDir);
|
|
997
|
+
if (rebuilt) this.lastCanonicalClaudeRebuildMtimeMs = transcriptMtime || Date.now();
|
|
1000
998
|
}
|
|
999
|
+
if (!rebuilt) return false;
|
|
1000
|
+
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
1001
|
+
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
1002
|
+
role: message.role,
|
|
1003
|
+
content: message.content,
|
|
1004
|
+
kind: message.kind,
|
|
1005
|
+
senderName: message.senderName,
|
|
1006
|
+
receivedAt: message.receivedAt,
|
|
1007
|
+
}));
|
|
1008
|
+
return true;
|
|
1009
|
+
} catch {
|
|
1010
|
+
return false;
|
|
1001
1011
|
}
|
|
1002
|
-
return false;
|
|
1003
1012
|
}
|
|
1004
1013
|
|
|
1005
1014
|
private restorePersistedHistoryFromCurrentSession(): void {
|
|
1006
1015
|
if (!this.providerSessionId) return;
|
|
1007
1016
|
this.syncCanonicalSavedHistoryIfNeeded();
|
|
1008
|
-
this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
|
|
1009
|
-
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId);
|
|
1017
|
+
this.historyWriter.compactHistorySession(this.type, this.providerSessionId, this.provider.historyBehavior);
|
|
1018
|
+
const restoredHistory = readChatHistory(this.type, 0, Number.MAX_SAFE_INTEGER, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
1010
1019
|
this.historyWriter.seedSessionHistory(
|
|
1011
1020
|
this.type,
|
|
1012
1021
|
restoredHistory.messages,
|
|
@@ -1058,24 +1067,24 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1058
1067
|
}
|
|
1059
1068
|
|
|
1060
1069
|
private querySqliteText(dbPath: string, query: string, params: Array<string | number>): string | null {
|
|
1061
|
-
let db: {
|
|
1062
|
-
prepare(sql: string): { get(...values: Array<string | number>): unknown };
|
|
1063
|
-
close(): void;
|
|
1064
|
-
} | null = null;
|
|
1065
1070
|
try {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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;
|
|
1069
1080
|
const sessionId = typeof row?.id === 'string' ? row.id.trim() : '';
|
|
1070
1081
|
return sessionId || null;
|
|
1071
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;
|
|
1072
1087
|
return null;
|
|
1073
|
-
} finally {
|
|
1074
|
-
try {
|
|
1075
|
-
db?.close();
|
|
1076
|
-
} catch {
|
|
1077
|
-
// noop
|
|
1078
|
-
}
|
|
1079
1088
|
}
|
|
1080
1089
|
}
|
|
1081
1090
|
}
|
|
@@ -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,12 @@ 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',
|
|
40
|
+
'allowInputDuringGeneration',
|
|
35
41
|
'scripts',
|
|
36
42
|
'vscodeCommands',
|
|
37
43
|
'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
|
-
}
|