praxis-agent 0.43.1 → 0.45.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.
@@ -7,9 +7,13 @@ import { validateClaudeHooks } from '../hooks/claude-hooks.js';
7
7
  import { validateClaudeMcpConfiguration } from '../mcp/claude-mcp-tools.js';
8
8
  import { loadClaudeAutoModeConfig } from '../permissions/claude-auto-classifier.js';
9
9
  import { ClaudePermissionResolver, validateClaudePermissionSettings, } from '../permissions/claude-permission-resolver.js';
10
- import { readPluginRegistry, validateClaudePlugin, } from '../plugins/claude-plugin-runtime.js';
10
+ import { loadClaudePlugins, readPluginRegistry, validateClaudePlugin, } from '../plugins/claude-plugin-runtime.js';
11
+ import { assessWorkspaceTrust, hasWorkspaceProviderSelection, workspaceTrustInventory, } from '../security/workspace-trust.js';
11
12
  import { loadNativeContextResources, loadNativeSettings, loadNativeSharedResources, } from '../persistence/native-resources.js';
12
13
  import { parseContextEnvironment, parseProviderEnvironment, } from '../providers/environment.js';
14
+ import { resolveProviderCredential } from '../providers/provider-auth.js';
15
+ import { ProviderCredentialVault } from '../persistence/provider-credential-vault.js';
16
+ import { resolveProviderTarget } from '../providers/provider-settings.js';
13
17
  import { ContextBudget } from '../core/context-budget.js';
14
18
  import { ModelPricingRegistry } from '../core/usage.js';
15
19
  import { redactSensitiveText, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
@@ -127,6 +131,7 @@ async function capture(id, operation) {
127
131
  export async function runDoctor(options) {
128
132
  const configRoot = resolve(options.configRoot);
129
133
  const cwd = resolve(options.cwd);
134
+ const claudeStatePath = resolve(options.claudeStatePath ?? resolve(configRoot, 'state.json'));
130
135
  const sensitiveValues = sensitiveEnvironmentValues(options.environment);
131
136
  const diagnosticOptions = {
132
137
  version: options.version,
@@ -163,6 +168,32 @@ export async function runDoctor(options) {
163
168
  });
164
169
  return nativeResources;
165
170
  };
171
+ const providerTrustAssessment = async () => {
172
+ const shared = await loadNativeSharedResources({
173
+ root: configRoot,
174
+ cwd,
175
+ environment: options.environment,
176
+ includeProjectMemory: false,
177
+ });
178
+ const plugins = await loadClaudePlugins({
179
+ configRoot,
180
+ cwd,
181
+ pluginDirectories: [],
182
+ pluginUrls: [],
183
+ loadInstalled: true,
184
+ readOnlyExecutables: true,
185
+ environment: options.environment,
186
+ });
187
+ const settings = [...shared.settings, ...plugins.settings];
188
+ if (!hasWorkspaceProviderSelection(settings))
189
+ return false;
190
+ const assessment = await assessWorkspaceTrust(await workspaceTrustInventory({
191
+ cwd,
192
+ settings,
193
+ mcp: [...shared.mcp, ...plugins.mcp],
194
+ }), claudeStatePath);
195
+ return assessment.status === 'trusted';
196
+ };
166
197
  const checks = [];
167
198
  checks.push(await capture('installation', async () => {
168
199
  const executablePath = await canonicalExecutable(options.executablePath);
@@ -197,17 +228,19 @@ export async function runDoctor(options) {
197
228
  };
198
229
  }));
199
230
  checks.push(await capture('provider', async () => {
200
- const provider = parseProviderEnvironment(options.environment);
231
+ const target = await resolveProviderTarget({
232
+ configRoot,
233
+ cwd,
234
+ environment: options.environment,
235
+ includeProjectSettings: await providerTrustAssessment(),
236
+ });
237
+ const controlsEnvironment = {
238
+ ...options.environment,
239
+ PRAXIS_PROVIDER: target.protocol === 'anthropic-messages' ? 'anthropic' : 'openai',
240
+ PRAXIS_BASE_URL: target.baseUrl,
241
+ };
242
+ const provider = parseProviderEnvironment(controlsEnvironment);
201
243
  parseContextEnvironment(options.environment);
202
- const apiKey = options.environment.PRAXIS_API_KEY;
203
- const model = options.environment.PRAXIS_MODEL;
204
- if (!apiKey || apiKey.trim().length === 0) {
205
- throw new Error('PRAXIS_API_KEY is required');
206
- }
207
- if (!model || model.trim().length === 0) {
208
- throw new Error('PRAXIS_MODEL is required');
209
- }
210
- const baseUrl = safeBaseUrl(provider.baseUrl);
211
244
  const contextWindow = options.environment.PRAXIS_CONTEXT_WINDOW_TOKENS;
212
245
  const contextReserve = options.environment.PRAXIS_CONTEXT_RESERVE_TOKENS;
213
246
  if (contextWindow !== undefined) {
@@ -220,19 +253,64 @@ export async function runDoctor(options) {
220
253
  : { reserveTokens: parsedReserve }),
221
254
  });
222
255
  }
223
- const pricing = ModelPricingRegistry.fromEnvironment(options.environment.PRAXIS_PRICING_JSON);
224
- const pricingDiagnosis = pricing.diagnose(model.trim());
225
- const knownPricing = pricingDiagnosis.source !== 'unknown';
256
+ const vault = new ProviderCredentialVault({
257
+ configRoot,
258
+ environment: options.environment,
259
+ });
260
+ const credential = await resolveProviderCredential({
261
+ target,
262
+ environment: options.environment,
263
+ vault,
264
+ commandRunner: async () => ({
265
+ stdout: '__praxis_doctor_credential_placeholder__',
266
+ exitCode: 0,
267
+ }),
268
+ });
269
+ const pricingDiagnosis = (() => {
270
+ if (target.billingMode !== 'api')
271
+ return undefined;
272
+ return ModelPricingRegistry.fromEnvironment(options.environment.PRAXIS_PRICING_JSON).diagnose(target.modelId);
273
+ })();
274
+ const helperSkipped = credential.source.source === 'command';
275
+ const safeCredential = {
276
+ type: credential.type,
277
+ source: credential.source.source,
278
+ ...(credential.source.source === 'env'
279
+ ? { name: credential.source.name }
280
+ : {}),
281
+ ...(credential.type === 'oauth'
282
+ ? {
283
+ expiry: credential.expiresAt > Date.now()
284
+ ? 'valid'
285
+ : 'expired',
286
+ }
287
+ : {}),
288
+ };
289
+ const warnings = [
290
+ ...(helperSkipped
291
+ ? ['credential helper execution was intentionally skipped']
292
+ : []),
293
+ ...(pricingDiagnosis?.source === 'unknown'
294
+ ? ['model pricing is unavailable and fail-closed']
295
+ : []),
296
+ ];
226
297
  return {
227
- ...(knownPricing ? {} : { status: 'warn' }),
228
- summary: knownPricing
229
- ? `${provider.provider} provider environment is valid; model pricing is ${pricingDiagnosis.source}`
230
- : `${provider.provider} provider environment is valid; model pricing is unavailable and fail-closed`,
298
+ ...(warnings.length === 0 ? {} : { status: 'warn' }),
299
+ summary: warnings.length === 0
300
+ ? `${target.providerId} provider target is valid`
301
+ : `${target.providerId} provider target is valid; ${warnings.join('; ')}`,
231
302
  details: {
232
- provider: provider.provider,
233
- model,
234
- baseUrl,
235
- pricing: pricingDiagnosis,
303
+ provider: target.providerId,
304
+ profile: target.profileId,
305
+ model: target.modelId,
306
+ protocol: target.protocol,
307
+ baseUrl: safeBaseUrl(provider.baseUrl),
308
+ billingMode: target.billingMode,
309
+ experimental: target.experimental,
310
+ credential: safeCredential,
311
+ ...(pricingDiagnosis === undefined
312
+ ? {}
313
+ : { pricing: pricingDiagnosis }),
236
314
  },
237
315
  };
238
316
  }));
