@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.
- package/OVERVIEW.md +3 -3
- package/README.md +13 -15
- package/client/analytics-dashboard.tsx +745 -613
- package/client/controls.tsx +295 -0
- package/client/diagnostics-section.tsx +178 -0
- package/client/{context-mode-footer.tsx → footer.tsx} +22 -20
- package/client/knowledge-section.tsx +725 -697
- package/client/{context-mode-pill.tsx → pill.tsx} +5 -5
- package/client/scoped-knowledge-section.tsx +14 -20
- package/client/screen-scope.ts +12 -6
- package/client/settings-screen.tsx +94 -97
- package/client/surface.tsx +377 -0
- package/index.client.tsx +3 -3
- package/index.server.ts +18 -27
- package/npm-shrinkwrap.json +8 -1747
- package/package.json +7 -7
- package/server/actions.ts +91 -102
- package/server/analytics.ts +257 -308
- package/server/binary.ts +119 -94
- package/server/database.ts +463 -0
- package/server/handlers.ts +40 -95
- package/server/integration.ts +230 -361
- package/server/knowledge.ts +65 -137
- package/server/mcp-process.ts +135 -86
- package/server/storage-discovery.ts +46 -199
- package/shared/actions.ts +2 -27
- package/shared/analytics.ts +60 -33
- package/shared/contracts.ts +16 -47
- package/shared/knowledge.ts +62 -65
- package/shared/providers.ts +62 -0
- package/shared/settings.ts +33 -18
- package/client/actions-section.tsx +0 -621
- package/client/context-mode-surface.tsx +0 -444
- package/server/context-mode-db.ts +0 -481
- package/shared/index.ts +0 -5
package/server/integration.ts
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
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
|
-
|
|
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
|
-
]
|
|
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
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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
|
-
*
|
|
297
|
-
*
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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()
|
|
322
|
-
|
|
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
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
):
|
|
349
|
-
return
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
):
|
|
360
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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,
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
}
|