@adhdev/daemon-core 0.8.93 → 0.8.94
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/provider-source-config.d.ts +9 -0
- package/dist/index.js +229 -111
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +229 -111
- package/dist/index.mjs.map +1 -1
- package/dist/providers/provider-loader.d.ts +17 -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 +51 -4
- package/src/commands/chat-commands.ts +20 -3
- package/src/config/provider-source-config.ts +10 -0
- package/src/providers/provider-loader.ts +89 -3
|
@@ -33,6 +33,17 @@ export declare class ProviderLoader {
|
|
|
33
33
|
setVersionArchive(archive: VersionArchive): void;
|
|
34
34
|
private static readonly GITHUB_TARBALL_URL;
|
|
35
35
|
private static readonly META_FILE;
|
|
36
|
+
private static readonly REPO_PROVIDER_DIRNAME;
|
|
37
|
+
private static readonly SIBLING_MARKER_FILE;
|
|
38
|
+
private static readonly SIBLING_ENV_VAR;
|
|
39
|
+
private probeStarts;
|
|
40
|
+
private siblingLogged;
|
|
41
|
+
private userDirSource;
|
|
42
|
+
/** Process-level dedup for stderr sibling-adoption notices (shared across all ProviderLoader instances). */
|
|
43
|
+
private static siblingStderrLogged;
|
|
44
|
+
private static looksLikeProviderRoot;
|
|
45
|
+
private static hasProviderRootMarker;
|
|
46
|
+
private detectDefaultUserDir;
|
|
36
47
|
constructor(options?: {
|
|
37
48
|
userDir?: string;
|
|
38
49
|
logFn?: (msg: string) => void;
|
|
@@ -40,6 +51,12 @@ export declare class ProviderLoader {
|
|
|
40
51
|
sourceMode?: ProviderSourceMode;
|
|
41
52
|
/** Deprecated alias for sourceMode='no-upstream' */
|
|
42
53
|
disableUpstream?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Directories from which to walk up looking for a sibling `adhdev-providers`
|
|
56
|
+
* checkout. Defaults to [process.cwd(), __dirname]. Used by tests for hermetic
|
|
57
|
+
* probing; production code should leave this unset.
|
|
58
|
+
*/
|
|
59
|
+
probeStarts?: string[];
|
|
43
60
|
});
|
|
44
61
|
private log;
|
|
45
62
|
/**
|
package/package.json
CHANGED
|
@@ -1492,7 +1492,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1492
1492
|
|
|
1493
1493
|
private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
|
|
1494
1494
|
if (this.parseErrorMessage) return 'error';
|
|
1495
|
-
if (startupModal) return 'waiting_approval';
|
|
1495
|
+
if (startupModal || this.activeModal) return 'waiting_approval';
|
|
1496
1496
|
if (this.isWaitingForResponse && this.currentTurnScope && this.currentStatus === 'idle') return 'generating';
|
|
1497
1497
|
return this.currentStatus;
|
|
1498
1498
|
}
|
|
@@ -1502,12 +1502,28 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1502
1502
|
getStatus(): CliSessionStatus {
|
|
1503
1503
|
const screenText = this.terminalScreen.getText() || '';
|
|
1504
1504
|
const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
|
|
1505
|
-
|
|
1505
|
+
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
1506
|
+
let effectiveModal = startupModal || this.activeModal;
|
|
1507
|
+
if (!startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === 'function') {
|
|
1508
|
+
try {
|
|
1509
|
+
const parsed = this.getScriptParsedStatus();
|
|
1510
|
+
const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
|
|
1511
|
+
&& parsed.activeModal.buttons.some((button: any) => typeof button === 'string' && button.trim())
|
|
1512
|
+
? parsed.activeModal
|
|
1513
|
+
: null;
|
|
1514
|
+
if (parsed?.status === 'waiting_approval' && parsedModal) {
|
|
1515
|
+
effectiveStatus = 'waiting_approval';
|
|
1516
|
+
effectiveModal = parsedModal;
|
|
1517
|
+
}
|
|
1518
|
+
} catch {
|
|
1519
|
+
// Ignore parse errors here; getScriptParsedStatus surfaces them on richer callers.
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1506
1522
|
return {
|
|
1507
1523
|
status: effectiveStatus,
|
|
1508
1524
|
messages: [...this.committedMessages],
|
|
1509
1525
|
workingDir: this.workingDir,
|
|
1510
|
-
activeModal:
|
|
1526
|
+
activeModal: effectiveModal,
|
|
1511
1527
|
errorMessage: this.parseErrorMessage || undefined,
|
|
1512
1528
|
errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
|
|
1513
1529
|
};
|
|
@@ -1565,6 +1581,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1565
1581
|
this.currentTurnScope,
|
|
1566
1582
|
screenText,
|
|
1567
1583
|
);
|
|
1584
|
+
const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
|
|
1585
|
+
&& parsed.activeModal.buttons.some((button: any) => typeof button === 'string' && button.trim())
|
|
1586
|
+
? parsed.activeModal
|
|
1587
|
+
: null;
|
|
1588
|
+
if (parsedModal && parsed?.status === 'waiting_approval') {
|
|
1589
|
+
this.activeModal = parsedModal;
|
|
1590
|
+
this.isWaitingForResponse = true;
|
|
1591
|
+
if (this.currentStatus !== 'waiting_approval') {
|
|
1592
|
+
this.setStatus('waiting_approval', 'parsed_waiting_approval');
|
|
1593
|
+
this.onStatusChange?.();
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1568
1596
|
if (this.maybeCommitVisibleIdleTranscript(parsed)) {
|
|
1569
1597
|
return this.getScriptParsedStatus();
|
|
1570
1598
|
}
|
|
@@ -2219,7 +2247,26 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2219
2247
|
|
|
2220
2248
|
resolveModal(buttonIndex: number): void {
|
|
2221
2249
|
const screenText = this.terminalScreen.getText() || '';
|
|
2222
|
-
|
|
2250
|
+
let modal = this.activeModal || this.getStartupConfirmationModal(screenText);
|
|
2251
|
+
if (!modal && typeof this.cliScripts?.parseOutput === 'function') {
|
|
2252
|
+
try {
|
|
2253
|
+
const parsed = this.getScriptParsedStatus();
|
|
2254
|
+
const parsedModal = parsed?.activeModal && Array.isArray(parsed.activeModal.buttons)
|
|
2255
|
+
&& parsed.activeModal.buttons.some((button: any) => typeof button === 'string' && button.trim())
|
|
2256
|
+
? parsed.activeModal
|
|
2257
|
+
: null;
|
|
2258
|
+
if (parsed?.status === 'waiting_approval' && parsedModal) {
|
|
2259
|
+
modal = parsedModal;
|
|
2260
|
+
this.activeModal = parsedModal;
|
|
2261
|
+
if (this.currentStatus !== 'waiting_approval') {
|
|
2262
|
+
this.setStatus('waiting_approval', 'resolve_modal_parse');
|
|
2263
|
+
this.onStatusChange?.();
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
} catch {
|
|
2267
|
+
// Ignore parse failures here; resolveModal falls back to current state.
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2223
2270
|
if (!this.ptyProcess || ((this.currentStatus !== 'waiting_approval') && !modal)) return;
|
|
2224
2271
|
this.clearIdleFinishCandidate('resolve_modal');
|
|
2225
2272
|
this.recordTrace('resolve_modal', {
|
|
@@ -42,7 +42,9 @@ function getTargetInstance(h: CommandHelpers, args: any): ApprovalSelectableInst
|
|
|
42
42
|
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
43
43
|
const sessionId = targetSessionId || h.currentSession?.sessionId || '';
|
|
44
44
|
if (!sessionId) return null;
|
|
45
|
-
|
|
45
|
+
const session = h.ctx.sessionRegistry?.get(sessionId);
|
|
46
|
+
const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
|
|
47
|
+
return (h.ctx.instanceManager?.getInstance(instanceKey) as ApprovalSelectableInstance | undefined) || null;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
function getTargetTransport(h: CommandHelpers, provider?: ProviderModule): SessionTransport | null {
|
|
@@ -1238,10 +1240,25 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1238
1240
|
}
|
|
1239
1241
|
|
|
1240
1242
|
const status = adapter.getStatus();
|
|
1241
|
-
|
|
1243
|
+
const targetInstance = getTargetInstance(h, args);
|
|
1244
|
+
const targetState = targetInstance?.getState?.() as { activeChat?: { status?: string; activeModal?: { message?: string; buttons?: string[] } | null } } | undefined;
|
|
1245
|
+
const surfacedModal = targetState?.activeChat?.activeModal && Array.isArray(targetState.activeChat.activeModal.buttons)
|
|
1246
|
+
&& targetState.activeChat.activeModal.buttons.some((candidate) => typeof candidate === 'string' && candidate.trim())
|
|
1247
|
+
? targetState.activeChat.activeModal
|
|
1248
|
+
: null;
|
|
1249
|
+
const statusModal = status?.activeModal && Array.isArray(status.activeModal.buttons)
|
|
1250
|
+
&& status.activeModal.buttons.some((candidate) => typeof candidate === 'string' && candidate.trim())
|
|
1251
|
+
? status.activeModal
|
|
1252
|
+
: null;
|
|
1253
|
+
const effectiveModal = statusModal || surfacedModal;
|
|
1254
|
+
const effectiveStatus = status?.status === 'waiting_approval' || targetState?.activeChat?.status === 'waiting_approval'
|
|
1255
|
+
? 'waiting_approval'
|
|
1256
|
+
: status?.status;
|
|
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 (effectiveStatus !== 'waiting_approval' && !effectiveModal) {
|
|
1242
1259
|
return { success: false, error: 'Not in approval state' };
|
|
1243
1260
|
}
|
|
1244
|
-
const buttons: string[] =
|
|
1261
|
+
const buttons: string[] = effectiveModal?.buttons || ['Allow once', 'Always allow', 'Deny'];
|
|
1245
1262
|
// Resolve button index: explicit buttonIndex arg → button text match → action fallback
|
|
1246
1263
|
let buttonIndex = typeof args?.buttonIndex === 'number' ? args.buttonIndex : -1;
|
|
1247
1264
|
if (buttonIndex < 0) {
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { ProviderSourceMode } from './config.js'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* How the effective `userDir` was resolved:
|
|
5
|
+
* - `explicit` — the user set `config.providerDir` or passed `userDir`
|
|
6
|
+
* - `sibling-env` — auto-adopted a sibling `adhdev-providers/` via ADHDEV_USE_SIBLING_PROVIDERS=1
|
|
7
|
+
* - `sibling-marker` — auto-adopted a sibling `adhdev-providers/` via a `.adhdev-provider-root` marker file
|
|
8
|
+
* - `home-default` — fell back to `~/.adhdev/providers`
|
|
9
|
+
*/
|
|
10
|
+
export type ProviderUserDirSource = 'explicit' | 'sibling-env' | 'sibling-marker' | 'home-default'
|
|
11
|
+
|
|
3
12
|
export interface ProviderSourceConfigSnapshot {
|
|
4
13
|
sourceMode: ProviderSourceMode
|
|
5
14
|
disableUpstream: boolean
|
|
6
15
|
explicitProviderDir: string | null
|
|
7
16
|
userDir: string
|
|
17
|
+
userDirSource: ProviderUserDirSource
|
|
8
18
|
upstreamDir: string
|
|
9
19
|
providerRoots: string[]
|
|
10
20
|
}
|
|
@@ -31,7 +31,7 @@ import type {
|
|
|
31
31
|
} from './contracts.js';
|
|
32
32
|
import { validateProviderDefinition } from './provider-schema.js';
|
|
33
33
|
import type { ProviderSourceMode } from '../config/config.js';
|
|
34
|
-
import type { ProviderSourceConfigSnapshot } from '../config/provider-source-config.js';
|
|
34
|
+
import type { ProviderSourceConfigSnapshot, ProviderUserDirSource } from '../config/provider-source-config.js';
|
|
35
35
|
|
|
36
36
|
interface ProviderAvailabilityState {
|
|
37
37
|
installed: boolean;
|
|
@@ -59,6 +59,75 @@ export class ProviderLoader {
|
|
|
59
59
|
|
|
60
60
|
private static readonly GITHUB_TARBALL_URL = 'https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz';
|
|
61
61
|
private static readonly META_FILE = '.meta.json';
|
|
62
|
+
private static readonly REPO_PROVIDER_DIRNAME = 'adhdev-providers';
|
|
63
|
+
private static readonly SIBLING_MARKER_FILE = '.adhdev-provider-root';
|
|
64
|
+
private static readonly SIBLING_ENV_VAR = 'ADHDEV_USE_SIBLING_PROVIDERS';
|
|
65
|
+
|
|
66
|
+
private probeStarts: string[] = [];
|
|
67
|
+
private siblingLogged = false;
|
|
68
|
+
private userDirSource: ProviderUserDirSource = 'home-default';
|
|
69
|
+
|
|
70
|
+
/** Process-level dedup for stderr sibling-adoption notices (shared across all ProviderLoader instances). */
|
|
71
|
+
private static siblingStderrLogged: Set<string> = new Set();
|
|
72
|
+
|
|
73
|
+
private static looksLikeProviderRoot(candidate: string): boolean {
|
|
74
|
+
try {
|
|
75
|
+
if (!fs.existsSync(candidate) || !fs.statSync(candidate).isDirectory()) return false;
|
|
76
|
+
return ['ide', 'extension', 'cli', 'acp'].some((category) =>
|
|
77
|
+
fs.existsSync(path.join(candidate, category))
|
|
78
|
+
);
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private static hasProviderRootMarker(candidate: string): boolean {
|
|
85
|
+
try {
|
|
86
|
+
return fs.existsSync(path.join(candidate, ProviderLoader.SIBLING_MARKER_FILE));
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private detectDefaultUserDir(): { path: string; source: 'sibling-env' | 'sibling-marker' | 'home-default' } {
|
|
93
|
+
const fallback = path.join(os.homedir(), '.adhdev', 'providers');
|
|
94
|
+
const envOptIn = process.env[ProviderLoader.SIBLING_ENV_VAR] === '1';
|
|
95
|
+
const visited = new Set<string>();
|
|
96
|
+
|
|
97
|
+
for (const start of this.probeStarts) {
|
|
98
|
+
let current = path.resolve(start);
|
|
99
|
+
while (!visited.has(current)) {
|
|
100
|
+
visited.add(current);
|
|
101
|
+
const siblingCandidate = path.join(path.dirname(current), ProviderLoader.REPO_PROVIDER_DIRNAME);
|
|
102
|
+
if (ProviderLoader.looksLikeProviderRoot(siblingCandidate)) {
|
|
103
|
+
const hasMarker = ProviderLoader.hasProviderRootMarker(siblingCandidate);
|
|
104
|
+
if (envOptIn || hasMarker) {
|
|
105
|
+
const source: 'sibling-env' | 'sibling-marker' = hasMarker ? 'sibling-marker' : 'sibling-env';
|
|
106
|
+
if (!this.siblingLogged) {
|
|
107
|
+
this.log(`Using sibling provider checkout (${source}): ${siblingCandidate}`);
|
|
108
|
+
this.siblingLogged = true;
|
|
109
|
+
}
|
|
110
|
+
// Force-surface adoption to stderr once per sibling path per process, so CLI
|
|
111
|
+
// entry points that suppress logFn still leave a visible trail.
|
|
112
|
+
if (!ProviderLoader.siblingStderrLogged.has(siblingCandidate)) {
|
|
113
|
+
ProviderLoader.siblingStderrLogged.add(siblingCandidate);
|
|
114
|
+
try {
|
|
115
|
+
process.stderr.write(
|
|
116
|
+
`[adhdev] Using sibling adhdev-providers checkout (${source}): ${siblingCandidate}\n`,
|
|
117
|
+
);
|
|
118
|
+
} catch { /* ignore */ }
|
|
119
|
+
}
|
|
120
|
+
return { path: siblingCandidate, source };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const parent = path.dirname(current);
|
|
124
|
+
if (parent === current) break;
|
|
125
|
+
current = parent;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { path: fallback, source: 'home-default' };
|
|
130
|
+
}
|
|
62
131
|
|
|
63
132
|
constructor(options?: {
|
|
64
133
|
userDir?: string;
|
|
@@ -67,12 +136,21 @@ export class ProviderLoader {
|
|
|
67
136
|
sourceMode?: ProviderSourceMode;
|
|
68
137
|
/** Deprecated alias for sourceMode='no-upstream' */
|
|
69
138
|
disableUpstream?: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Directories from which to walk up looking for a sibling `adhdev-providers`
|
|
141
|
+
* checkout. Defaults to [process.cwd(), __dirname]. Used by tests for hermetic
|
|
142
|
+
* probing; production code should leave this unset.
|
|
143
|
+
*/
|
|
144
|
+
probeStarts?: string[];
|
|
70
145
|
}) {
|
|
71
146
|
this.logFn = options?.logFn || LOG.forComponent('Provider').asLogFn();
|
|
147
|
+
this.probeStarts = options?.probeStarts ?? [process.cwd(), __dirname];
|
|
72
148
|
|
|
73
149
|
// Default directory for auto-downloads
|
|
74
150
|
this.defaultProvidersDir = path.join(os.homedir(), '.adhdev', 'providers');
|
|
75
|
-
|
|
151
|
+
const detected = this.detectDefaultUserDir();
|
|
152
|
+
this.userDir = detected.path;
|
|
153
|
+
this.userDirSource = detected.source;
|
|
76
154
|
this.upstreamDir = path.join(this.defaultProvidersDir, '.upstream');
|
|
77
155
|
this.disableUpstream = false;
|
|
78
156
|
|
|
@@ -117,6 +195,7 @@ export class ProviderLoader {
|
|
|
117
195
|
disableUpstream: this.disableUpstream,
|
|
118
196
|
explicitProviderDir: this.explicitProviderDir,
|
|
119
197
|
userDir: this.userDir,
|
|
198
|
+
userDirSource: this.userDirSource,
|
|
120
199
|
upstreamDir: this.upstreamDir,
|
|
121
200
|
providerRoots: this.getProviderRoots(),
|
|
122
201
|
};
|
|
@@ -138,7 +217,14 @@ export class ProviderLoader {
|
|
|
138
217
|
}
|
|
139
218
|
|
|
140
219
|
this.sourceMode = nextSourceMode;
|
|
141
|
-
|
|
220
|
+
if (this.explicitProviderDir) {
|
|
221
|
+
this.userDir = this.explicitProviderDir;
|
|
222
|
+
this.userDirSource = 'explicit';
|
|
223
|
+
} else {
|
|
224
|
+
const detected = this.detectDefaultUserDir();
|
|
225
|
+
this.userDir = detected.path;
|
|
226
|
+
this.userDirSource = detected.source;
|
|
227
|
+
}
|
|
142
228
|
this.upstreamDir = path.join(this.defaultProvidersDir, '.upstream');
|
|
143
229
|
this.disableUpstream = this.sourceMode === 'no-upstream';
|
|
144
230
|
|