@omercnet/paseo-context-mode 3.1.3 → 3.1.4-next.434.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.
@@ -1,29 +1,26 @@
1
- import { readFile, stat } from "node:fs/promises";
1
+ import { createReadStream } from "node:fs";
2
+ import { stat } from "node:fs/promises";
2
3
  import { homedir } from "node:os";
3
- import { isAbsolute, join, resolve } from "node:path";
4
- import type { ContextModeSettings, IntegrationAudit, ProviderIntegration } from "../shared";
4
+ import { join } from "node:path";
5
+ import type { IntegrationAudit, ProviderIntegration } from "../shared/contracts";
6
+ import {
7
+ CONTEXT_MODE_PROVIDERS,
8
+ type ContextModePlatform,
9
+ ContextModeProviderSchema,
10
+ contextModePlatformFor,
11
+ } from "../shared/providers";
12
+ import type { ContextModeSettings } from "../shared/settings";
5
13
  import type { LaunchDescriptor } from "./binary";
14
+ import { serverArgs } from "./mcp-process";
15
+ import { resolveContextModeDataDir } from "./storage-discovery";
6
16
 
7
17
  const AUDITED_PROVIDERS = [
8
- "claude",
9
- "codex",
10
- "copilot",
18
+ ...CONTEXT_MODE_PROVIDERS.map(({ provider }) => provider),
11
19
  "cursor",
12
- "opencode",
13
20
  "pi",
14
21
  "omp",
15
22
  "omp-plugin",
16
- ] as const;
17
-
18
- type AuditedProvider = (typeof AUDITED_PROVIDERS)[number];
19
- type ContextModePlatform =
20
- | "claude-code"
21
- | "codex"
22
- | "copilot-cli"
23
- | "cursor"
24
- | "opencode"
25
- | "pi"
26
- | "omp";
23
+ ];
27
24
 