@@ -0,0 +1,72 @@
1
+ export declare class ProviderKeychainUnavailableError extends Error {
2
+ constructor();
3
+ }
4
+ export interface ProviderCredentialKey {
5
+ providerId: string;
6
+ profileId: string;
7
+ }
8
+ export interface ApiKeyCredential {
9
+ type: 'api-key';
10
+ secret: string;
11
+ revision: number;
12
+ updatedAt: string;
13
+ }
14
+ export interface ApiKeyCredentialInput {
15
+ type: 'api-key';
16
+ secret: string;
17
+ }
18
+ export interface OAuthCredential {
19
+ type: 'oauth';
20
+ accessToken: string;
21
+ refreshToken: string;
22
+ expiresAt: number;
23
+ accountId?: string;
24
+ revision: number;
25
+ updatedAt: string;
26
+ }
27
+ export interface OAuthCredentialInput {
28
+ type: 'oauth';
29
+ accessToken: string;
30
+ refreshToken: string;
31
+ expiresAt: number;
32
+ accountId?: string;
33
+ }
34
+ export type ProviderCredentialRecord = ApiKeyCredential | OAuthCredential;
35
+ export type ProviderCredentialInput = ApiKeyCredentialInput | OAuthCredentialInput;
36
+ export interface ProviderCredentialMetadata {
37
+ key: ProviderCredentialKey;
38
+ type: ProviderCredentialRecord['type'];
39
+ revision: number;
40
+ updatedAt: string;
41
+ expiresAt?: number;
42
+ }
43
+ export interface ProviderCredentialKeychainAdapter {
44
+ read(service: string): Promise<string | undefined>;
45
+ write(service: string, serializedEnvelope: string): Promise<void>;
46
+ delete(service: string): Promise<void>;
47
+ }
48
+ export interface ProviderCredentialVaultOptions {
49
+ configRoot: string;
50
+ useKeychain?: boolean;
51
+ keychain?: ProviderCredentialKeychainAdapter;
52
+ environment?: Readonly<Record<string, string | undefined>>;
53
+ }
54
+ export declare function providerCredentialFilePath(configRoot: string): string;
55
+ export declare function providerCredentialService(configRoot: string): string;
56
+ export declare class ProviderCredentialVault {
57
+ private readonly root;
58
+ private readonly file;
59
+ private readonly useKeychain;
60
+ private readonly service;
61
+ private readonly keychain;
62
+ private readonly lease;
63
+ constructor(options: ProviderCredentialVaultOptions);
64
+ read(key: ProviderCredentialKey): Promise<ProviderCredentialRecord | undefined>;
65
+ list(): Promise<ProviderCredentialMetadata[]>;
66
+ modify(key: ProviderCredentialKey, callback: (current: ProviderCredentialRecord | undefined) => ProviderCredentialInput | undefined | Promise<ProviderCredentialInput | undefined>): Promise<ProviderCredentialRecord | undefined>;
67
+ delete(key: ProviderCredentialKey): Promise<void>;
68
+ private readReconciled;
69
+ private persist;
70
+ private acquire;
71
+ }
72
+ //# sourceMappingURL=provider-credential-vault.d.ts.map