@adhdev/daemon-core 0.6.50 → 0.6.52
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/index.d.ts +52 -3
- package/dist/index.js +119 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/cli/claude-cli/provider.json +6 -6
- package/providers/_builtin/cli/codex-cli/provider.json +4 -2
- package/providers/_builtin/cli/gemini-cli/provider.json +4 -2
- package/providers/_builtin/registry.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +42 -14
- package/src/daemon/dev-server.ts +4 -37
- package/src/providers/provider-loader.ts +86 -6
package/package.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"type": "select",
|
|
14
14
|
"default": "terminal",
|
|
15
15
|
"public": true,
|
|
16
|
-
"label": "
|
|
17
|
-
"description": "terminal: PTY
|
|
16
|
+
"label": "Display Mode",
|
|
17
|
+
"description": "terminal: Native PTY view, chat: Parsed message view",
|
|
18
18
|
"options": [
|
|
19
19
|
"terminal",
|
|
20
20
|
"chat"
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"type": "boolean",
|
|
25
25
|
"default": true,
|
|
26
26
|
"public": true,
|
|
27
|
-
"label": "
|
|
28
|
-
"description": "
|
|
27
|
+
"label": "Notifications",
|
|
28
|
+
"description": "Display notifications on state changes"
|
|
29
29
|
},
|
|
30
30
|
"autoApprove": {
|
|
31
31
|
"type": "boolean",
|
|
32
32
|
"default": false,
|
|
33
33
|
"public": true,
|
|
34
|
-
"label": "
|
|
35
|
-
"description": "
|
|
34
|
+
"label": "Auto Approve",
|
|
35
|
+
"description": "Automatically approve tool execution without prompt"
|
|
36
36
|
},
|
|
37
37
|
"approvalAlert": {
|
|
38
38
|
"type": "boolean",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"type": "select",
|
|
13
13
|
"default": "terminal",
|
|
14
14
|
"public": true,
|
|
15
|
-
"label": "
|
|
15
|
+
"label": "Display Mode",
|
|
16
|
+
"description": "terminal: Native PTY view, chat: Parsed message view",
|
|
16
17
|
"options": [
|
|
17
18
|
"terminal",
|
|
18
19
|
"chat"
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
"type": "boolean",
|
|
23
24
|
"default": true,
|
|
24
25
|
"public": true,
|
|
25
|
-
"label": "
|
|
26
|
+
"label": "Notifications",
|
|
27
|
+
"description": "Display notifications on state changes"
|
|
26
28
|
},
|
|
27
29
|
"autoApprove": {
|
|
28
30
|
"type": "boolean",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"type": "select",
|
|
13
13
|
"default": "terminal",
|
|
14
14
|
"public": true,
|
|
15
|
-
"label": "
|
|
15
|
+
"label": "Display Mode",
|
|
16
|
+
"description": "terminal: Native PTY view, chat: Parsed message view",
|
|
16
17
|
"options": [
|
|
17
18
|
"terminal",
|
|
18
19
|
"chat"
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
"type": "boolean",
|
|
23
24
|
"default": true,
|
|
24
25
|
"public": true,
|
|
25
|
-
"label": "
|
|
26
|
+
"label": "Notifications",
|
|
27
|
+
"description": "Display notifications on state changes"
|
|
26
28
|
},
|
|
27
29
|
"autoApprove": {
|
|
28
30
|
"type": "boolean",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* category: 'cli'
|
|
11
11
|
* binary: string — binary name
|
|
12
12
|
* spawn: { command, args, shell, env }
|
|
13
|
-
* patterns: { prompt, generating, approval, ready }
|
|
13
|
+
* patterns: { prompt, generating, approval, ready } — prompt helps end startup splash-gate early; sendMessage does not wait for it
|
|
14
14
|
* timeouts?: { idleFinish, generatingIdle, maxResponse, approvalCooldown, outputSettle, ... }
|
|
15
15
|
* cleanOutput(raw, lastUserInput): string
|
|
16
16
|
*/
|
|
@@ -253,6 +253,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
253
253
|
private idleTimeout: NodeJS.Timeout | null = null;
|
|
254
254
|
private ready = false;
|
|
255
255
|
private startupBuffer = '';
|
|
256
|
+
/** After spawn: briefly skip generating/settle so splash/redraw does not flip status; not used to gate sendMessage */
|
|
257
|
+
private startupParseGate = false;
|
|
258
|
+
private spawnAt = 0;
|
|
256
259
|
|
|
257
260
|
// PTY I/O
|
|
258
261
|
private onPtyDataCallback: ((data: string) => void) | null = null;
|
|
@@ -421,10 +424,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
421
424
|
this.ptyProcess = null;
|
|
422
425
|
this.setStatus('stopped', 'pty_exit');
|
|
423
426
|
this.ready = false;
|
|
427
|
+
this.startupParseGate = false;
|
|
428
|
+
this.spawnAt = 0;
|
|
424
429
|
this.onStatusChange?.();
|
|
425
430
|
});
|
|
426
431
|
|
|
427
|
-
this.
|
|
432
|
+
this.spawnAt = Date.now();
|
|
433
|
+
this.startupParseGate = true;
|
|
434
|
+
this.startupBuffer = '';
|
|
435
|
+
this.ready = true;
|
|
436
|
+
this.setStatus('idle', 'pty_ready');
|
|
428
437
|
this.onStatusChange?.();
|
|
429
438
|
}
|
|
430
439
|
|
|
@@ -449,12 +458,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
449
458
|
// Rolling buffer (recent 1000 chars)
|
|
450
459
|
this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1000);
|
|
451
460
|
|
|
452
|
-
// ───
|
|
453
|
-
if (
|
|
461
|
+
// ─── Startup window: first-run dialogs + avoid false generating during splash (sendMessage is not gated on prompt text)
|
|
462
|
+
if (this.startupParseGate) {
|
|
454
463
|
this.startupBuffer += cleanData;
|
|
455
464
|
LOG.info('CLI', `[${this.cliType}] startup chunk (${cleanData.length} chars): ${cleanData.slice(0, 200).replace(/\n/g, '\\n')}`);
|
|
456
465
|
|
|
457
|
-
// Startup dialog auto-proceed (Enter)
|
|
458
466
|
const dialogPatterns = [
|
|
459
467
|
/Do you want to connect/i,
|
|
460
468
|
/Do you trust the files/i,
|
|
@@ -468,14 +476,19 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
468
476
|
return;
|
|
469
477
|
}
|
|
470
478
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
479
|
+
const elapsed = Date.now() - this.spawnAt;
|
|
480
|
+
const bufCap = this.startupBuffer.length > 12000;
|
|
481
|
+
const promptMatched = patterns.prompt.some(p => p.test(this.startupBuffer));
|
|
482
|
+
if (promptMatched || elapsed > 8000 || bufCap) {
|
|
483
|
+
this.startupParseGate = false;
|
|
484
|
+
if (promptMatched) {
|
|
485
|
+
LOG.info('CLI', `[${this.cliType}] ✓ Startup gate end (prompt matched)`);
|
|
486
|
+
} else {
|
|
487
|
+
LOG.info('CLI', `[${this.cliType}] startup gate end (${elapsed}ms, cap=${bufCap}, prompt=${promptMatched})`);
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
return;
|
|
477
491
|
}
|
|
478
|
-
return;
|
|
479
492
|
}
|
|
480
493
|
|
|
481
494
|
if (cleanData.trim().length > 5) {
|
|
@@ -695,6 +708,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
695
708
|
this.ptyProcess = null;
|
|
696
709
|
this.setStatus('stopped', 'stop_cmd');
|
|
697
710
|
this.ready = false;
|
|
711
|
+
this.startupParseGate = false;
|
|
712
|
+
this.spawnAt = 0;
|
|
698
713
|
this.onStatusChange?.();
|
|
699
714
|
}, this.timeouts.shutdownGrace);
|
|
700
715
|
}
|
|
@@ -747,16 +762,29 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
747
762
|
* Used by DevServer /api/cli/debug endpoint.
|
|
748
763
|
*/
|
|
749
764
|
getDebugState(): Record<string, any> {
|
|
765
|
+
const sb = this.startupBuffer;
|
|
766
|
+
const testOnStartup = (p: RegExp): boolean => {
|
|
767
|
+
const flags = p.flags.includes('g') ? p.flags.replace(/g/g, '') : p.flags;
|
|
768
|
+
return new RegExp(p.source, flags).test(sb);
|
|
769
|
+
};
|
|
770
|
+
const promptDiagnostics = this.provider.patterns.prompt.map((p) => ({
|
|
771
|
+
pattern: p.toString(),
|
|
772
|
+
matchedAgainstStartupBuffer: testOnStartup(p),
|
|
773
|
+
}));
|
|
750
774
|
return {
|
|
751
775
|
type: this.cliType,
|
|
752
776
|
name: this.cliName,
|
|
753
777
|
status: this.currentStatus,
|
|
754
778
|
ready: this.ready,
|
|
779
|
+
startupParseGate: this.startupParseGate,
|
|
780
|
+
spawnAt: this.spawnAt,
|
|
755
781
|
workingDir: this.workingDir,
|
|
756
782
|
messages: this.messages.slice(-20),
|
|
757
783
|
messageCount: this.messages.length,
|
|
758
|
-
// Buffers
|
|
759
|
-
startupBuffer:
|
|
784
|
+
// Buffers (longer tails here than in periodic logs — for matching provider.json patterns)
|
|
785
|
+
startupBuffer: sb.slice(-4000),
|
|
786
|
+
startupBufferLength: sb.length,
|
|
787
|
+
promptDiagnostics,
|
|
760
788
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
761
789
|
settledBuffer: this.settledBuffer.slice(-500),
|
|
762
790
|
responseBuffer: this.responseBuffer.slice(-500),
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -730,39 +730,7 @@ export class DevServer {
|
|
|
730
730
|
|
|
731
731
|
/** Find the provider directory on disk */
|
|
732
732
|
private findProviderDir(type: string): string | null {
|
|
733
|
-
|
|
734
|
-
if (!provider) return null;
|
|
735
|
-
const cat = provider.category;
|
|
736
|
-
const builtinDir = (this.providerLoader as any).builtinDirs?.[0] || path.resolve(__dirname, '../providers/_builtin');
|
|
737
|
-
const userDir = path.join(os.homedir(), '.adhdev', 'providers');
|
|
738
|
-
|
|
739
|
-
// Direct match first
|
|
740
|
-
const directCandidates = [
|
|
741
|
-
path.join(userDir, type),
|
|
742
|
-
path.join(builtinDir, cat, type),
|
|
743
|
-
path.join(builtinDir, type),
|
|
744
|
-
];
|
|
745
|
-
for (const d of directCandidates) {
|
|
746
|
-
if (fs.existsSync(path.join(d, 'provider.json'))) return d;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// Scan category dir for matching type field
|
|
750
|
-
const catDir = path.join(builtinDir, cat);
|
|
751
|
-
if (fs.existsSync(catDir)) {
|
|
752
|
-
try {
|
|
753
|
-
for (const entry of fs.readdirSync(catDir, { withFileTypes: true })) {
|
|
754
|
-
if (!entry.isDirectory()) continue;
|
|
755
|
-
const jsonPath = path.join(catDir, entry.name, 'provider.json');
|
|
756
|
-
if (fs.existsSync(jsonPath)) {
|
|
757
|
-
try {
|
|
758
|
-
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
759
|
-
if (data.type === type) return path.join(catDir, entry.name);
|
|
760
|
-
} catch { /* skip */ }
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
} catch { /* skip */ }
|
|
764
|
-
}
|
|
765
|
-
return null;
|
|
733
|
+
return this.providerLoader.findProviderDir(type);
|
|
766
734
|
}
|
|
767
735
|
|
|
768
736
|
/** GET /api/providers/:type/files — list all files in provider directory */
|
|
@@ -1140,10 +1108,9 @@ export class DevServer {
|
|
|
1140
1108
|
|
|
1141
1109
|
let targetDir: string;
|
|
1142
1110
|
if (location === 'user') {
|
|
1143
|
-
targetDir =
|
|
1111
|
+
targetDir = this.providerLoader.getUserProviderDir(category, type);
|
|
1144
1112
|
} else {
|
|
1145
|
-
|
|
1146
|
-
targetDir = path.join(builtinDir, category, type);
|
|
1113
|
+
targetDir = this.providerLoader.getBuiltinProviderDir(category, type);
|
|
1147
1114
|
}
|
|
1148
1115
|
|
|
1149
1116
|
const jsonPath = path.join(targetDir, 'provider.json');
|
|
@@ -1928,7 +1895,7 @@ export class DevServer {
|
|
|
1928
1895
|
this.sendAutoImplSSE({ event: 'progress', data: { function: '_init', status: 'loading_reference', message: `레퍼런스 스크립트 로드 중 (${reference})...` } });
|
|
1929
1896
|
|
|
1930
1897
|
let referenceScripts: Record<string, string> = {};
|
|
1931
|
-
const builtinDir =
|
|
1898
|
+
const builtinDir = this.providerLoader.getPrimaryBuiltinDir();
|
|
1932
1899
|
const refDir = path.join(builtinDir, 'ide', reference);
|
|
1933
1900
|
if (fs.existsSync(refDir)) {
|
|
1934
1901
|
// Find latest versioned scripts dir
|
|
@@ -77,6 +77,87 @@ export class ProviderLoader {
|
|
|
77
77
|
|
|
78
78
|
// ─── Public API ────────────────────────────────
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Ordered builtin roots used for local fallback/reference data.
|
|
82
|
+
*/
|
|
83
|
+
getBuiltinDirs(): string[] {
|
|
84
|
+
return [...this.builtinDirs];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Primary builtin root used for local scaffolding/reference flows.
|
|
89
|
+
*/
|
|
90
|
+
getPrimaryBuiltinDir(): string {
|
|
91
|
+
return this.builtinDirs[0];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* User override root (~/.adhdev/providers by default).
|
|
96
|
+
*/
|
|
97
|
+
getUserDir(): string {
|
|
98
|
+
return this.userDir;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Auto-updated upstream root (~/.adhdev/providers/.upstream by default).
|
|
103
|
+
*/
|
|
104
|
+
getUpstreamDir(): string {
|
|
105
|
+
return this.upstreamDir;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Provider search order for on-disk lookups.
|
|
110
|
+
* Highest-priority editable overrides come first.
|
|
111
|
+
*/
|
|
112
|
+
getProviderRoots(): string[] {
|
|
113
|
+
return [this.userDir, this.upstreamDir, ...this.builtinDirs];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Canonical provider directory shape for a given root.
|
|
118
|
+
*/
|
|
119
|
+
getProviderDir(root: string, category: ProviderCategory, type: string): string {
|
|
120
|
+
return path.join(root, category, type);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Canonical user override directory for a provider.
|
|
125
|
+
*/
|
|
126
|
+
getUserProviderDir(category: ProviderCategory, type: string): string {
|
|
127
|
+
return this.getProviderDir(this.userDir, category, type);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Canonical upstream directory for a provider.
|
|
132
|
+
*/
|
|
133
|
+
getUpstreamProviderDir(category: ProviderCategory, type: string): string {
|
|
134
|
+
return this.getProviderDir(this.upstreamDir, category, type);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Canonical builtin directory for a provider.
|
|
139
|
+
*/
|
|
140
|
+
getBuiltinProviderDir(category: ProviderCategory, type: string): string {
|
|
141
|
+
return this.getProviderDir(this.getPrimaryBuiltinDir(), category, type);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Find the on-disk directory for a provider by type.
|
|
146
|
+
* Search order: user override → upstream → builtin fallback.
|
|
147
|
+
*/
|
|
148
|
+
findProviderDir(type: string): string | null {
|
|
149
|
+
return this.findProviderDirInternal(type);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a file within a provider directory.
|
|
154
|
+
*/
|
|
155
|
+
resolveProviderFile(type: string, ...segments: string[]): string | null {
|
|
156
|
+
const dir = this.findProviderDirInternal(type);
|
|
157
|
+
if (!dir) return null;
|
|
158
|
+
return path.join(dir, ...segments);
|
|
159
|
+
}
|
|
160
|
+
|
|
80
161
|
/**
|
|
81
162
|
* Load all providers (3-tier priority)
|
|
82
163
|
* 1. .upstream/ (GitHub auto-download — primary source)
|
|
@@ -455,7 +536,7 @@ export class ProviderLoader {
|
|
|
455
536
|
* Tries scripts.js first, then individual .js files.
|
|
456
537
|
*/
|
|
457
538
|
private loadScriptsFromDir(type: string, scriptDir: string): Record<string, any> | null {
|
|
458
|
-
const providerDir = this.
|
|
539
|
+
const providerDir = this.findProviderDirInternal(type);
|
|
459
540
|
if (!providerDir) {
|
|
460
541
|
this.log(` [loadScriptsFromDir] ${type}: providerDir not found`);
|
|
461
542
|
return null;
|
|
@@ -857,18 +938,17 @@ export class ProviderLoader {
|
|
|
857
938
|
|
|
858
939
|
/**
|
|
859
940
|
* Find the on-disk directory for a provider by type.
|
|
860
|
-
*
|
|
941
|
+
* Preferred shape: root/category/type. Legacy flat root/type is kept temporarily for compatibility.
|
|
861
942
|
*/
|
|
862
|
-
private
|
|
943
|
+
private findProviderDirInternal(type: string): string | null {
|
|
863
944
|
const provider = this.providers.get(type);
|
|
864
945
|
if (!provider) return null;
|
|
865
946
|
const cat = provider.category;
|
|
866
947
|
|
|
867
|
-
const searchRoots =
|
|
948
|
+
const searchRoots = this.getProviderRoots();
|
|
868
949
|
for (const root of searchRoots) {
|
|
869
950
|
if (!fs.existsSync(root)) continue;
|
|
870
|
-
|
|
871
|
-
for (const candidate of [path.join(root, type), path.join(root, cat, type)]) {
|
|
951
|
+
for (const candidate of [this.getProviderDir(root, cat, type), path.join(root, type)]) {
|
|
872
952
|
if (fs.existsSync(path.join(candidate, 'provider.json'))) return candidate;
|
|
873
953
|
}
|
|
874
954
|
// Scan category dir for type match
|