28
25
  type McpServer = {
29
26
  type: "stdio";
@@ -42,17 +39,10 @@ type CreateRequest = {
42
39
  env?: Readonly<Record<string, string>>;
43
40
  };
44
41
 
45
- type SessionOpenRequest = {
46
- provider: string;
47
- cwd?: string;
48
- env: Readonly<Record<string, string>>;
49
- };
50
-
51
42
  export interface IntegrationDependencies {
52
43
  home?: string;
53
44
  pathExists?: (path: string) => Promise<boolean>;
54
45
  fileContains?: (path: string, text: string) => Promise<boolean>;
55
- now?: () => Date;
56
46
  env?: NodeJS.ProcessEnv;
57
47
  providers?: ProviderOverrides;
58
48
  }
@@ -67,9 +57,9 @@ export interface ProviderConfigSource {
67
57
  }
68
58
 
69
59
  /**
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).
60
+ * Reads `agents.providers` from the daemon so a derived provider (`extends`) maps like its parent
61
+ * and its own `env` (for example `CODEX_HOME`) locates its native config. Failures degrade to
62
+ * "no overrides" (canonical provider behavior).
73
63
  */
74
64
  export async function readProviderOverrides(
75
65
  paseo: ProviderConfigSource | undefined,
@@ -102,13 +92,26 @@ export async function readProviderOverrides(
102
92
  }
103
93
  }
104
94
 
105
- export interface ContextModeStorageOptions {
106
- home?: string;
107
- cwd?: string;
108
- env?: Readonly<Record<string, string | undefined>>;
95
+ /**
96
+ * Mirrors Paseo's single-level `extends`: a derived provider maps like its parent, and its
97
+ * effective env is the parent's override env overlaid with its own. `platform` is null when
98
+ * Context Mode 2 cannot serve the provider over MCP.
99
+ */
100
+ export function resolveProvider(
101
+ id: string,
102
+ providers: ProviderOverrides,
103
+ ): { base: string; platform: ContextModePlatform | null; env: Record<string, string> } {
104
+ const own = Object.hasOwn(providers, id) ? providers[id] : undefined;
105
+ const parent = own?.extends ?? id;
106
+ const inherited =
107
+ parent !== id && Object.hasOwn(providers, parent) ? providers[parent] : undefined;
108
+ const base = parent.startsWith("omp-plugin-") ? "omp-plugin" : parent;
109
+ const supported = ContextModeProviderSchema.safeParse(base);
110
+ const platform = supported.success ? contextModePlatformFor(supported.data) : null;
111
+ return { base, platform, env: { ...inherited?.env, ...own?.env } };
109
112
  }
110
113
 
111
- type ProviderContext = {
114
+ type ProbeContext = {
112
115
  home: string;
113
116
  cwd?: string;
114
117
  env: Readonly<Record<string, string | undefined>>;
@@ -116,194 +119,49 @@ type ProviderContext = {
116
119
 
117
120
  type NativeProbe = { kind: "path"; path: string } | { kind: "file"; path: string; marker: string };
118
121
 
119
- type ProviderPolicy = {
120
- platform: ContextModePlatform;
121
- storageRoot: (context: ProviderContext) => string;
122
- nativeProbes: (context: ProviderContext) => readonly NativeProbe[];
123
- };
124
-
125
- function configuredRoot(
126
- value: string | undefined,
127
- fallback: string,
128
- { home, cwd }: Pick<ProviderContext, "home" | "cwd">,
129
- ): string {
130
- const candidate = value?.trim();
131
- if (!candidate) return fallback;
132
- if (candidate === "~") return home;
133
- if (candidate.startsWith("~/") || candidate.startsWith("~\\")) {
134
- return join(home, candidate.slice(2));
135
- }
136
- return isAbsolute(candidate) ? candidate : resolve(cwd ?? home, candidate);
137
- }
138
-
139
- function claudeRoot(context: ProviderContext): string {
140
- return configuredRoot(context.env.CLAUDE_CONFIG_DIR, join(context.home, ".claude"), context);
141
- }
142
-
143
- function codexRoot(context: ProviderContext): string {
144
- return configuredRoot(context.env.CODEX_HOME, join(context.home, ".codex"), context);
145
- }
146
-
147
- function copilotRoot(context: ProviderContext): string {
148
- return configuredRoot(context.env.COPILOT_HOME, join(context.home, ".copilot"), context);
149
- }
150
-
151
- function opencodeRoot(context: ProviderContext): string {
152
- const configHome = configuredRoot(
153
- context.env.XDG_CONFIG_HOME,
154
- join(context.home, ".config"),
155
- context,
156
- );
157
- return join(configHome, "opencode");
158
- }
159
-
160
- function ompAgentRoot(context: ProviderContext): string {
161
- return configuredRoot(
162
- context.env.PI_CODING_AGENT_DIR,
163
- join(context.home, ".omp", "agent"),
164
- context,
165
- );
166
- }
167
-
168
- function projectProbe(context: ProviderContext, ...segments: string[]): NativeProbe[] {
169
- return context.cwd
170
- ? [{ kind: "file", path: join(context.cwd, ...segments), marker: "context-mode" }]
171
- : [];
172
- }
173
-
174
- const OMP_POLICY: ProviderPolicy = {
175
- platform: "omp",
176
- storageRoot: ({ home }) => join(home, ".omp", "context-mode"),
177
- nativeProbes: (context) => [
178
- ...projectProbe(context, ".omp", "mcp.json"),
179
- { kind: "file", path: join(ompAgentRoot(context), "mcp.json"), marker: "context-mode" },
180
- {
181
- kind: "file",
182
- path: join(context.home, ".omp", "plugins", "package.json"),
183
- marker: '"context-mode"',
184
- },
185
- { kind: "path", path: join(context.home, ".omp", "plugins", "node_modules", "context-mode") },
186
- ],
187
- };
188
-
189
- const PROVIDER_POLICIES: Readonly<Record<Exclude<AuditedProvider, "omp-plugin">, ProviderPolicy>> =
190
- {
191
- claude: {
192
- platform: "claude-code",
193
- storageRoot: (context) => join(claudeRoot(context), "context-mode"),
194
- nativeProbes: (context) => [
195
- {
196
- kind: "file",
197
- path: join(claudeRoot(context), "settings.json"),
198
- marker: "context-mode",
199
- },
200
- { kind: "file", path: join(context.home, ".claude.json"), marker: "context-mode" },
201
- {
202
- kind: "file",
203
- path: join(claudeRoot(context), "plugins", "installed_plugins.json"),
204
- marker: "context-mode",
205
- },
206
- { kind: "path", path: join(claudeRoot(context), "plugins", "cache", "context-mode") },
207
- ],
208
- },
209
- codex: {
210
- platform: "codex",
211
- storageRoot: (context) => join(codexRoot(context), "context-mode"),
212
- nativeProbes: (context) => {
213
- const configPath = join(codexRoot(context), "config.toml");
214
- return [
215
- { kind: "file", path: configPath, marker: '[plugins."context-mode@context-mode"]' },
216
- { kind: "file", path: configPath, marker: "[mcp_servers.context-mode]" },
217
- ];
218
- },
219
- },
220
- copilot: {
221
- platform: "copilot-cli",
222
- storageRoot: (context) => join(copilotRoot(context), "context-mode"),
223
- nativeProbes: (context) => [
224
- {
225
- kind: "file",
226
- path: join(copilotRoot(context), "mcp-config.json"),
227
- marker: "context-mode",
228
- },
229
- ],
230
- },
231
- cursor: {
232
- platform: "cursor",
233
- storageRoot: ({ home }) => join(home, ".cursor", "context-mode"),
234
- nativeProbes: (context) => [
235
- ...projectProbe(context, ".cursor", "mcp.json"),
236
- {
237
- kind: "file",
238
- path: join(context.home, ".cursor", "mcp.json"),
239
- marker: "context-mode",
240
- },
241
- {
242
- kind: "path",
243
- path: join(context.home, ".cursor", "plugins", "local", "context-mode"),
244
- },
245
- ],
246
- },
247
- opencode: {
248
- platform: "opencode",
249
- storageRoot: (context) => join(opencodeRoot(context), "context-mode"),
250
- nativeProbes: (context) => [
251
- ...(context.cwd
252
- ? [
253
- "opencode.jsonc",
254
- "opencode.json",
255
- join(".opencode", "opencode.jsonc"),
256
- join(".opencode", "opencode.json"),
257
- ].map(
258
- (path): NativeProbe => ({
259
- kind: "file",
260
- path: join(context.cwd as string, path),
261
- marker: "context-mode",
262
- }),
263
- )
264
- : []),
265
- {
266
- kind: "file",
267
- path: join(opencodeRoot(context), "opencode.jsonc"),
268
- marker: "context-mode",
269
- },
270
- {
271
- kind: "file",
272
- path: join(opencodeRoot(context), "opencode.json"),
273
- marker: "context-mode",
274
- },
275
- ],
276
- },
277
- pi: {
278
- platform: "pi",
279
- storageRoot: ({ home }) => join(home, ".pi", "context-mode"),
280
- nativeProbes: ({ home }) => [
281
- { kind: "file", path: join(home, ".pi", "agent", "settings.json"), marker: "context-mode" },
282
- { kind: "path", path: join(home, ".pi", "extensions", "context-mode", "package.json") },
283
- ],
284
- },
285
- omp: OMP_POLICY,
286
- };
287
-
288
- function providerPolicy(provider: string): ProviderPolicy | null {
289
- if (provider === "omp-plugin" || provider.startsWith("omp-plugin-")) return OMP_POLICY;
290
- return Object.hasOwn(PROVIDER_POLICIES, provider)
291
- ? PROVIDER_POLICIES[provider as keyof typeof PROVIDER_POLICIES]
292
- : null;
293
- }
294
-
295
122
  /**
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.
123
+ * Where `context-mode setup` (or the agent's own plugin install) registers Context Mode, with
124
+ * config homes resolved as setup resolves them (an empty variable means unset).
298
125
  */
299
- export 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 } };
126
+ function nativeProbes(platform: ContextModePlatform, { home, cwd, env }: ProbeContext) {
127
+ const probes: NativeProbe[] = [];
128
+ const file = (path: string, marker = "context-mode") =>
129
+ probes.push({ kind: "file", path, marker });
130
+ switch (platform) {
131
+ case "claude-code": {
132
+ const claude = env.CLAUDE_CONFIG_DIR || join(home, ".claude");
133
+ file(join(claude, "plugins", "installed_plugins.json"), '"context-mode@context-mode"');
134
+ file(join(claude, "settings.json"), '"context-mode@context-mode"');
135
+ file(join(home, ".claude.json"), '"context-mode":');
136
+ break;
137
+ }
138
+ case "codex": {
139
+ const config = join(env.CODEX_HOME || join(home, ".codex"), "config.toml");
140
+ file(config, "[mcp_servers.context-mode]");
141
+ file(config, '[plugins."context-mode@context-mode"]');
142
+ break;
143
+ }
144
+ case "copilot-cli": {
145
+ const copilot = env.COPILOT_HOME || join(home, ".copilot");
146
+ file(join(copilot, "mcp-config.json"), '"context-mode"');
147
+ probes.push({ kind: "path", path: join(copilot, "hooks", "context-mode.json") });
148
+ break;
149
+ }
150
+ case "opencode": {
151
+ const opencode =
152
+ env.OPENCODE_CONFIG_DIR || join(env.XDG_CONFIG_HOME || join(home, ".config"), "opencode");
153
+ const names = ["opencode.jsonc", "opencode.json"];
154
+ for (const name of names) file(join(opencode, name));
155
+ if (cwd) {
156
+ for (const name of names) {
157
+ file(join(cwd, name));
158
+ file(join(cwd, ".opencode", name));
159
+ }
160
+ }
161
+ break;
162
+ }
163
+ }
164
+ return probes;
307
165
  }
308
166
 
309
167
  async function defaultPathExists(path: string): Promise<boolean> {
@@ -315,131 +173,172 @@ async function defaultPathExists(path: string): Promise<boolean> {
315
173
  }
316
174
  }
317
175
 
176
+ /** Scan results per (path, marker); audit polls and agent creation repeat the same probes. */
177
+ const fileScans = new Map<string, { mtimeMs: number; size: number; found: boolean }>();
178
+
179
+ /** Streams the file, so a large `~/.claude.json` is still searched without loading it whole. */
318
180
  async function defaultFileContains(path: string, text: string): Promise<boolean> {
319
181
  try {
320
182
  const metadata = await stat(path);
321
- if (!metadata.isFile() || metadata.size > 1024 * 1024) return false;
322
- return (await readFile(path, "utf8")).includes(text);
183
+ if (!metadata.isFile()) return false;
184
+ const { mtimeMs, size } = metadata;
185
+ const key = `${path}\0${text}`;
186
+ const cached = fileScans.get(key);
187
+ if (cached?.mtimeMs === mtimeMs && cached.size === size) return cached.found;
188
+ let found = false;
189
+ let tail = "";
190
+ for await (const chunk of createReadStream(path, { encoding: "utf8" })) {
191
+ const window = tail + chunk;
192
+ if (window.includes(text)) {
193
+ found = true;
194
+ break;
195
+ }
196
+ tail = window.slice(window.length - text.length + 1);
197
+ }
198
+ fileScans.set(key, { mtimeMs, size, found });
199
+ return found;
323
200
  } catch {
324
201
  return false;
325
202
  }
326
203
  }
327
204
 
328
- async function nativeIntegrationDetected(
329
- policy: ProviderPolicy,
330
- context: ProviderContext,
331
- pathExists: (path: string) => Promise<boolean>,
332
- fileContains: (path: string, text: string) => Promise<boolean>,
205
+ async function anyProbeMatches(
206
+ probes: readonly NativeProbe[],
207
+ dependencies: IntegrationDependencies,
333
208
  ): Promise<boolean> {
334
- for (const probe of policy.nativeProbes(context)) {
335
- if (probe.kind === "path") {
336
- if (await pathExists(probe.path)) return true;
337
- } else if (await fileContains(probe.path, probe.marker)) {
209
+ const pathExists = dependencies.pathExists ?? defaultPathExists;
210
+ const fileContains = dependencies.fileContains ?? defaultFileContains;
211
+ for (const probe of probes) {
212
+ if (
213
+ probe.kind === "path"
214
+ ? await pathExists(probe.path)
215
+ : await fileContains(probe.path, probe.marker)
216
+ ) {
338
217
  return true;
339
218
  }
340
219
  }
341
220
  return false;
342
221
  }
343
222
 
344
- function serverEnvironment(
345
- policy: ProviderPolicy,
346
- storageRoot: string,
347
- explicitEnvironment: Readonly<Record<string, string>> = {},
348
- ): Record<string, string> {
349
- return {
350
- CONTEXT_MODE_PLATFORM: explicitEnvironment.CONTEXT_MODE_PLATFORM ?? policy.platform,
351
- CONTEXT_MODE_DIR: explicitEnvironment.CONTEXT_MODE_DIR ?? storageRoot,
352
- };
223
+ function nativeIntegrationDetected(
224
+ platform: ContextModePlatform,
225
+ context: ProbeContext,
226
+ dependencies: IntegrationDependencies,
227
+ ): Promise<boolean> {
228
+ return anyProbeMatches(nativeProbes(platform, context), dependencies);
229
+ }
230
+
231
+ /** Providers Paseo cannot inject: their own Context Mode install is reported, nothing more. */
232
+ interface HostOnlyPolicy {
233
+ probes(context: ProbeContext): NativeProbe[];
234
+ installed: string;
235
+ missing: string;
353
236
  }
354
237
 
355
- function providerContext(
356
- home: string,
357
- cwd: string | undefined,
358
- env: Readonly<Record<string, string | undefined>> | undefined,
359
- ): ProviderContext {
360
- return { home, cwd, env: env ?? {} };
238
+ function piProbes({ home, cwd, env }: ProbeContext): NativeProbe[] {
239
+ // `pi install npm:context-mode` lists the package in Pi's settings, user or project level.
240
+ const agentDir = env.PI_CODING_AGENT_DIR || join(home, ".pi", "agent");
241
+ const probes: NativeProbe[] = [
242
+ { kind: "file", path: join(agentDir, "settings.json"), marker: "context-mode" },
243
+ ];
244
+ if (cwd)
245
+ probes.push({ kind: "file", path: join(cwd, ".pi", "settings.json"), marker: "context-mode" });
246
+ return probes;
361
247
  }
362
248
 
363
- export function contextModeStorageRootFor(
364
- provider: string,
365
- options: ContextModeStorageOptions = {},
366
- ): string | null {
367
- const policy = providerPolicy(provider);
368
- if (!policy) return null;
369
- const home = options.home ?? homedir();
370
- const context = providerContext(home, options.cwd, options.env);
371
- const explicitRoot = options.env?.CONTEXT_MODE_DIR?.trim();
372
- if (explicitRoot && isAbsolute(explicitRoot)) return resolve(explicitRoot);
373
- const dataRoot = options.env?.CONTEXT_MODE_DATA_DIR?.trim();
374
- if (dataRoot && isAbsolute(dataRoot)) return join(resolve(dataRoot), "context-mode");
375
- return policy.storageRoot(context);
249
+ function ompProbes({ home, cwd, env }: ProbeContext): NativeProbe[] {
250
+ const agentDir = env.PI_CODING_AGENT_DIR || join(home, ".omp", "agent");
251
+ const probes: NativeProbe[] = [
252
+ { kind: "file", path: join(agentDir, "mcp.json"), marker: '"context-mode"' },
253
+ ];
254
+ if (cwd)
255
+ probes.push({ kind: "file", path: join(cwd, ".omp", "mcp.json"), marker: '"context-mode"' });
256
+ return probes;
257
+ }
258
+
259
+ const OMP_POLICY: HostOnlyPolicy = {
260
+ probes: ompProbes,
261
+ installed:
262
+ "OMP loads its own Context Mode server from its MCP config, so Paseo injects nothing. Context Mode 2 has no OMP integration (upstream keeps OMP on 1.0.169), so OMP's sessions are not shown here.",
263
+ missing:
264
+ "Context Mode 2 has no OMP integration, so Paseo injects nothing. Upstream keeps OMP on Context Mode 1.0.169, installed through OMP's own plugins.",
265
+ };
266
+
267
+ const HOST_ONLY: Readonly<Record<string, HostOnlyPolicy>> = {
268
+ pi: {
269
+ probes: piProbes,
270
+ installed:
271
+ "Context Mode runs inside Pi as its own extension, so Paseo injects nothing. Pi records its sessions in the shared store, so they appear in Savings.",
272
+ missing:
273
+ "Context Mode 2 runs inside Pi only as an extension, so Paseo cannot inject it. Install it in Pi with `pi install npm:context-mode`.",
274
+ },
275
+ omp: OMP_POLICY,
276
+ "omp-plugin": OMP_POLICY,
277
+ };
278
+
279
+ const UNSUPPORTED_DETAIL: Readonly<Record<string, string>> = {
280
+ cursor: "Context Mode 2 dropped Cursor, so nothing is injected.",
281
+ };
282
+
283
+ async function hostOnlyIntegration(
284
+ base: string,
285
+ context: ProbeContext,
286
+ dependencies: IntegrationDependencies,
287
+ ): Promise<Pick<ProviderIntegration, "activation" | "detail"> | null> {
288
+ if (Object.hasOwn(HOST_ONLY, base)) {
289
+ const policy = HOST_ONLY[base];
290
+ const installed = await anyProbeMatches(policy.probes(context), dependencies);
291
+ return installed
292
+ ? { activation: "native", detail: policy.installed }
293
+ : { activation: "disabled", detail: policy.missing };
294
+ }
295
+ if (Object.hasOwn(UNSUPPORTED_DETAIL, base)) {
296
+ return { activation: "disabled", detail: UNSUPPORTED_DETAIL[base] };
297
+ }
298
+ return null;
376
299
  }
377
300
 
301
+ const ACTIVATION_DETAIL: Readonly<Record<ProviderIntegration["activation"], string>> = {
302
+ native:
303
+ "Context Mode is already registered natively for this provider, so duplicate MCP injection is disabled. Already-running agents may need a provider refresh or restart to load it.",
304
+ mcp: "The Context Mode MCP server is injected only when a new Paseo agent is created. Existing agents keep their saved MCP configuration and must be recreated or configured manually to gain its tools.",
305
+ disabled:
306
+ "Automatic activation is disabled. Existing and new agents keep their current MCP configuration.",
307
+ };
308
+
378
309
  export async function createIntegrationAudit(
379
310
  settings: ContextModeSettings,
380
- runtime: { path: string; version: string | null },
381
311
  dependencies: IntegrationDependencies = {},
382
312
  ): Promise<IntegrationAudit> {
383
313
  const home = dependencies.home ?? homedir();
384
- const pathExists = dependencies.pathExists ?? defaultPathExists;
385
- const fileContains = dependencies.fileContains ?? defaultFileContains;
386
314
  const hostEnvironment = dependencies.env ?? process.env;
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);
395
- if (existing) return existing;
396
- const detected = nativeIntegrationDetected(
397
- policy,
398
- providerContext(home, undefined, env),
399
- pathExists,
400
- fileContains,
401
- );
402
- nativeByPolicy.set(cacheKey, detected);
403
- return detected;
404
- };
405
315
  const configured = dependencies.providers ?? {};
406
- const derived = Object.keys(configured).filter(
407
- (id) => !(AUDITED_PROVIDERS as readonly string[]).includes(id),
408
- );
316
+ const derived = Object.keys(configured).filter((id) => !AUDITED_PROVIDERS.includes(id));
409
317
  const providers: ProviderIntegration[] = [];
410
318
  for (const provider of [...AUDITED_PROVIDERS, ...derived]) {
411
- const { base, policy, env: providerEnv } = resolveProvider(provider, configured);
412
- if (!policy) continue;
413
- const env = { ...hostEnvironment, ...providerEnv };
414
- const storageRoot = contextModeStorageRootFor(base, { home, env });
415
- if (!storageRoot) continue;
416
- const reusesExistingStorage = await pathExists(storageRoot);
319
+ const { base, platform, env } = resolveProvider(provider, configured);
320
+ if (platform === null) {
321
+ const row = await hostOnlyIntegration(
322
+ base,
323
+ { home, env: { ...hostEnvironment, ...env } },
324
+ dependencies,
325
+ );
326
+ if (row) providers.push({ provider, platform, ...row });
327
+ continue;
328
+ }
329
+ const nativeChecked = settings.autoInject && settings.preferNativeIntegrations;
417
330
  const native =
418
- settings.autoInject &&
419
- settings.preferNativeIntegrations &&
420
- (await detectNative(policy, env, JSON.stringify(providerEnv)));
421
- const activation = !settings.autoInject ? "disabled" : native ? "native" : "mcp";
422
- providers.push({
423
- provider,
424
- platform: policy.platform,
425
- activation,
426
- storageRoot,
427
- reusesExistingStorage,
428
- detail:
429
- activation === "native"
430
- ? "An existing native Context Mode registration was found in an authoritative provider config location, so duplicate MCP injection is disabled. Already-running agents may need a provider refresh or restart to load it."
431
- : activation === "mcp"
432
- ? "The bundled Context Mode MCP server is injected only when a new Paseo agent is created. Existing agents keep their saved MCP configuration and must be recreated or configured manually to gain its tools."
433
- : "Automatic activation is disabled. Existing and new agents keep their current MCP configuration.",
434
- });
331
+ nativeChecked &&
332
+ (await nativeIntegrationDetected(
333
+ platform,
334
+ { home, env: { ...hostEnvironment, ...env } },
335
+ dependencies,
336
+ ));
337
+ let activation: ProviderIntegration["activation"] = "disabled";
338
+ if (settings.autoInject) activation = native ? "native" : "mcp";
339
+ providers.push({ provider, platform, activation, detail: ACTIVATION_DETAIL[activation] });
435
340
  }
436
- return {
437
- runtimePath: runtime.path,
438
- runtimeVersion: runtime.version,
439
- injectionEnabled: settings.autoInject,
440
- providers,
441
- checkedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
442
- };
341
+ return { providers };
443
342
  }
444
343
 
445
344
  export async function injectContextModeOnCreate<T extends CreateRequest>(
@@ -455,35 +354,34 @@ export async function injectContextModeOnCreate<T extends CreateRequest>(
455
354
  ) {
456
355
  return request;
457
356
  }
458
- const {
459
- base,
460
- policy,
461
- env: providerEnv,
462
- } = resolveProvider(request.config.provider, dependencies.providers);
463
- if (!policy) return request;
464
- const home = dependencies.home ?? homedir();
465
- const hostEnvironment = dependencies.env ?? process.env;
466
- const pathExists = dependencies.pathExists ?? defaultPathExists;
467
- const fileContains = dependencies.fileContains ?? defaultFileContains;
468
- const env = { ...hostEnvironment, ...providerEnv, ...request.env };
357
+ const { platform, env } = resolveProvider(request.config.provider, dependencies.providers ?? {});
358
+ if (platform === null) return request;
469
359
  if (
470
360
  settings.preferNativeIntegrations &&
471
361
  (await nativeIntegrationDetected(
472
- policy,
473
- providerContext(home, request.config.cwd, env),
474
- pathExists,
475
- fileContains,
362
+ platform,
363
+ {
364
+ home: dependencies.home ?? homedir(),
365
+ cwd: request.config.cwd,
366
+ env: { ...(dependencies.env ?? process.env), ...env, ...request.env },
367
+ },
368
+ dependencies,
476
369
  ))
477
370
  ) {
478
371
  return request;
479
372
  }
480
- const storageRoot = contextModeStorageRootFor(base, { home, cwd: request.config.cwd, env });
481
- if (!storageRoot) return request;
482
373
  const server: McpServer = {
483
374
  type: "stdio",
484
- command: launch.program,
485
- args: launch.args,
486
- env: { ...serverEnvironment(policy, storageRoot, request.env), ...launch.env },
375
+ ...serverArgs(launch, platform),
376
+ // Agents may start with a different environment than this daemon. Pin the host or provider
377
+ // store, never a per-request one, so the dashboard, Resume and Forget reach what agents write.
378
+ env: {
379
+ ...launch.env,
380
+ CONTEXT_MODE_DATA_DIR: resolveContextModeDataDir(
381
+ { ...(dependencies.env ?? process.env), ...env },
382
+ dependencies.home ?? homedir(),
383
+ ),
384
+ },
487
385
  };
488
386
  return {
489
387
  ...request,
@@ -493,32 +391,3 @@ export async function injectContextModeOnCreate<T extends CreateRequest>(
493
391
  },
494
392
  };
495
393
  }
496
-
497
- export async function injectContextModeEnvironment<T extends SessionOpenRequest>(
498
- request: T,
499
- settings: ContextModeSettings,
500
- dependencies: IntegrationDependencies = {},
501
- ): Promise<T> {
502
- if (!settings.autoInject) return request;
503
- const {
504
- base,
505
- policy,
506
- env: providerEnv,
507
- } = resolveProvider(request.provider, dependencies.providers);
508
- if (!policy) return request;
509
- const home = dependencies.home ?? homedir();
510
- const hostEnvironment = dependencies.env ?? process.env;
511
- const storageRoot = contextModeStorageRootFor(base, {
512
- home,
513
- cwd: request.cwd,
514
- env: { ...hostEnvironment, ...providerEnv, ...request.env },
515
- });
516
- if (!storageRoot) return request;
517
- return {
518
- ...request,
519
- env: {
520
- ...serverEnvironment(policy, storageRoot),
521
- ...request.env,
522
- },
523
- };
524
- }