@vfarcic/dot-ai 1.19.1 → 1.21.0
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/core/ai-provider-factory.d.ts.map +1 -1
- package/dist/core/ai-provider-factory.js +1 -0
- package/dist/core/model-config.d.ts +1 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +1 -0
- package/dist/core/providers/copilot-token-exchanger.d.ts +41 -0
- package/dist/core/providers/copilot-token-exchanger.d.ts.map +1 -0
- package/dist/core/providers/copilot-token-exchanger.js +58 -0
- package/dist/core/providers/vercel-provider.d.ts.map +1 -1
- package/dist/core/providers/vercel-provider.js +89 -1
- package/dist/core/user-prompts-loader.d.ts +70 -4
- package/dist/core/user-prompts-loader.d.ts.map +1 -1
- package/dist/core/user-prompts-loader.js +185 -11
- package/dist/interfaces/rest-api.d.ts +34 -1
- package/dist/interfaces/rest-api.d.ts.map +1 -1
- package/dist/interfaces/rest-api.js +129 -16
- package/dist/interfaces/schemas/prompts.d.ts +3 -3
- package/dist/tools/prompts.d.ts +15 -5
- package/dist/tools/prompts.d.ts.map +1 -1
- package/dist/tools/prompts.js +23 -9
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-provider-factory.d.ts","sourceRoot":"","sources":["../../src/core/ai-provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-provider-factory.d.ts","sourceRoot":"","sources":["../../src/core/ai-provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAkCvE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,iBAAiB;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU;IA+BnD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,aAAa,IAAI,UAAU;IAsGlC;;;;;OAKG;IACH,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAWrD;;;;OAIG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;;;;OAKG;IACH,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAGxD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAE7C"}
|
|
@@ -31,6 +31,7 @@ const PROVIDER_ENV_KEYS = {
|
|
|
31
31
|
alibaba: 'ALIBABA_API_KEY', // PRD #382: Alibaba Qwen 3.5 Plus
|
|
32
32
|
xai: 'XAI_API_KEY',
|
|
33
33
|
custom: 'CUSTOM_LLM_API_KEY', // PRD #194 / Issue #474: Explicit opt-in to custom OpenAI-compatible endpoint
|
|
34
|
+
copilot: 'GITHUB_COPILOT_TOKEN', // PRD #587: GitHub Copilot provider
|
|
34
35
|
};
|
|
35
36
|
const IMPLEMENTED_PROVIDERS = Object.keys(model_config_1.CURRENT_MODELS);
|
|
36
37
|
/**
|
|
@@ -18,6 +18,7 @@ export declare const CURRENT_MODELS: {
|
|
|
18
18
|
readonly openrouter: "anthropic/claude-haiku-4.5";
|
|
19
19
|
readonly custom: "gpt-5.4";
|
|
20
20
|
readonly amazon_bedrock: "global.anthropic.claude-sonnet-4-6";
|
|
21
|
+
readonly copilot: "claude-sonnet-4.6";
|
|
21
22
|
};
|
|
22
23
|
/**
|
|
23
24
|
* Get current model for a provider
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../../src/core/model-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../../src/core/model-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;CAejB,CAAC;AAEX;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,OAAO,cAAc,GAAG,MAAM,CAE7E"}
|
|
@@ -22,6 +22,7 @@ exports.CURRENT_MODELS = {
|
|
|
22
22
|
openrouter: 'anthropic/claude-haiku-4.5', // PRD #194: OpenRouter default model (overridden by AI_MODEL env var)
|
|
23
23
|
custom: 'gpt-5.4', // PRD #194: Custom endpoint default model (overridden by AI_MODEL env var)
|
|
24
24
|
amazon_bedrock: 'global.anthropic.claude-sonnet-4-6', // PRD #175: Amazon Bedrock default model (overridden by AI_MODEL env var)
|
|
25
|
+
copilot: 'claude-sonnet-4.6', // PRD #587: GitHub Copilot provider - use dot notation (catalog ID); Copilot supports both /chat/completions and /v1/messages for Claude
|
|
25
26
|
};
|
|
26
27
|
/**
|
|
27
28
|
* Get current model for a provider
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot Credential Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves a GitHub token suitable for direct use against api.githubcopilot.com.
|
|
5
|
+
* Hermes/dot-ai uses the raw token directly as the Bearer credential — no
|
|
6
|
+
* token-exchange step is required and the exchange endpoint
|
|
7
|
+
* (api.github.com/copilot_internal/v2/token) can return 404 for some account
|
|
8
|
+
* types, so it is intentionally NOT used here.
|
|
9
|
+
*
|
|
10
|
+
* Supported token types (classic PATs / ghp_* are NOT accepted):
|
|
11
|
+
* gho_* OAuth token (recommended — via `gh auth login`)
|
|
12
|
+
* github_pat_* Fine-grained PAT (needs Copilot Requests permission)
|
|
13
|
+
* ghu_* GitHub App installation token
|
|
14
|
+
*
|
|
15
|
+
* Token resolution priority:
|
|
16
|
+
* 1. GITHUB_COPILOT_TOKEN env var
|
|
17
|
+
* 2. GH_TOKEN env var
|
|
18
|
+
* 3. GITHUB_TOKEN env var
|
|
19
|
+
*
|
|
20
|
+
* On HTTP 401, callers should invoke resolve() again to re-read the chain
|
|
21
|
+
* (credentials may have been refreshed externally) and retry once.
|
|
22
|
+
*
|
|
23
|
+
* PRD #587: GitHub Copilot Provider
|
|
24
|
+
*/
|
|
25
|
+
export interface CopilotCredentialResolver {
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a GitHub token from the environment chain / CLI.
|
|
28
|
+
* Throws if no supported token is found.
|
|
29
|
+
*/
|
|
30
|
+
resolve(): string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a CopilotCredentialResolver.
|
|
34
|
+
*
|
|
35
|
+
* @param overrideToken Optional explicit token (e.g. from env at factory time).
|
|
36
|
+
* If provided and supported it is returned immediately without
|
|
37
|
+
* inspecting the env chain.
|
|
38
|
+
*/
|
|
39
|
+
export declare function makeCopilotCredentialResolver(overrideToken?: string): CopilotCredentialResolver;
|
|
40
|
+
export type CopilotTokenExchanger = CopilotCredentialResolver;
|
|
41
|
+
//# sourceMappingURL=copilot-token-exchanger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-token-exchanger.d.ts","sourceRoot":"","sources":["../../../src/core/providers/copilot-token-exchanger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,OAAO,IAAI,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,CAAC,EAAE,MAAM,GACrB,yBAAyB,CAuB3B;AAID,MAAM,MAAM,qBAAqB,GAAG,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub Copilot Credential Resolver
|
|
4
|
+
*
|
|
5
|
+
* Resolves a GitHub token suitable for direct use against api.githubcopilot.com.
|
|
6
|
+
* Hermes/dot-ai uses the raw token directly as the Bearer credential — no
|
|
7
|
+
* token-exchange step is required and the exchange endpoint
|
|
8
|
+
* (api.github.com/copilot_internal/v2/token) can return 404 for some account
|
|
9
|
+
* types, so it is intentionally NOT used here.
|
|
10
|
+
*
|
|
11
|
+
* Supported token types (classic PATs / ghp_* are NOT accepted):
|
|
12
|
+
* gho_* OAuth token (recommended — via `gh auth login`)
|
|
13
|
+
* github_pat_* Fine-grained PAT (needs Copilot Requests permission)
|
|
14
|
+
* ghu_* GitHub App installation token
|
|
15
|
+
*
|
|
16
|
+
* Token resolution priority:
|
|
17
|
+
* 1. GITHUB_COPILOT_TOKEN env var
|
|
18
|
+
* 2. GH_TOKEN env var
|
|
19
|
+
* 3. GITHUB_TOKEN env var
|
|
20
|
+
*
|
|
21
|
+
* On HTTP 401, callers should invoke resolve() again to re-read the chain
|
|
22
|
+
* (credentials may have been refreshed externally) and retry once.
|
|
23
|
+
*
|
|
24
|
+
* PRD #587: GitHub Copilot Provider
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.makeCopilotCredentialResolver = makeCopilotCredentialResolver;
|
|
28
|
+
const SUPPORTED_PREFIXES = ['gho_', 'github_pat_', 'ghu_'];
|
|
29
|
+
function isSupported(token) {
|
|
30
|
+
return SUPPORTED_PREFIXES.some((p) => token.startsWith(p));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a CopilotCredentialResolver.
|
|
34
|
+
*
|
|
35
|
+
* @param overrideToken Optional explicit token (e.g. from env at factory time).
|
|
36
|
+
* If provided and supported it is returned immediately without
|
|
37
|
+
* inspecting the env chain.
|
|
38
|
+
*/
|
|
39
|
+
function makeCopilotCredentialResolver(overrideToken) {
|
|
40
|
+
return {
|
|
41
|
+
resolve() {
|
|
42
|
+
// 1. Explicit override (e.g. GITHUB_COPILOT_TOKEN passed by factory)
|
|
43
|
+
if (overrideToken && isSupported(overrideToken)) {
|
|
44
|
+
return overrideToken;
|
|
45
|
+
}
|
|
46
|
+
// 2. Env chain
|
|
47
|
+
for (const envVar of ['GITHUB_COPILOT_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN']) {
|
|
48
|
+
const val = process.env[envVar];
|
|
49
|
+
if (val && isSupported(val)) {
|
|
50
|
+
return val;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw new Error('No supported GitHub token found for Copilot. ' +
|
|
54
|
+
'Set GITHUB_COPILOT_TOKEN (gho_*, github_pat_*, or ghu_*). ' +
|
|
55
|
+
'GH_TOKEN and GITHUB_TOKEN are also checked in that order.');
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel-provider.d.ts","sourceRoot":"","sources":["../../../src/core/providers/vercel-provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,OAAO,EACL,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EACd,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"vercel-provider.d.ts","sourceRoot":"","sources":["../../../src/core/providers/vercel-provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,OAAO,EACL,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EACd,MAAM,0BAA0B,CAAC;AA8DlC,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,aAAa,CAAC,CAAyB;IAC/C,OAAO,CAAC,aAAa,CAAiB;gBAE1B,MAAM,EAAE,gBAAgB;IAYpC,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,eAAe;IA+OvB,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,MAAM;IAIzB,YAAY,IAAI,MAAM;IAItB,aAAa,IAAI,OAAO;IAIxB,OAAO,CAAC,iBAAiB;IAyBnB,WAAW,CACf,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,MAAkB,EAC7B,iBAAiB,CAAC,EAAE;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GACA,OAAO,CAAC,UAAU,CAAC;IAwJtB;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAmc/D"}
|
|
@@ -22,6 +22,7 @@ const model_config_1 = require("../model-config");
|
|
|
22
22
|
const investigation_1 = require("../constants/investigation");
|
|
23
23
|
const ai_tracing_1 = require("../tracing/ai-tracing");
|
|
24
24
|
const ai_retry_config_1 = require("../ai-retry-config");
|
|
25
|
+
const copilot_token_exchanger_1 = require("./copilot-token-exchanger");
|
|
25
26
|
// Get all supported provider keys dynamically from CURRENT_MODELS
|
|
26
27
|
const SUPPORTED_PROVIDERS = Object.keys(model_config_1.CURRENT_MODELS);
|
|
27
28
|
class VercelProvider {
|
|
@@ -43,7 +44,8 @@ class VercelProvider {
|
|
|
43
44
|
this.initializeModel();
|
|
44
45
|
}
|
|
45
46
|
validateConfiguration() {
|
|
46
|
-
|
|
47
|
+
// Copilot resolves its credential from the env chain at fetch time — no apiKey required.
|
|
48
|
+
if (!this.apiKey && this.providerType !== 'copilot') {
|
|
47
49
|
throw new Error(constants_1.AI_SERVICE_ERROR_TEMPLATES.API_KEY_REQUIRED(this.providerType));
|
|
48
50
|
}
|
|
49
51
|
if (!SUPPORTED_PROVIDERS.includes(this.providerType)) {
|
|
@@ -169,6 +171,92 @@ class VercelProvider {
|
|
|
169
171
|
// Use .chat() explicitly for custom endpoints to use /chat/completions instead of /responses
|
|
170
172
|
this.modelInstance = provider.chat(this.model);
|
|
171
173
|
return; // Early return - model instance already set
|
|
174
|
+
case 'copilot': {
|
|
175
|
+
// PRD #587: GitHub Copilot provider
|
|
176
|
+
// Uses the raw GitHub token (gho_*, github_pat_*, ghu_*) directly as a
|
|
177
|
+
// Bearer credential against api.githubcopilot.com — no token-exchange step.
|
|
178
|
+
//
|
|
179
|
+
// Routing (mirrors Hermes Agent):
|
|
180
|
+
// - Claude model IDs (claude-*) → createAnthropic at githubcopilot.com
|
|
181
|
+
// because the Copilot OpenAI-compat non-streaming response for Claude omits
|
|
182
|
+
// the "index" field that @ai-sdk/openai requires, causing parse failures.
|
|
183
|
+
// - All other models → createOpenAI at githubcopilot.com (OpenAI-compat path)
|
|
184
|
+
//
|
|
185
|
+
// Model IDs must use dot notation matching the Copilot catalog
|
|
186
|
+
// (e.g. claude-sonnet-4.6, NOT claude-sonnet-4-6).
|
|
187
|
+
// On 401: re-resolve credentials from the env chain and retry once.
|
|
188
|
+
const resolver = (0, copilot_token_exchanger_1.makeCopilotCredentialResolver)(this.apiKey);
|
|
189
|
+
// These headers were captured from VS Code Copilot Chat network traffic.
|
|
190
|
+
// They are required by api.githubcopilot.com to accept the request —
|
|
191
|
+
// the endpoint validates the Integration-Id and Editor-Version before routing.
|
|
192
|
+
// Future maintainers: if GitHub changes the required headers, update here.
|
|
193
|
+
const copilotHeaders = {
|
|
194
|
+
'Copilot-Integration-Id': 'vscode-chat',
|
|
195
|
+
'Editor-Version': 'vscode/1.104.1',
|
|
196
|
+
'Openai-Intent': 'conversation-edits',
|
|
197
|
+
'x-initiator': 'user',
|
|
198
|
+
};
|
|
199
|
+
const COPILOT_FETCH_TIMEOUT_MS = 30000; // 30s — matches git-utils.ts fetchWithTimeout
|
|
200
|
+
const copilotFetch = async (url, init) => {
|
|
201
|
+
const token = resolver.resolve();
|
|
202
|
+
const headers = new Headers(init?.headers);
|
|
203
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
204
|
+
for (const [k, v] of Object.entries(copilotHeaders)) {
|
|
205
|
+
headers.set(k, v);
|
|
206
|
+
}
|
|
207
|
+
const controller = new AbortController();
|
|
208
|
+
const timeoutId = setTimeout(() => controller.abort(), COPILOT_FETCH_TIMEOUT_MS);
|
|
209
|
+
let response;
|
|
210
|
+
try {
|
|
211
|
+
response = await fetch(url, { ...init, headers, signal: controller.signal });
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
clearTimeout(timeoutId);
|
|
215
|
+
}
|
|
216
|
+
if (response.status === 401) {
|
|
217
|
+
// Drain body to allow connection reuse before retrying
|
|
218
|
+
await response.text().catch(() => { });
|
|
219
|
+
// Re-resolve from env chain (credentials may have been refreshed externally)
|
|
220
|
+
const freshToken = resolver.resolve();
|
|
221
|
+
// Build fresh headers for retry — do not mutate the first-attempt object
|
|
222
|
+
const retryHeaders = new Headers(init?.headers);
|
|
223
|
+
retryHeaders.set('Authorization', `Bearer ${freshToken}`);
|
|
224
|
+
for (const [k, v] of Object.entries(copilotHeaders)) {
|
|
225
|
+
retryHeaders.set(k, v);
|
|
226
|
+
}
|
|
227
|
+
const retryController = new AbortController();
|
|
228
|
+
const retryTimeoutId = setTimeout(() => retryController.abort(), COPILOT_FETCH_TIMEOUT_MS);
|
|
229
|
+
try {
|
|
230
|
+
return await fetch(url, { ...init, headers: retryHeaders, signal: retryController.signal });
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
clearTimeout(retryTimeoutId);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return response;
|
|
237
|
+
};
|
|
238
|
+
const isClaudeModel = this.model.startsWith('claude-');
|
|
239
|
+
if (isClaudeModel) {
|
|
240
|
+
// Use Anthropic SDK routed through the Copilot endpoint.
|
|
241
|
+
// baseURL must include /v1 — the SDK appends /messages, so the
|
|
242
|
+
// final URL is https://api.githubcopilot.com/v1/messages.
|
|
243
|
+
const anthropicProvider = (0, anthropic_1.createAnthropic)({
|
|
244
|
+
baseURL: 'https://api.githubcopilot.com/v1',
|
|
245
|
+
apiKey: 'unused', // required by SDK but overridden by copilotFetch
|
|
246
|
+
fetch: copilotFetch,
|
|
247
|
+
});
|
|
248
|
+
this.modelInstance = anthropicProvider(this.model);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
provider = (0, openai_1.createOpenAI)({
|
|
252
|
+
apiKey: 'unused',
|
|
253
|
+
baseURL: 'https://api.githubcopilot.com',
|
|
254
|
+
fetch: copilotFetch,
|
|
255
|
+
});
|
|
256
|
+
this.modelInstance = provider.chat(this.model);
|
|
257
|
+
}
|
|
258
|
+
return; // Early return - model instance already set
|
|
259
|
+
}
|
|
172
260
|
default:
|
|
173
261
|
throw new Error(`Cannot initialize model for provider: ${this.providerType}`);
|
|
174
262
|
}
|
|
@@ -24,17 +24,65 @@ export interface UserPromptsConfig {
|
|
|
24
24
|
cacheTtlSeconds: number;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Per-request override for user prompts repository.
|
|
28
|
+
* When supplied to loadUserPrompts(), bypasses DOT_AI_USER_PROMPTS_REPO env vars
|
|
29
|
+
* for that call while reusing DOT_AI_GIT_TOKEN and the cache TTL.
|
|
30
|
+
*/
|
|
31
|
+
export interface UserPromptsOverride {
|
|
32
|
+
repoUrl: string;
|
|
33
|
+
branch?: string;
|
|
34
|
+
subPath?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Cache state for tracking repository freshness.
|
|
38
|
+
* Cache is invalidated when any of repoUrl, branch, or subPath changes,
|
|
39
|
+
* so we record all three on every clone.
|
|
28
40
|
*/
|
|
29
41
|
interface CacheState {
|
|
30
42
|
lastPullTime: number;
|
|
31
43
|
localPath: string;
|
|
44
|
+
repoUrl: string;
|
|
45
|
+
branch: string;
|
|
46
|
+
subPath: string;
|
|
32
47
|
}
|
|
33
48
|
/**
|
|
34
49
|
* Read user prompts configuration from environment variables
|
|
35
50
|
* Returns null if DOT_AI_USER_PROMPTS_REPO is not set
|
|
36
51
|
*/
|
|
37
52
|
export declare function getUserPromptsConfig(): UserPromptsConfig | null;
|
|
53
|
+
/**
|
|
54
|
+
* Compute the `source` value the REST endpoints expose in their responses.
|
|
55
|
+
* The CLI (vfarcic/dot-ai-cli) uses this string verbatim to tag the skill
|
|
56
|
+
* files it writes, so:
|
|
57
|
+
* - per-request override supplied → override.repoUrl
|
|
58
|
+
* - no override, env-var configured → DOT_AI_USER_PROMPTS_REPO
|
|
59
|
+
* - no override, no env-var → "built-in"
|
|
60
|
+
*
|
|
61
|
+
* URLs flow through scrubSourceUrl before returning, which scrubs BOTH:
|
|
62
|
+
* - userinfo credentials (https://user:token@host/repo)
|
|
63
|
+
* - credential-bearing query params (?access_token=... etc — CodeRabbit
|
|
64
|
+
* Major A)
|
|
65
|
+
*
|
|
66
|
+
* Stability is preserved: scrubSourceUrl is deterministic (fixed `***`
|
|
67
|
+
* placeholder), so two identical credential-bearing inputs produce the same
|
|
68
|
+
* scrubbed source — keeping the CLI's "wipe only my own slice" invariant
|
|
69
|
+
* intact.
|
|
70
|
+
*/
|
|
71
|
+
export declare function computePromptsSource(override?: UserPromptsOverride): string;
|
|
72
|
+
/**
|
|
73
|
+
* Build a UserPromptsConfig from a per-request override.
|
|
74
|
+
* Reuses DOT_AI_GIT_TOKEN and DOT_AI_USER_PROMPTS_CACHE_TTL from the environment;
|
|
75
|
+
* branch and subPath fall back to the same defaults as the env-var path.
|
|
76
|
+
*
|
|
77
|
+
* Validates the override inputs before returning:
|
|
78
|
+
* - repoUrl scheme must be http or https (prevents file://, ssh://, etc.)
|
|
79
|
+
* - subPath must be a relative path inside the cache directory (no '..',
|
|
80
|
+
* no absolute, no null bytes)
|
|
81
|
+
* - branch must match the git-safe character set used elsewhere
|
|
82
|
+
*
|
|
83
|
+
* Throws Error with a credential-scrubbed message on any validation failure.
|
|
84
|
+
*/
|
|
85
|
+
export declare function getUserPromptsConfigFromOverride(override: UserPromptsOverride): UserPromptsConfig;
|
|
38
86
|
/**
|
|
39
87
|
* Get the cache directory for user prompts
|
|
40
88
|
* Tries project-relative tmp first, falls back to system temp
|
|
@@ -45,10 +93,28 @@ export declare function getCacheDirectory(): string;
|
|
|
45
93
|
*/
|
|
46
94
|
export declare function sanitizeUrlForLogging(url: string): string;
|
|
47
95
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
96
|
+
* Source-only deep scrub for URLs that flow into the response body and the
|
|
97
|
+
* on-disk skill frontmatter the CLI writes (PRD #581 / CodeRabbit Major A).
|
|
98
|
+
*
|
|
99
|
+
* In addition to the userinfo scrub from sanitizeUrlForLogging, redacts the
|
|
100
|
+
* VALUES of query parameters whose names look credential-bearing (token, key,
|
|
101
|
+
* secret, password, auth, credential — case-insensitive). The placeholder is
|
|
102
|
+
* a fixed string so the output is deterministic — same input always produces
|
|
103
|
+
* the same source, preserving the CLI's "tag-and-wipe-by-source" invariant.
|
|
104
|
+
*
|
|
105
|
+
* Scope: ONLY called from computePromptsSource. NOT a general-purpose
|
|
106
|
+
* replacement for sanitizeUrlForLogging — the broader hygiene of the shared
|
|
107
|
+
* helper was deferred per the M2 follow-up scope.
|
|
108
|
+
*/
|
|
109
|
+
export declare function scrubSourceUrl(url: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* Load user prompts from a git repository.
|
|
112
|
+
*
|
|
113
|
+
* When `override` is supplied, fetches from that repository for this call only,
|
|
114
|
+
* ignoring DOT_AI_USER_PROMPTS_REPO env vars. Otherwise, uses env-var configuration.
|
|
115
|
+
* Returns empty array if not configured or on error.
|
|
50
116
|
*/
|
|
51
|
-
export declare function loadUserPrompts(logger: Logger, forceRefresh?: boolean): Promise<Prompt[]>;
|
|
117
|
+
export declare function loadUserPrompts(logger: Logger, forceRefresh?: boolean, override?: UserPromptsOverride): Promise<Prompt[]>;
|
|
52
118
|
/**
|
|
53
119
|
* Clear the cache state (useful for testing)
|
|
54
120
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-prompts-loader.d.ts","sourceRoot":"","sources":["../../src/core/user-prompts-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"user-prompts-loader.d.ts","sourceRoot":"","sources":["../../src/core/user-prompts-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAO1C,OAAO,EAAE,MAAM,EAA8B,MAAM,kBAAkB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,UAAU,UAAU;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,GAAG,IAAI,CAsB/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAS3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,mBAAmB,GAC5B,iBAAiB,CAkDnB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAqB1C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUzD;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoBlD;AA2PD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,YAAY,GAAE,OAAe,EAC7B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,MAAM,EAAE,CAAC,CA0HnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,UAAU,GAAG,IAAI,CAE5D"}
|
|
@@ -47,8 +47,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
47
47
|
})();
|
|
48
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
49
|
exports.getUserPromptsConfig = getUserPromptsConfig;
|
|
50
|
+
exports.computePromptsSource = computePromptsSource;
|
|
51
|
+
exports.getUserPromptsConfigFromOverride = getUserPromptsConfigFromOverride;
|
|
50
52
|
exports.getCacheDirectory = getCacheDirectory;
|
|
51
53
|
exports.sanitizeUrlForLogging = sanitizeUrlForLogging;
|
|
54
|
+
exports.scrubSourceUrl = scrubSourceUrl;
|
|
52
55
|
exports.loadUserPrompts = loadUserPrompts;
|
|
53
56
|
exports.clearUserPromptsCache = clearUserPromptsCache;
|
|
54
57
|
exports.getUserPromptsCacheState = getUserPromptsCacheState;
|
|
@@ -79,6 +82,88 @@ function getUserPromptsConfig() {
|
|
|
79
82
|
cacheTtlSeconds,
|
|
80
83
|
};
|
|
81
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Compute the `source` value the REST endpoints expose in their responses.
|
|
87
|
+
* The CLI (vfarcic/dot-ai-cli) uses this string verbatim to tag the skill
|
|
88
|
+
* files it writes, so:
|
|
89
|
+
* - per-request override supplied → override.repoUrl
|
|
90
|
+
* - no override, env-var configured → DOT_AI_USER_PROMPTS_REPO
|
|
91
|
+
* - no override, no env-var → "built-in"
|
|
92
|
+
*
|
|
93
|
+
* URLs flow through scrubSourceUrl before returning, which scrubs BOTH:
|
|
94
|
+
* - userinfo credentials (https://user:token@host/repo)
|
|
95
|
+
* - credential-bearing query params (?access_token=... etc — CodeRabbit
|
|
96
|
+
* Major A)
|
|
97
|
+
*
|
|
98
|
+
* Stability is preserved: scrubSourceUrl is deterministic (fixed `***`
|
|
99
|
+
* placeholder), so two identical credential-bearing inputs produce the same
|
|
100
|
+
* scrubbed source — keeping the CLI's "wipe only my own slice" invariant
|
|
101
|
+
* intact.
|
|
102
|
+
*/
|
|
103
|
+
function computePromptsSource(override) {
|
|
104
|
+
if (override?.repoUrl) {
|
|
105
|
+
return scrubSourceUrl(override.repoUrl);
|
|
106
|
+
}
|
|
107
|
+
const envRepo = process.env.DOT_AI_USER_PROMPTS_REPO;
|
|
108
|
+
if (envRepo) {
|
|
109
|
+
return scrubSourceUrl(envRepo);
|
|
110
|
+
}
|
|
111
|
+
return 'built-in';
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a UserPromptsConfig from a per-request override.
|
|
115
|
+
* Reuses DOT_AI_GIT_TOKEN and DOT_AI_USER_PROMPTS_CACHE_TTL from the environment;
|
|
116
|
+
* branch and subPath fall back to the same defaults as the env-var path.
|
|
117
|
+
*
|
|
118
|
+
* Validates the override inputs before returning:
|
|
119
|
+
* - repoUrl scheme must be http or https (prevents file://, ssh://, etc.)
|
|
120
|
+
* - subPath must be a relative path inside the cache directory (no '..',
|
|
121
|
+
* no absolute, no null bytes)
|
|
122
|
+
* - branch must match the git-safe character set used elsewhere
|
|
123
|
+
*
|
|
124
|
+
* Throws Error with a credential-scrubbed message on any validation failure.
|
|
125
|
+
*/
|
|
126
|
+
function getUserPromptsConfigFromOverride(override) {
|
|
127
|
+
// F1: repoUrl scheme must be http/https
|
|
128
|
+
let parsedUrl;
|
|
129
|
+
try {
|
|
130
|
+
parsedUrl = new URL(override.repoUrl);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new Error(`Invalid override repoUrl: ${sanitizeUrlForLogging(override.repoUrl)} (failed to parse)`);
|
|
134
|
+
}
|
|
135
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
136
|
+
throw new Error(`Invalid override repoUrl scheme: ${parsedUrl.protocol} (only http and https are allowed) for ${sanitizeUrlForLogging(override.repoUrl)}`);
|
|
137
|
+
}
|
|
138
|
+
// F2: subPath must be a safe relative path
|
|
139
|
+
let normalizedSubPath = '';
|
|
140
|
+
if (override.subPath) {
|
|
141
|
+
if (override.subPath.includes('\0')) {
|
|
142
|
+
throw new Error('Invalid override subPath: contains null byte');
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
normalizedSubPath = (0, git_utils_1.sanitizeRelativePath)(override.subPath);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
149
|
+
throw new Error(`Invalid override subPath: ${message}`, { cause: error });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// F3: branch (if supplied) must match the git-safe character set
|
|
153
|
+
const branch = override.branch || 'main';
|
|
154
|
+
if (override.branch !== undefined && !isValidGitBranch(override.branch)) {
|
|
155
|
+
throw new Error(`Invalid override branch name: ${override.branch}`);
|
|
156
|
+
}
|
|
157
|
+
const parsedTtl = parseInt(process.env.DOT_AI_USER_PROMPTS_CACHE_TTL || '86400', 10);
|
|
158
|
+
const cacheTtlSeconds = Number.isNaN(parsedTtl) || parsedTtl < 0 ? 86400 : parsedTtl;
|
|
159
|
+
return {
|
|
160
|
+
repoUrl: override.repoUrl,
|
|
161
|
+
branch,
|
|
162
|
+
subPath: normalizedSubPath,
|
|
163
|
+
gitToken: process.env.DOT_AI_GIT_TOKEN,
|
|
164
|
+
cacheTtlSeconds,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
82
167
|
/**
|
|
83
168
|
* Get the cache directory for user prompts
|
|
84
169
|
* Tries project-relative tmp first, falls back to system temp
|
|
@@ -120,6 +205,47 @@ function sanitizeUrlForLogging(url) {
|
|
|
120
205
|
return url.replace(/\/\/[^@]+@/, '//***@');
|
|
121
206
|
}
|
|
122
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Query-param names whose values look credential-bearing. Conservative
|
|
210
|
+
* allowlist-on-redaction — case-insensitive substring match.
|
|
211
|
+
*/
|
|
212
|
+
const CREDENTIAL_PARAM_RE = /token|key|secret|password|auth|credential/i;
|
|
213
|
+
/**
|
|
214
|
+
* Source-only deep scrub for URLs that flow into the response body and the
|
|
215
|
+
* on-disk skill frontmatter the CLI writes (PRD #581 / CodeRabbit Major A).
|
|
216
|
+
*
|
|
217
|
+
* In addition to the userinfo scrub from sanitizeUrlForLogging, redacts the
|
|
218
|
+
* VALUES of query parameters whose names look credential-bearing (token, key,
|
|
219
|
+
* secret, password, auth, credential — case-insensitive). The placeholder is
|
|
220
|
+
* a fixed string so the output is deterministic — same input always produces
|
|
221
|
+
* the same source, preserving the CLI's "tag-and-wipe-by-source" invariant.
|
|
222
|
+
*
|
|
223
|
+
* Scope: ONLY called from computePromptsSource. NOT a general-purpose
|
|
224
|
+
* replacement for sanitizeUrlForLogging — the broader hygiene of the shared
|
|
225
|
+
* helper was deferred per the M2 follow-up scope.
|
|
226
|
+
*/
|
|
227
|
+
function scrubSourceUrl(url) {
|
|
228
|
+
// Userinfo scrub first; this also handles "//user:pass@" via the regex
|
|
229
|
+
// fallback when URL parsing fails.
|
|
230
|
+
const userinfoScrubbed = sanitizeUrlForLogging(url);
|
|
231
|
+
try {
|
|
232
|
+
const parsed = new URL(userinfoScrubbed);
|
|
233
|
+
let mutated = false;
|
|
234
|
+
for (const name of [...parsed.searchParams.keys()]) {
|
|
235
|
+
if (CREDENTIAL_PARAM_RE.test(name)) {
|
|
236
|
+
parsed.searchParams.set(name, '***');
|
|
237
|
+
mutated = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return mutated ? parsed.toString() : userinfoScrubbed;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Unparseable; return the userinfo-scrubbed form unchanged. Query-param
|
|
244
|
+
// scrubbing on unparseable URLs is meaningless — there's no reliable
|
|
245
|
+
// boundary to walk.
|
|
246
|
+
return userinfoScrubbed;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
123
249
|
/**
|
|
124
250
|
* Validate git branch name to prevent command injection
|
|
125
251
|
* Allows alphanumeric characters, hyphens, underscores, slashes, and dots
|
|
@@ -207,11 +333,34 @@ async function ensureRepository(config, logger, forceRefresh = false) {
|
|
|
207
333
|
const localPath = getCacheDirectory();
|
|
208
334
|
const now = Date.now();
|
|
209
335
|
const ttlMs = config.cacheTtlSeconds * 1000;
|
|
210
|
-
// Check if we need to clone or pull
|
|
211
|
-
|
|
212
|
-
|
|
336
|
+
// Check if we need to clone or pull. The cache is invalidated when any of
|
|
337
|
+
// (repoUrl, branch, subPath) differs from the cached entry so that, for
|
|
338
|
+
// example, a future override with a different branch but the same repoUrl
|
|
339
|
+
// won't serve stale content from the previous clone.
|
|
340
|
+
const cacheMisses = cacheState !== null &&
|
|
341
|
+
(cacheState.repoUrl !== config.repoUrl ||
|
|
342
|
+
cacheState.branch !== config.branch ||
|
|
343
|
+
cacheState.subPath !== config.subPath);
|
|
344
|
+
if (!cacheState || !fs.existsSync(cacheState.localPath) || cacheMisses) {
|
|
345
|
+
// First time, cache directory deleted, or cached coordinates differ - clone fresh
|
|
346
|
+
if (cacheMisses) {
|
|
347
|
+
logger.debug('Cached repo coordinates differ from requested, re-cloning', {
|
|
348
|
+
cachedUrl: sanitizeUrlForLogging(cacheState.repoUrl),
|
|
349
|
+
cachedBranch: cacheState.branch,
|
|
350
|
+
cachedSubPath: cacheState.subPath,
|
|
351
|
+
requestedUrl: sanitizeUrlForLogging(config.repoUrl),
|
|
352
|
+
requestedBranch: config.branch,
|
|
353
|
+
requestedSubPath: config.subPath,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
213
356
|
await cloneRepository(config, localPath, logger);
|
|
214
|
-
cacheState = {
|
|
357
|
+
cacheState = {
|
|
358
|
+
lastPullTime: now,
|
|
359
|
+
localPath,
|
|
360
|
+
repoUrl: config.repoUrl,
|
|
361
|
+
branch: config.branch,
|
|
362
|
+
subPath: config.subPath,
|
|
363
|
+
};
|
|
215
364
|
}
|
|
216
365
|
else if (forceRefresh || now - cacheState.lastPullTime >= ttlMs) {
|
|
217
366
|
// Cache expired or force refresh - pull
|
|
@@ -283,11 +432,28 @@ function loadSkillFolder(dirPath, dirName, logger) {
|
|
|
283
432
|
return prompt;
|
|
284
433
|
}
|
|
285
434
|
/**
|
|
286
|
-
* Load user prompts from
|
|
287
|
-
*
|
|
435
|
+
* Load user prompts from a git repository.
|
|
436
|
+
*
|
|
437
|
+
* When `override` is supplied, fetches from that repository for this call only,
|
|
438
|
+
* ignoring DOT_AI_USER_PROMPTS_REPO env vars. Otherwise, uses env-var configuration.
|
|
439
|
+
* Returns empty array if not configured or on error.
|
|
288
440
|
*/
|
|
289
|
-
async function loadUserPrompts(logger, forceRefresh = false) {
|
|
290
|
-
|
|
441
|
+
async function loadUserPrompts(logger, forceRefresh = false, override) {
|
|
442
|
+
let config;
|
|
443
|
+
try {
|
|
444
|
+
// Override validation can throw on bad scheme / traversal / branch — keep
|
|
445
|
+
// the call inside the catch so a malformed per-request override returns
|
|
446
|
+
// [] rather than propagating an exception to the caller.
|
|
447
|
+
config = override
|
|
448
|
+
? getUserPromptsConfigFromOverride(override)
|
|
449
|
+
: getUserPromptsConfig();
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
const rawMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
453
|
+
const safeMessage = (0, git_utils_1.scrubCredentials)(rawMessage);
|
|
454
|
+
logger.error('Failed to load user prompts, falling back to built-in only', new Error(safeMessage));
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
291
457
|
if (!config) {
|
|
292
458
|
logger.debug('User prompts not configured (DOT_AI_USER_PROMPTS_REPO not set)');
|
|
293
459
|
return [];
|
|
@@ -313,7 +479,10 @@ async function loadUserPrompts(logger, forceRefresh = false) {
|
|
|
313
479
|
const prompt = (0, prompts_1.loadPromptFile)(filePath, 'user');
|
|
314
480
|
prompts.push(prompt);
|
|
315
481
|
loadedNames.add(prompt.name);
|
|
316
|
-
logger.debug('Loaded user prompt', {
|
|
482
|
+
logger.debug('Loaded user prompt', {
|
|
483
|
+
name: prompt.name,
|
|
484
|
+
file: entry.name,
|
|
485
|
+
});
|
|
317
486
|
}
|
|
318
487
|
catch (error) {
|
|
319
488
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -361,8 +530,13 @@ async function loadUserPrompts(logger, forceRefresh = false) {
|
|
|
361
530
|
return prompts;
|
|
362
531
|
}
|
|
363
532
|
catch (error) {
|
|
364
|
-
|
|
365
|
-
|
|
533
|
+
// F5: scrub credentials before they reach loggers/callers. Underlying git
|
|
534
|
+
// errors can echo the authenticated URL (with embedded token) verbatim.
|
|
535
|
+
const rawMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
536
|
+
const safeMessage = (0, git_utils_1.scrubCredentials)(config.gitToken
|
|
537
|
+
? rawMessage.replaceAll(config.gitToken, '***')
|
|
538
|
+
: rawMessage);
|
|
539
|
+
logger.error('Failed to load user prompts, falling back to built-in only', new Error(safeMessage));
|
|
366
540
|
return [];
|
|
367
541
|
}
|
|
368
542
|
}
|
|
@@ -10,6 +10,27 @@ import { RestRouteRegistry } from './rest-route-registry';
|
|
|
10
10
|
import { Logger } from '../core/error-handling';
|
|
11
11
|
import { DotAI } from '../core/index';
|
|
12
12
|
import { PluginManager } from '../core/plugin-manager';
|
|
13
|
+
/**
|
|
14
|
+
* Constant placeholder used when the request URL fails to parse and the
|
|
15
|
+
* caller would otherwise have to choose between logging a potentially
|
|
16
|
+
* credential-bearing query string or dropping the request log entirely. A
|
|
17
|
+
* stable string keeps log-grepping useful.
|
|
18
|
+
*/
|
|
19
|
+
export declare const UNPARSEABLE_QUERY_PLACEHOLDER = "?<redacted-unparseable>";
|
|
20
|
+
/**
|
|
21
|
+
* F3: req.url is logged on every request; with PRD #581 the query string may
|
|
22
|
+
* carry `?repo=<user-supplied-url>` whose value can include credentials. This
|
|
23
|
+
* helper rewrites the `repo` value to its credential-scrubbed form so the
|
|
24
|
+
* raw token doesn't reach the log. Everything else is preserved verbatim.
|
|
25
|
+
*
|
|
26
|
+
* CodeRabbit Major B: on parse failure, we no longer return the input
|
|
27
|
+
* verbatim — an unparseable URL is more likely than a parseable one to hide
|
|
28
|
+
* a credential (a stray character may have broken the parse). Instead, keep
|
|
29
|
+
* the pathname (so the log still tells you which endpoint was hit) but
|
|
30
|
+
* REDACT the entire query string with a fixed placeholder. URLs without a
|
|
31
|
+
* '?' are pass-through (no risk).
|
|
32
|
+
*/
|
|
33
|
+
export declare function sanitizeRequestUrlForLogging(url: string | undefined): string | undefined;
|
|
13
34
|
/**
|
|
14
35
|
* HTTP status codes for REST responses
|
|
15
36
|
*/
|
|
@@ -212,6 +233,18 @@ export declare class RestApiRouter {
|
|
|
212
233
|
* Returns container logs for a pod
|
|
213
234
|
*/
|
|
214
235
|
private handleGetLogs;
|
|
236
|
+
/**
|
|
237
|
+
* Extract and validate the per-request `repo` override (PRD #581).
|
|
238
|
+
*
|
|
239
|
+
* Returns:
|
|
240
|
+
* - { ok: true, override } when no repo param is supplied, override is undefined.
|
|
241
|
+
* - { ok: true, override } when a syntactically valid override URL is supplied.
|
|
242
|
+
* - { ok: false, message } when the override fails validation (HTTP 400).
|
|
243
|
+
*
|
|
244
|
+
* The validation message is run through scrubCredentials so embedded tokens
|
|
245
|
+
* never reach the wire response.
|
|
246
|
+
*/
|
|
247
|
+
private extractPromptsOverride;
|
|
215
248
|
/**
|
|
216
249
|
* Handle prompts list requests
|
|
217
250
|
*/
|
|
@@ -221,7 +254,7 @@ export declare class RestApiRouter {
|
|
|
221
254
|
*/
|
|
222
255
|
private handlePromptsGetRequest;
|
|
223
256
|
/**
|
|
224
|
-
* Handle prompts cache refresh requests (PRD #386)
|
|
257
|
+
* Handle prompts cache refresh requests (PRD #386, extended PRD #581)
|
|
225
258
|
*/
|
|
226
259
|
private handlePromptsCacheRefresh;
|
|
227
260
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/interfaces/rest-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAc,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/interfaces/rest-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,7 +39,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.RestApiRouter = exports.HttpStatus = void 0;
|
|
42
|
+
exports.RestApiRouter = exports.HttpStatus = exports.UNPARSEABLE_QUERY_PLACEHOLDER = void 0;
|
|
43
|
+
exports.sanitizeRequestUrlForLogging = sanitizeRequestUrlForLogging;
|
|
43
44
|
const node_url_1 = require("node:url");
|
|
44
45
|
const openapi_generator_1 = require("./openapi-generator");
|
|
45
46
|
const rest_route_registry_1 = require("./rest-route-registry");
|
|
@@ -47,6 +48,8 @@ const routes_1 = require("./routes");
|
|
|
47
48
|
const resource_sync_handler_1 = require("./resource-sync-handler");
|
|
48
49
|
const embedding_migration_handler_1 = require("./embedding-migration-handler");
|
|
49
50
|
const prompts_1 = require("../tools/prompts");
|
|
51
|
+
const user_prompts_loader_1 = require("../core/user-prompts-loader");
|
|
52
|
+
const git_utils_1 = require("../core/git-utils");
|
|
50
53
|
const generic_session_manager_1 = require("../core/generic-session-manager");
|
|
51
54
|
const session_events_1 = require("../core/session-events");
|
|
52
55
|
const shared_prompt_loader_1 = require("../core/shared-prompt-loader");
|
|
@@ -60,6 +63,51 @@ const manage_knowledge_1 = require("../tools/manage-knowledge");
|
|
|
60
63
|
const user_management_1 = require("./oauth/user-management");
|
|
61
64
|
const request_context_1 = require("./request-context");
|
|
62
65
|
const rbac_1 = require("../core/rbac");
|
|
66
|
+
/**
|
|
67
|
+
* Constant placeholder used when the request URL fails to parse and the
|
|
68
|
+
* caller would otherwise have to choose between logging a potentially
|
|
69
|
+
* credential-bearing query string or dropping the request log entirely. A
|
|
70
|
+
* stable string keeps log-grepping useful.
|
|
71
|
+
*/
|
|
72
|
+
exports.UNPARSEABLE_QUERY_PLACEHOLDER = '?<redacted-unparseable>';
|
|
73
|
+
/**
|
|
74
|
+
* F3: req.url is logged on every request; with PRD #581 the query string may
|
|
75
|
+
* carry `?repo=<user-supplied-url>` whose value can include credentials. This
|
|
76
|
+
* helper rewrites the `repo` value to its credential-scrubbed form so the
|
|
77
|
+
* raw token doesn't reach the log. Everything else is preserved verbatim.
|
|
78
|
+
*
|
|
79
|
+
* CodeRabbit Major B: on parse failure, we no longer return the input
|
|
80
|
+
* verbatim — an unparseable URL is more likely than a parseable one to hide
|
|
81
|
+
* a credential (a stray character may have broken the parse). Instead, keep
|
|
82
|
+
* the pathname (so the log still tells you which endpoint was hit) but
|
|
83
|
+
* REDACT the entire query string with a fixed placeholder. URLs without a
|
|
84
|
+
* '?' are pass-through (no risk).
|
|
85
|
+
*/
|
|
86
|
+
function sanitizeRequestUrlForLogging(url) {
|
|
87
|
+
if (!url)
|
|
88
|
+
return url;
|
|
89
|
+
if (!url.includes('repo='))
|
|
90
|
+
return url;
|
|
91
|
+
try {
|
|
92
|
+
// req.url is path-relative; provide a dummy base for URL parsing.
|
|
93
|
+
const parsed = new node_url_1.URL(url, 'http://internal.invalid');
|
|
94
|
+
const repo = parsed.searchParams.get('repo');
|
|
95
|
+
if (repo) {
|
|
96
|
+
parsed.searchParams.set('repo', (0, user_prompts_loader_1.sanitizeUrlForLogging)(repo));
|
|
97
|
+
}
|
|
98
|
+
// Return path + search only (drop the dummy base).
|
|
99
|
+
return parsed.pathname + parsed.search + parsed.hash;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// F3/Major B: don't echo a potentially credential-bearing query string
|
|
103
|
+
// we couldn't parse. Keep the path (useful for log triage) and replace
|
|
104
|
+
// the rest with a constant marker.
|
|
105
|
+
const qIdx = url.indexOf('?');
|
|
106
|
+
if (qIdx === -1)
|
|
107
|
+
return url;
|
|
108
|
+
return url.slice(0, qIdx) + exports.UNPARSEABLE_QUERY_PLACEHOLDER;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
63
111
|
/**
|
|
64
112
|
* HTTP status codes for REST responses
|
|
65
113
|
*/
|
|
@@ -122,7 +170,10 @@ class RestApiRouter {
|
|
|
122
170
|
this.logger.debug('REST API request received', {
|
|
123
171
|
requestId,
|
|
124
172
|
method: req.method,
|
|
125
|
-
|
|
173
|
+
// F3: req.url may carry a `?repo=<user-supplied-url>` whose query
|
|
174
|
+
// value includes credentials (PRD #581). Sanitize that single
|
|
175
|
+
// value before logging.
|
|
176
|
+
url: sanitizeRequestUrlForLogging(req.url),
|
|
126
177
|
hasBody: !!body,
|
|
127
178
|
});
|
|
128
179
|
// Handle CORS preflight
|
|
@@ -192,9 +243,9 @@ class RestApiRouter {
|
|
|
192
243
|
'GET:/api/v1/namespaces': () => this.handleGetNamespaces(req, res, requestId),
|
|
193
244
|
'GET:/api/v1/events': () => this.handleGetEvents(req, res, requestId, searchParams),
|
|
194
245
|
'GET:/api/v1/logs': () => this.handleGetLogs(req, res, requestId, searchParams),
|
|
195
|
-
'GET:/api/v1/prompts': () => this.handlePromptsListRequest(req, res, requestId),
|
|
196
|
-
'POST:/api/v1/prompts/refresh': () => this.handlePromptsCacheRefresh(req, res, requestId),
|
|
197
|
-
'POST:/api/v1/prompts/:promptName': () => this.handlePromptsGetRequest(req, res, requestId, params.promptName, body),
|
|
246
|
+
'GET:/api/v1/prompts': () => this.handlePromptsListRequest(req, res, requestId, searchParams),
|
|
247
|
+
'POST:/api/v1/prompts/refresh': () => this.handlePromptsCacheRefresh(req, res, requestId, body),
|
|
248
|
+
'POST:/api/v1/prompts/:promptName': () => this.handlePromptsGetRequest(req, res, requestId, params.promptName, body, searchParams),
|
|
198
249
|
'GET:/api/v1/visualize/:sessionId': () => this.handleVisualize(req, res, requestId, params.sessionId, searchParams),
|
|
199
250
|
'GET:/api/v1/events/remediations': () => this.handleRemediationSSE(req, res, requestId),
|
|
200
251
|
'GET:/api/v1/sessions': () => this.handleListSessions(req, res, requestId, searchParams),
|
|
@@ -1035,13 +1086,58 @@ class RestApiRouter {
|
|
|
1035
1086
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'LOGS_ERROR', 'Failed to retrieve logs', { error: errorMessage });
|
|
1036
1087
|
}
|
|
1037
1088
|
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Extract and validate the per-request `repo` override (PRD #581).
|
|
1091
|
+
*
|
|
1092
|
+
* Returns:
|
|
1093
|
+
* - { ok: true, override } when no repo param is supplied, override is undefined.
|
|
1094
|
+
* - { ok: true, override } when a syntactically valid override URL is supplied.
|
|
1095
|
+
* - { ok: false, message } when the override fails validation (HTTP 400).
|
|
1096
|
+
*
|
|
1097
|
+
* The validation message is run through scrubCredentials so embedded tokens
|
|
1098
|
+
* never reach the wire response.
|
|
1099
|
+
*/
|
|
1100
|
+
extractPromptsOverride(repoParam) {
|
|
1101
|
+
// Treat absent (null/undefined) as no override.
|
|
1102
|
+
if (repoParam === undefined || repoParam === null) {
|
|
1103
|
+
return { ok: true, override: undefined };
|
|
1104
|
+
}
|
|
1105
|
+
// The wire contract says `repo` is a string. Anything else (array,
|
|
1106
|
+
// number, object, boolean) is a 400 — otherwise downstream code
|
|
1107
|
+
// would crash to a 500.
|
|
1108
|
+
if (typeof repoParam !== 'string') {
|
|
1109
|
+
return {
|
|
1110
|
+
ok: false,
|
|
1111
|
+
message: `repo must be a string (got ${Array.isArray(repoParam) ? 'array' : typeof repoParam})`,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
const trimmed = repoParam.trim();
|
|
1115
|
+
if (!trimmed) {
|
|
1116
|
+
return { ok: true, override: undefined };
|
|
1117
|
+
}
|
|
1118
|
+
const candidate = { repoUrl: trimmed };
|
|
1119
|
+
try {
|
|
1120
|
+
// Throws on invalid scheme / subPath / branch.
|
|
1121
|
+
(0, user_prompts_loader_1.getUserPromptsConfigFromOverride)(candidate);
|
|
1122
|
+
return { ok: true, override: candidate };
|
|
1123
|
+
}
|
|
1124
|
+
catch (error) {
|
|
1125
|
+
const raw = error instanceof Error ? error.message : 'Invalid override';
|
|
1126
|
+
return { ok: false, message: (0, git_utils_1.scrubCredentials)(raw) };
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1038
1129
|
/**
|
|
1039
1130
|
* Handle prompts list requests
|
|
1040
1131
|
*/
|
|
1041
|
-
async handlePromptsListRequest(req, res, requestId) {
|
|
1132
|
+
async handlePromptsListRequest(req, res, requestId, searchParams) {
|
|
1042
1133
|
try {
|
|
1043
1134
|
this.logger.info('Processing prompts list request', { requestId });
|
|
1044
|
-
const
|
|
1135
|
+
const overrideResult = this.extractPromptsOverride(searchParams.get('repo'));
|
|
1136
|
+
if (!overrideResult.ok) {
|
|
1137
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'VALIDATION_ERROR', overrideResult.message);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const result = await (0, prompts_1.handlePromptsListRequest)({}, this.logger, requestId, overrideResult.override);
|
|
1045
1141
|
const response = {
|
|
1046
1142
|
success: true,
|
|
1047
1143
|
data: result,
|
|
@@ -1067,14 +1163,19 @@ class RestApiRouter {
|
|
|
1067
1163
|
/**
|
|
1068
1164
|
* Handle prompt get requests
|
|
1069
1165
|
*/
|
|
1070
|
-
async handlePromptsGetRequest(req, res, requestId, promptName, body) {
|
|
1166
|
+
async handlePromptsGetRequest(req, res, requestId, promptName, body, searchParams) {
|
|
1071
1167
|
try {
|
|
1072
1168
|
this.logger.info('Processing prompt get request', {
|
|
1073
1169
|
requestId,
|
|
1074
1170
|
promptName,
|
|
1075
1171
|
});
|
|
1172
|
+
const overrideResult = this.extractPromptsOverride(searchParams.get('repo'));
|
|
1173
|
+
if (!overrideResult.ok) {
|
|
1174
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'VALIDATION_ERROR', overrideResult.message);
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1076
1177
|
const bodyObj = body;
|
|
1077
|
-
const result = await (0, prompts_1.handlePromptsGetRequest)({ name: promptName, arguments: bodyObj?.arguments }, this.logger, requestId);
|
|
1178
|
+
const result = await (0, prompts_1.handlePromptsGetRequest)({ name: promptName, arguments: bodyObj?.arguments }, this.logger, requestId, overrideResult.override);
|
|
1078
1179
|
const response = {
|
|
1079
1180
|
success: true,
|
|
1080
1181
|
data: result,
|
|
@@ -1105,16 +1206,23 @@ class RestApiRouter {
|
|
|
1105
1206
|
}
|
|
1106
1207
|
}
|
|
1107
1208
|
/**
|
|
1108
|
-
* Handle prompts cache refresh requests (PRD #386)
|
|
1209
|
+
* Handle prompts cache refresh requests (PRD #386, extended PRD #581)
|
|
1109
1210
|
*/
|
|
1110
|
-
async handlePromptsCacheRefresh(req, res, requestId) {
|
|
1211
|
+
async handlePromptsCacheRefresh(req, res, requestId, body) {
|
|
1111
1212
|
try {
|
|
1112
1213
|
this.logger.info('Processing prompts cache refresh request', {
|
|
1113
1214
|
requestId,
|
|
1114
1215
|
});
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const
|
|
1216
|
+
// body.repo type is checked inside extractPromptsOverride (it accepts
|
|
1217
|
+
// unknown and rejects non-string values with 400 — see F2).
|
|
1218
|
+
const bodyObj = body;
|
|
1219
|
+
const overrideResult = this.extractPromptsOverride(bodyObj?.repo);
|
|
1220
|
+
if (!overrideResult.ok) {
|
|
1221
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'VALIDATION_ERROR', overrideResult.message);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const prompts = await (0, prompts_1.loadAllPrompts)(this.logger, undefined, true, overrideResult.override);
|
|
1225
|
+
const source = (0, user_prompts_loader_1.computePromptsSource)(overrideResult.override);
|
|
1118
1226
|
const response = {
|
|
1119
1227
|
success: true,
|
|
1120
1228
|
data: {
|
|
@@ -1419,7 +1527,12 @@ class RestApiRouter {
|
|
|
1419
1527
|
const status = searchParams.get('status') || undefined;
|
|
1420
1528
|
const limit = Math.min(Math.max(parseInt(searchParams.get('limit') || '50', 10) || 50, 1), 200);
|
|
1421
1529
|
const offset = Math.max(parseInt(searchParams.get('offset') || '0', 10) || 0, 0);
|
|
1422
|
-
this.logger.info('Listing sessions', {
|
|
1530
|
+
this.logger.info('Listing sessions', {
|
|
1531
|
+
requestId,
|
|
1532
|
+
status,
|
|
1533
|
+
limit,
|
|
1534
|
+
offset,
|
|
1535
|
+
});
|
|
1423
1536
|
const sessionManager = new generic_session_manager_1.GenericSessionManager('rem');
|
|
1424
1537
|
const sessionIds = sessionManager.listSessions();
|
|
1425
1538
|
// Load all sessions and build summaries
|
|
@@ -1482,7 +1595,7 @@ class RestApiRouter {
|
|
|
1482
1595
|
const headers = {
|
|
1483
1596
|
'Content-Type': 'text/event-stream',
|
|
1484
1597
|
'Cache-Control': 'no-cache',
|
|
1485
|
-
|
|
1598
|
+
Connection: 'keep-alive',
|
|
1486
1599
|
};
|
|
1487
1600
|
if (this.config.enableCors) {
|
|
1488
1601
|
headers['Access-Control-Allow-Origin'] = '*';
|
|
@@ -68,8 +68,8 @@ export type PromptsListResponse = z.infer<typeof PromptsListResponseSchema>;
|
|
|
68
68
|
*/
|
|
69
69
|
export declare const PromptMessageSchema: z.ZodObject<{
|
|
70
70
|
role: z.ZodEnum<{
|
|
71
|
-
system: "system";
|
|
72
71
|
user: "user";
|
|
72
|
+
system: "system";
|
|
73
73
|
assistant: "assistant";
|
|
74
74
|
}>;
|
|
75
75
|
content: z.ZodObject<{
|
|
@@ -94,8 +94,8 @@ export declare const PromptGetDataSchema: z.ZodObject<{
|
|
|
94
94
|
description: z.ZodOptional<z.ZodString>;
|
|
95
95
|
messages: z.ZodArray<z.ZodObject<{
|
|
96
96
|
role: z.ZodEnum<{
|
|
97
|
-
system: "system";
|
|
98
97
|
user: "user";
|
|
98
|
+
system: "system";
|
|
99
99
|
assistant: "assistant";
|
|
100
100
|
}>;
|
|
101
101
|
content: z.ZodObject<{
|
|
@@ -115,8 +115,8 @@ export declare const PromptGetResponseSchema: z.ZodObject<{
|
|
|
115
115
|
description: z.ZodOptional<z.ZodString>;
|
|
116
116
|
messages: z.ZodArray<z.ZodObject<{
|
|
117
117
|
role: z.ZodEnum<{
|
|
118
|
-
system: "system";
|
|
119
118
|
user: "user";
|
|
119
|
+
system: "system";
|
|
120
120
|
assistant: "assistant";
|
|
121
121
|
}>;
|
|
122
122
|
content: z.ZodObject<{
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare function mergePrompts(builtInPrompts: Prompt[], userPrompts: Prom
|
|
|
42
42
|
* Loads all prompts (built-in + user) with collision detection
|
|
43
43
|
* This is the main entry point for loading prompts
|
|
44
44
|
*/
|
|
45
|
-
export declare function loadAllPrompts(logger: Logger, baseDir?: string, forceRefresh?: boolean): Promise<Prompt[]>;
|
|
45
|
+
export declare function loadAllPrompts(logger: Logger, baseDir?: string, forceRefresh?: boolean, override?: import('../core/user-prompts-loader.js').UserPromptsOverride): Promise<Prompt[]>;
|
|
46
46
|
export interface PromptsListArgs {
|
|
47
47
|
baseDir?: string;
|
|
48
48
|
excludeFileSkills?: boolean;
|
|
@@ -53,11 +53,16 @@ interface PromptsListResponse {
|
|
|
53
53
|
description: string;
|
|
54
54
|
arguments?: PromptArgument[];
|
|
55
55
|
}>;
|
|
56
|
+
source: string;
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
58
|
-
* Handle prompts/list MCP request
|
|
59
|
+
* Handle prompts/list MCP request.
|
|
60
|
+
*
|
|
61
|
+
* The optional `override` parameter (PRD #581) is REST-only; MCP callers always
|
|
62
|
+
* pass undefined since there's no natural way for MCP to express a per-request
|
|
63
|
+
* repo override.
|
|
59
64
|
*/
|
|
60
|
-
export declare function handlePromptsListRequest(args: PromptsListArgs | undefined, logger: Logger, requestId: string): Promise<PromptsListResponse>;
|
|
65
|
+
export declare function handlePromptsListRequest(args: PromptsListArgs | undefined, logger: Logger, requestId: string, override?: import('../core/user-prompts-loader.js').UserPromptsOverride): Promise<PromptsListResponse>;
|
|
61
66
|
interface PromptsGetArgs {
|
|
62
67
|
name: string;
|
|
63
68
|
arguments?: Record<string, string>;
|
|
@@ -73,10 +78,15 @@ interface PromptsGetResponse {
|
|
|
73
78
|
};
|
|
74
79
|
}>;
|
|
75
80
|
files?: PromptFile[];
|
|
81
|
+
source: string;
|
|
76
82
|
}
|
|
77
83
|
/**
|
|
78
|
-
* Handle prompts/get MCP request
|
|
84
|
+
* Handle prompts/get MCP request.
|
|
85
|
+
*
|
|
86
|
+
* The optional `override` parameter (PRD #581) is REST-only; MCP callers always
|
|
87
|
+
* pass undefined since there's no natural way for MCP to express a per-request
|
|
88
|
+
* repo override.
|
|
79
89
|
*/
|
|
80
|
-
export declare function handlePromptsGetRequest(args: PromptsGetArgs, logger: Logger, requestId: string): Promise<PromptsGetResponse>;
|
|
90
|
+
export declare function handlePromptsGetRequest(args: PromptsGetArgs, logger: Logger, requestId: string, override?: import('../core/user-prompts-loader.js').UserPromptsOverride): Promise<PromptsGetResponse>;
|
|
81
91
|
export {};
|
|
82
92
|
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/tools/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAQhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;CACtB;AA8ED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,UAAU,GAAG,MAAmB,EACxC,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAyCR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAoC7E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,cAAc,EAAE,MAAM,EAAE,EACxB,WAAW,EAAE,MAAM,EAAE,EACrB,MAAM,EAAE,MAAM,GACb,MAAM,EAAE,CAmBV;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,GAAE,OAAe,
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/tools/prompts.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/prompts.js
CHANGED
|
@@ -209,14 +209,16 @@ function mergePrompts(builtInPrompts, userPrompts, logger) {
|
|
|
209
209
|
* Loads all prompts (built-in + user) with collision detection
|
|
210
210
|
* This is the main entry point for loading prompts
|
|
211
211
|
*/
|
|
212
|
-
async function loadAllPrompts(logger, baseDir, forceRefresh = false) {
|
|
212
|
+
async function loadAllPrompts(logger, baseDir, forceRefresh = false, override) {
|
|
213
213
|
// Load built-in prompts (synchronous)
|
|
214
214
|
const builtInPrompts = loadBuiltInPrompts(logger, baseDir);
|
|
215
|
-
// Load user prompts from git repository (async, graceful failure)
|
|
215
|
+
// Load user prompts from git repository (async, graceful failure). When a
|
|
216
|
+
// per-request override is supplied (PRD #581), it replaces the env-var
|
|
217
|
+
// configured repo for this single call.
|
|
216
218
|
let userPrompts = [];
|
|
217
219
|
try {
|
|
218
220
|
const { loadUserPrompts } = await Promise.resolve().then(() => __importStar(require('../core/user-prompts-loader.js')));
|
|
219
|
-
userPrompts = await loadUserPrompts(logger, forceRefresh);
|
|
221
|
+
userPrompts = await loadUserPrompts(logger, forceRefresh, override);
|
|
220
222
|
}
|
|
221
223
|
catch (error) {
|
|
222
224
|
logger.debug('User prompts loader not available or failed', {
|
|
@@ -234,12 +236,16 @@ async function loadAllPrompts(logger, baseDir, forceRefresh = false) {
|
|
|
234
236
|
return allPrompts;
|
|
235
237
|
}
|
|
236
238
|
/**
|
|
237
|
-
* Handle prompts/list MCP request
|
|
239
|
+
* Handle prompts/list MCP request.
|
|
240
|
+
*
|
|
241
|
+
* The optional `override` parameter (PRD #581) is REST-only; MCP callers always
|
|
242
|
+
* pass undefined since there's no natural way for MCP to express a per-request
|
|
243
|
+
* repo override.
|
|
238
244
|
*/
|
|
239
|
-
async function handlePromptsListRequest(args, logger, requestId) {
|
|
245
|
+
async function handlePromptsListRequest(args, logger, requestId, override) {
|
|
240
246
|
try {
|
|
241
247
|
logger.info('Processing prompts/list request', { requestId });
|
|
242
|
-
const allPrompts = await loadAllPrompts(logger, process.env.NODE_ENV === 'test' ? args?.baseDir : undefined);
|
|
248
|
+
const allPrompts = await loadAllPrompts(logger, process.env.NODE_ENV === 'test' ? args?.baseDir : undefined, false, override);
|
|
243
249
|
// Filter out file-dependent skills when requested (MCP clients can't deliver files)
|
|
244
250
|
const prompts = args?.excludeFileSkills
|
|
245
251
|
? allPrompts.filter(p => !p.files || p.files.length === 0)
|
|
@@ -259,8 +265,10 @@ async function handlePromptsListRequest(args, logger, requestId) {
|
|
|
259
265
|
requestId,
|
|
260
266
|
promptCount: promptList.length,
|
|
261
267
|
});
|
|
268
|
+
const { computePromptsSource } = await Promise.resolve().then(() => __importStar(require('../core/user-prompts-loader.js')));
|
|
262
269
|
return {
|
|
263
270
|
prompts: promptList,
|
|
271
|
+
source: computePromptsSource(override),
|
|
264
272
|
};
|
|
265
273
|
}
|
|
266
274
|
catch (error) {
|
|
@@ -274,9 +282,13 @@ async function handlePromptsListRequest(args, logger, requestId) {
|
|
|
274
282
|
}
|
|
275
283
|
}
|
|
276
284
|
/**
|
|
277
|
-
* Handle prompts/get MCP request
|
|
285
|
+
* Handle prompts/get MCP request.
|
|
286
|
+
*
|
|
287
|
+
* The optional `override` parameter (PRD #581) is REST-only; MCP callers always
|
|
288
|
+
* pass undefined since there's no natural way for MCP to express a per-request
|
|
289
|
+
* repo override.
|
|
278
290
|
*/
|
|
279
|
-
async function handlePromptsGetRequest(args, logger, requestId) {
|
|
291
|
+
async function handlePromptsGetRequest(args, logger, requestId, override) {
|
|
280
292
|
try {
|
|
281
293
|
logger.info('Processing prompts/get request', {
|
|
282
294
|
requestId,
|
|
@@ -285,7 +297,7 @@ async function handlePromptsGetRequest(args, logger, requestId) {
|
|
|
285
297
|
if (!args.name) {
|
|
286
298
|
throw new Error(validation_1.VALIDATION_MESSAGES.MISSING_PARAMETER('name'));
|
|
287
299
|
}
|
|
288
|
-
const prompts = await loadAllPrompts(logger, process.env.NODE_ENV === 'test' ? args?.baseDir : undefined);
|
|
300
|
+
const prompts = await loadAllPrompts(logger, process.env.NODE_ENV === 'test' ? args?.baseDir : undefined, false, override);
|
|
289
301
|
const prompt = prompts.find(p => p.name === args.name);
|
|
290
302
|
if (!prompt) {
|
|
291
303
|
throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.MEDIUM, `Prompt not found: ${args.name}`, {
|
|
@@ -322,6 +334,7 @@ async function handlePromptsGetRequest(args, logger, requestId) {
|
|
|
322
334
|
promptName: prompt.name,
|
|
323
335
|
argumentsProvided: Object.keys(providedArgs).length,
|
|
324
336
|
});
|
|
337
|
+
const { computePromptsSource } = await Promise.resolve().then(() => __importStar(require('../core/user-prompts-loader.js')));
|
|
325
338
|
// Convert to MCP prompts/get response format
|
|
326
339
|
const response = {
|
|
327
340
|
description: prompt.description,
|
|
@@ -334,6 +347,7 @@ async function handlePromptsGetRequest(args, logger, requestId) {
|
|
|
334
347
|
},
|
|
335
348
|
},
|
|
336
349
|
],
|
|
350
|
+
source: computePromptsSource(override),
|
|
337
351
|
};
|
|
338
352
|
if (prompt.files && prompt.files.length > 0) {
|
|
339
353
|
response.files = prompt.files;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vfarcic/dot-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance",
|
|
5
5
|
"mcpName": "io.github.vfarcic/dot-ai",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"globals": "^17.2.0",
|
|
102
102
|
"prettier": "^3.0.0",
|
|
103
103
|
"ts-node": "^10.9.0",
|
|
104
|
+
"tsx": "^4.22.3",
|
|
104
105
|
"typescript": "^5.0.0",
|
|
105
106
|
"typescript-eslint": "^8.54.0",
|
|
106
107
|
"uuid": "^14.0.0",
|