@omercnet/paseo-context-mode 1.2.2 → 1.2.3-next.233.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-context-mode",
3
- "version": "1.2.2",
3
+ "version": "1.2.3-next.233.1",
4
4
  "type": "module",
5
5
  "description": "Provider-aware Context Mode health, savings, and MCP activation for Paseo.",
6
6
  "license": "MIT",
@@ -38,10 +38,11 @@
38
38
  "server",
39
39
  "shared",
40
40
  "paseo-plugin.json",
41
- "OVERVIEW.md"
41
+ "OVERVIEW.md",
42
+ "npm-shrinkwrap.json"
42
43
  ],
43
44
  "scripts": {
44
- "check": "biome check .",
45
+ "check": "biome check . && node ../scripts/shrinkwrap.mjs --check .",
45
46
  "check:write": "biome check --write .",
46
47
  "test": "vitest run",
47
48
  "test:unit": "vitest run",
@@ -51,10 +52,10 @@
51
52
  },
52
53
  "devDependencies": {
53
54
  "@biomejs/biome": "^2.5.10",
54
- "@getpaseo/cli": "0.11.0-beta.5",
55
- "@getpaseo/client": "0.11.0-beta.5",
56
- "@getpaseo/plugin": "0.11.0-beta.5",
57
- "@getpaseo/protocol": "0.11.0-beta.5",
55
+ "@getpaseo/cli": "0.11.1",
56
+ "@getpaseo/client": "0.11.1",
57
+ "@getpaseo/plugin": "0.11.1",
58
+ "@getpaseo/protocol": "0.11.1",
58
59
  "@tanstack/react-query": "^5.102.3",
59
60
  "@types/node": "^24.10.1",
60
61
  "@types/react": "^19.2.18",
@@ -8,7 +8,12 @@ import type {
8
8
  IntegrationAudit,
9
9
  } from "../shared";
10
10
  import { type BinaryResolution, type LaunchDescriptor, resolveContextModeBinary } from "./binary";
11
- import { createIntegrationAudit } from "./integration";
11
+ import {
12
+ createIntegrationAudit,
13
+ type IntegrationDependencies,
14
+ type ProviderConfigSource,
15
+ readProviderOverrides,
16
+ } from "./integration";
12
17
  import {
13
18
  callContextModeTool,
14
19
  inspectContextMode,
@@ -28,6 +33,7 @@ export interface ContextModeHandlerDependencies {
28
33
  createAudit?: (
29
34
  settings: ContextModeSettings,
30
35
  runtime: { path: string; version: string | null },
36
+ dependencies?: IntegrationDependencies,
31
37
  ) => Promise<IntegrationAudit>;
32
38
  }
33
39
 
@@ -35,7 +41,10 @@ export interface ContextModeHandlers {
35
41
  status(input: ContextModeRefreshInput): Promise<BinaryStatus>;
36
42
  doctor(input: ContextModeRefreshInput): Promise<CommandReport>;
37
43
  stats(input: ContextModeRefreshInput): Promise<CommandReport>;
38
- audit(input: ContextModeRefreshInput): Promise<IntegrationAudit>;
44
+ audit(
45
+ input: ContextModeRefreshInput,
46
+ context?: { paseo?: ProviderConfigSource },
47
+ ): Promise<IntegrationAudit>;
39
48
  }
40
49
 
41
50
  interface CacheEntry<T> {
@@ -170,7 +179,10 @@ export function createContextModeHandlers(
170
179
  });
171
180
  }
172
181
 
173
- async function audit(_input: ContextModeRefreshInput): Promise<IntegrationAudit> {
182
+ async function audit(
183
+ _input: ContextModeRefreshInput,
184
+ context?: { paseo?: ProviderConfigSource },
185
+ ): Promise<IntegrationAudit> {
174
186
  const resolved = await settingsAndBinary();
175
187
  if (resolved.state === "unavailable") {
176
188
  return {
@@ -185,10 +197,11 @@ export function createContextModeHandlers(
185
197
  version: null,
186
198
  tools: [],
187
199
  }));
188
- return buildAudit(resolved.settings, {
189
- path: resolved.binary.path,
190
- version: inspection.version,
191
- });
200
+ return buildAudit(
201
+ resolved.settings,
202
+ { path: resolved.binary.path, version: inspection.version },
203
+ { providers: await readProviderOverrides(context?.paseo) },
204
+ );
192
205
  }
193
206
 
194
207
  return {
@@ -54,6 +54,52 @@ export interface IntegrationDependencies {
54
54
  fileContains?: (path: string, text: string) => Promise<boolean>;
55
55
  now?: () => Date;
56
56
  env?: NodeJS.ProcessEnv;
57
+ providers?: ProviderOverrides;
58
+ }
59
+
60
+ export type ProviderOverrides = Readonly<
61
+ Record<string, { extends?: string; env?: Readonly<Record<string, string>> }>
62
+ >;
63
+
64
+ /** Structural slice of the hook/RPC `paseo` API; avoids a client-package import. */
65
+ export interface ProviderConfigSource {
66
+ config: { get(): Promise<{ config: { providers?: unknown } }> };
67
+ }
68
+
69
+ /**
70
+ * Reads `agents.providers` from the daemon. Paseo applies a provider's own `env` and `extends`
71
+ * after the `agent.create` hook runs, so the hooks must look them up to find the right storage.
72
+ * Failures degrade to "no overrides" (canonical provider behavior).
73
+ */
74
+ export async function readProviderOverrides(
75
+ paseo: ProviderConfigSource | undefined,
76
+ ): Promise<ProviderOverrides> {
77
+ try {
78
+ const { config } = (await paseo?.config.get()) ?? {};
79
+ const entries: Array<[string, { extends?: string; env?: Record<string, string> }]> = [];
80
+ for (const [id, value] of Object.entries(config?.providers ?? {})) {
81
+ if (typeof value !== "object" || value === null) continue;
82
+ const { extends: parent, env } = value as { extends?: unknown; env?: unknown };
83
+ entries.push([
84
+ id,
85
+ {
86
+ ...(typeof parent === "string" ? { extends: parent } : {}),
87
+ ...(typeof env === "object" && env !== null && !Array.isArray(env)
88
+ ? {
89
+ env: Object.fromEntries(
90
+ Object.entries(env).filter(
91
+ (entry): entry is [string, string] => typeof entry[1] === "string",
92
+ ),
93
+ ),
94
+ }
95
+ : {}),
96
+ },
97
+ ]);
98
+ }
99
+ return Object.fromEntries(entries);
100
+ } catch {
101
+ return {};
102
+ }
57
103
  }
58
104
 
59
105
  export interface ContextModeStorageOptions {
@@ -241,7 +287,23 @@ const PROVIDER_POLICIES: Readonly<Record<Exclude<AuditedProvider, "omp-plugin">,
241
287
 
242
288
  function providerPolicy(provider: string): ProviderPolicy | null {
243
289
  if (provider === "omp-plugin" || provider.startsWith("omp-plugin-")) return OMP_POLICY;
244
- return PROVIDER_POLICIES[provider as keyof typeof PROVIDER_POLICIES] ?? null;
290
+ return Object.hasOwn(PROVIDER_POLICIES, provider)
291
+ ? PROVIDER_POLICIES[provider as keyof typeof PROVIDER_POLICIES]
292
+ : null;
293
+ }
294
+
295
+ /**
296
+ * Mirrors Paseo's single-level `extends`: a derived provider uses its parent's policy, and its
297
+ * effective env is the parent's override env overlaid with its own.
298
+ */
299
+ function resolveProvider(
300
+ id: string,
301
+ providers: ProviderOverrides = {},
302
+ ): { base: string; policy: ProviderPolicy | null; env: Record<string, string> } {
303
+ const own = Object.hasOwn(providers, id) ? providers[id] : undefined;
304
+ const base = own?.extends ?? id;
305
+ const parent = base !== id && Object.hasOwn(providers, base) ? providers[base] : undefined;
306
+ return { base, policy: providerPolicy(base), env: { ...parent?.env, ...own?.env } };
245
307
  }
246
308
 
247
309
  async function defaultPathExists(path: string): Promise<boolean> {
@@ -322,24 +384,40 @@ export async function createIntegrationAudit(
322
384
  const pathExists = dependencies.pathExists ?? defaultPathExists;
323
385
  const fileContains = dependencies.fileContains ?? defaultFileContains;
324
386
  const hostEnvironment = dependencies.env ?? process.env;
325
- const context = providerContext(home, undefined, hostEnvironment);
326
- const nativeByPolicy = new Map<ProviderPolicy, Promise<boolean>>();
327
- const detectNative = (policy: ProviderPolicy) => {
328
- const existing = nativeByPolicy.get(policy);
387
+ const nativeByPolicy = new Map<string, Promise<boolean>>();
388
+ const detectNative = (
389
+ policy: ProviderPolicy,
390
+ env: Readonly<Record<string, string | undefined>>,
391
+ key: string,
392
+ ) => {
393
+ const cacheKey = `${policy.platform}\0${key}`;
394
+ const existing = nativeByPolicy.get(cacheKey);
329
395
  if (existing) return existing;
330
- const detected = nativeIntegrationDetected(policy, context, pathExists, fileContains);
331
- nativeByPolicy.set(policy, detected);
396
+ const detected = nativeIntegrationDetected(
397
+ policy,
398
+ providerContext(home, undefined, env),
399
+ pathExists,
400
+ fileContains,
401
+ );
402
+ nativeByPolicy.set(cacheKey, detected);
332
403
  return detected;
333
404
  };
405
+ const configured = dependencies.providers ?? {};
406
+ const derived = Object.keys(configured).filter(
407
+ (id) => !(AUDITED_PROVIDERS as readonly string[]).includes(id),
408
+ );
334
409
  const providers: ProviderIntegration[] = [];
335
- for (const provider of AUDITED_PROVIDERS) {
336
- const policy = providerPolicy(provider);
410
+ for (const provider of [...AUDITED_PROVIDERS, ...derived]) {
411
+ const { base, policy, env: providerEnv } = resolveProvider(provider, configured);
337
412
  if (!policy) continue;
338
- const storageRoot = contextModeStorageRootFor(provider, { home, env: hostEnvironment });
413
+ const env = { ...hostEnvironment, ...providerEnv };
414
+ const storageRoot = contextModeStorageRootFor(base, { home, env });
339
415
  if (!storageRoot) continue;
340
416
  const reusesExistingStorage = await pathExists(storageRoot);
341
417
  const native =
342
- settings.autoInject && settings.preferNativeIntegrations && (await detectNative(policy));
418
+ settings.autoInject &&
419
+ settings.preferNativeIntegrations &&
420
+ (await detectNative(policy, env, JSON.stringify(providerEnv)));
343
421
  const activation = !settings.autoInject ? "disabled" : native ? "native" : "mcp";
344
422
  providers.push({
345
423
  provider,
@@ -377,34 +455,35 @@ export async function injectContextModeOnCreate<T extends CreateRequest>(
377
455
  ) {
378
456
  return request;
379
457
  }
380
- const policy = providerPolicy(request.config.provider);
458
+ const {
459
+ base,
460
+ policy,
461
+ env: providerEnv,
462
+ } = resolveProvider(request.config.provider, dependencies.providers);
381
463
  if (!policy) return request;
382
464
  const home = dependencies.home ?? homedir();
383
465
  const hostEnvironment = dependencies.env ?? process.env;
384
466
  const pathExists = dependencies.pathExists ?? defaultPathExists;
385
467
  const fileContains = dependencies.fileContains ?? defaultFileContains;
386
- const context = providerContext(home, request.config.cwd, {
387
- ...hostEnvironment,
388
- ...request.env,
389
- });
468
+ const env = { ...hostEnvironment, ...providerEnv, ...request.env };
390
469
  if (
391
470
  settings.preferNativeIntegrations &&
392
- (await nativeIntegrationDetected(policy, context, pathExists, fileContains))
471
+ (await nativeIntegrationDetected(
472
+ policy,
473
+ providerContext(home, request.config.cwd, env),
474
+ pathExists,
475
+ fileContains,
476
+ ))
393
477
  ) {
394
478
  return request;
395
479
  }
396
- const storageRoot = contextModeStorageRootFor(request.config.provider, {
397
- home,
398
- cwd: request.config.cwd,
399
- env: { ...hostEnvironment, ...request.env },
400
- });
480
+ const storageRoot = contextModeStorageRootFor(base, { home, cwd: request.config.cwd, env });
401
481
  if (!storageRoot) return request;
402
- const env = serverEnvironment(policy, storageRoot, request.env);
403
482
  const server: McpServer = {
404
483
  type: "stdio",
405
484
  command: launch.program,
406
485
  args: launch.args,
407
- env,
486
+ env: serverEnvironment(policy, storageRoot, request.env),
408
487
  };
409
488
  return {
410
489
  ...request,
@@ -421,14 +500,18 @@ export async function injectContextModeEnvironment<T extends SessionOpenRequest>
421
500
  dependencies: IntegrationDependencies = {},
422
501
  ): Promise<T> {
423
502
  if (!settings.autoInject) return request;
424
- const policy = providerPolicy(request.provider);
503
+ const {
504
+ base,
505
+ policy,
506
+ env: providerEnv,
507
+ } = resolveProvider(request.provider, dependencies.providers);
425
508
  if (!policy) return request;
426
509
  const home = dependencies.home ?? homedir();
427
510
  const hostEnvironment = dependencies.env ?? process.env;
428
- const storageRoot = contextModeStorageRootFor(request.provider, {
511
+ const storageRoot = contextModeStorageRootFor(base, {
429
512
  home,
430
513
  cwd: request.cwd,
431
- env: { ...hostEnvironment, ...request.env },
514
+ env: { ...hostEnvironment, ...providerEnv, ...request.env },
432
515
  });
433
516
  if (!storageRoot) return request;
434
517
  return {