@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/binary.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { constants } from "node:fs";
|
|
2
3
|
import { access, readFile, realpath, stat } from "node:fs/promises";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
5
|
+
import { dirname, join, win32 } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import type { ContextModeSettings } from "../shared/settings";
|
|
6
8
|
import type { ProviderConfigSource } from "./integration";
|
|
7
9
|
|
|
8
10
|
export interface LaunchDescriptor {
|
|
@@ -15,18 +17,18 @@ export type BinaryResolution =
|
|
|
15
17
|
| {
|
|
16
18
|
state: "found";
|
|
17
19
|
path: string;
|
|
18
|
-
source: "settings" | "
|
|
20
|
+
source: "settings" | "bundled";
|
|
19
21
|
launch: LaunchDescriptor;
|
|
20
22
|
}
|
|
21
23
|
| { state: "missing"; code: "not-installed" | "unsupported"; message: string };
|
|
22
24
|
|
|
23
25
|
export interface BinaryResolutionDependencies {
|
|
24
|
-
env?: NodeJS.ProcessEnv;
|
|
25
26
|
platform?: NodeJS.Platform;
|
|
26
27
|
nodePath?: string;
|
|
27
28
|
electron?: boolean;
|
|
28
29
|
canExecute?: (path: string) => Promise<boolean>;
|
|
29
30
|
isFile?: (path: string) => Promise<boolean>;
|
|
31
|
+
readVersion?: (launch: LaunchDescriptor) => Promise<string | null>;
|
|
30
32
|
paseo?: ProviderConfigSource;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -44,7 +46,9 @@ async function findBundledCli(paseo: ProviderConfigSource | undefined): Promise<
|
|
|
44
46
|
try {
|
|
45
47
|
const manifest = JSON.parse(await readFile(join(directory, "paseo-plugin.json"), "utf8"));
|
|
46
48
|
if (manifest?.id !== "context-mode") continue;
|
|
47
|
-
|
|
49
|
+
// The package exports only its OpenCode plugin (`<root>/opencode/plugin.js`); the CLI sits at the root.
|
|
50
|
+
const main = createRequire(join(directory, "package.json")).resolve("context-mode");
|
|
51
|
+
return join(dirname(dirname(main)), "cli.mjs");
|
|
48
52
|
} catch {
|
|
49
53
|
// Not this plugin, or its install has no bundled runtime.
|
|
50
54
|
}
|
|
@@ -70,21 +74,6 @@ async function defaultCanExecute(path: string, platform: NodeJS.Platform): Promi
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
function pathCandidates(
|
|
74
|
-
name: string,
|
|
75
|
-
directory: string,
|
|
76
|
-
platform: NodeJS.Platform,
|
|
77
|
-
env: NodeJS.ProcessEnv,
|
|
78
|
-
) {
|
|
79
|
-
const pathApi = platform === "win32" ? win32 : posix;
|
|
80
|
-
if (platform !== "win32") return [pathApi.join(directory, name)];
|
|
81
|
-
const extensions = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
82
|
-
return [
|
|
83
|
-
pathApi.join(directory, name),
|
|
84
|
-
...extensions.map((extension) => pathApi.join(directory, `${name}${extension}`)),
|
|
85
|
-
];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
77
|
async function windowsLaunchDescriptor(
|
|
89
78
|
candidate: string,
|
|
90
79
|
nodePath: string,
|
|
@@ -100,10 +89,8 @@ async function windowsLaunchDescriptor(
|
|
|
100
89
|
|
|
101
90
|
const directory = win32.dirname(candidate);
|
|
102
91
|
const moduleCandidates = [
|
|
103
|
-
win32.join(directory, "node_modules", "context-mode", "cli.
|
|
104
|
-
win32.join(directory, "
|
|
105
|
-
win32.join(directory, "..", "context-mode", "cli.bundle.mjs"),
|
|
106
|
-
win32.join(directory, "..", "context-mode", "build", "cli.js"),
|
|
92
|
+
win32.join(directory, "node_modules", "context-mode", "cli.mjs"),
|
|
93
|
+
win32.join(directory, "..", "context-mode", "cli.mjs"),
|
|
107
94
|
];
|
|
108
95
|
for (const modulePath of moduleCandidates) {
|
|
109
96
|
if (await isFile(modulePath)) return { program: nodePath, args: [modulePath] };
|
|
@@ -111,93 +98,131 @@ async function windowsLaunchDescriptor(
|
|
|
111
98
|
return null;
|
|
112
99
|
}
|
|
113
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The user's own binary when one is configured, else the runtime bundled with the plugin. A
|
|
103
|
+
* configured binary that cannot run is reported, never silently replaced by the bundled one.
|
|
104
|
+
*/
|
|
114
105
|
export async function resolveContextModeBinary(
|
|
115
106
|
settings: ContextModeSettings,
|
|
116
107
|
dependencies: BinaryResolutionDependencies = {},
|
|
117
108
|
): Promise<BinaryResolution> {
|
|
118
|
-
const env = dependencies.env ?? process.env;
|
|
119
109
|
const platform = dependencies.platform ?? process.platform;
|
|
120
|
-
const pathApi = platform === "win32" ? win32 : posix;
|
|
121
110
|
const nodePath = dependencies.nodePath ?? process.execPath;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
source: "settings" | "path",
|
|
131
|
-
): Promise<Extract<BinaryResolution, { state: "found" }> | null> {
|
|
132
|
-
if (!(await canExecute(candidate))) return null;
|
|
133
|
-
const canonicalPath = await realpath(candidate).catch(() => candidate);
|
|
134
|
-
if (platform !== "win32") {
|
|
135
|
-
return {
|
|
136
|
-
state: "found",
|
|
137
|
-
path: canonicalPath,
|
|
138
|
-
source,
|
|
139
|
-
launch: { program: canonicalPath, args: [] },
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
const launch = await windowsLaunchDescriptor(canonicalPath, nodePath, isFile);
|
|
143
|
-
if (!launch) {
|
|
144
|
-
unsupportedShim = canonicalPath;
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
return { state: "found", path: canonicalPath, source, launch };
|
|
111
|
+
if (settings.binaryPath !== null) {
|
|
112
|
+
return resolveConfiguredBinary(settings.binaryPath, {
|
|
113
|
+
platform,
|
|
114
|
+
nodePath,
|
|
115
|
+
canExecute: dependencies.canExecute ?? ((path: string) => defaultCanExecute(path, platform)),
|
|
116
|
+
isFile: dependencies.isFile ?? defaultIsFile,
|
|
117
|
+
readVersion: dependencies.readVersion ?? readContextModeVersion,
|
|
118
|
+
});
|
|
148
119
|
}
|
|
149
120
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
const configured = await resolveCandidate(configuredPath, "settings");
|
|
159
|
-
if (configured) return configured;
|
|
121
|
+
const bundledCli = await findBundledCli(dependencies.paseo);
|
|
122
|
+
if (!bundledCli) {
|
|
123
|
+
return {
|
|
124
|
+
state: "missing",
|
|
125
|
+
code: "not-installed",
|
|
126
|
+
message:
|
|
127
|
+
"The bundled Context Mode runtime is missing from this plugin install. Reinstall the plugin or configure your own binary.",
|
|
128
|
+
};
|
|
160
129
|
}
|
|
130
|
+
const canonicalPath = await realpath(bundledCli).catch(() => bundledCli);
|
|
131
|
+
// Under the desktop app `nodePath` is Electron. Paseo strips this variable from agent
|
|
132
|
+
// environments, so an injected MCP server needs it to start as Node.
|
|
133
|
+
const electron = dependencies.electron ?? process.versions.electron !== undefined;
|
|
134
|
+
return {
|
|
135
|
+
state: "found",
|
|
136
|
+
path: canonicalPath,
|
|
137
|
+
source: "bundled",
|
|
138
|
+
launch: {
|
|
139
|
+
program: nodePath,
|
|
140
|
+
args: [canonicalPath],
|
|
141
|
+
...(electron ? { env: { ELECTRON_RUN_AS_NODE: "1" } } : {}),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
161
145
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
146
|
+
interface ConfiguredBinaryChecks {
|
|
147
|
+
platform: NodeJS.Platform;
|
|
148
|
+
nodePath: string;
|
|
149
|
+
canExecute: (path: string) => Promise<boolean>;
|
|
150
|
+
isFile: (path: string) => Promise<boolean>;
|
|
151
|
+
readVersion: (launch: LaunchDescriptor) => Promise<string | null>;
|
|
152
|
+
}
|
|
168
153
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const electron = dependencies.electron ?? process.versions.electron !== undefined;
|
|
154
|
+
async function resolveConfiguredBinary(
|
|
155
|
+
configuredPath: string,
|
|
156
|
+
checks: ConfiguredBinaryChecks,
|
|
157
|
+
): Promise<BinaryResolution> {
|
|
158
|
+
if (!(await checks.canExecute(configuredPath))) {
|
|
175
159
|
return {
|
|
176
|
-
state: "
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
launch: {
|
|
180
|
-
program: nodePath,
|
|
181
|
-
args: [canonicalPath],
|
|
182
|
-
...(electron ? { env: { ELECTRON_RUN_AS_NODE: "1" } } : {}),
|
|
183
|
-
},
|
|
160
|
+
state: "missing",
|
|
161
|
+
code: "not-installed",
|
|
162
|
+
message: `The configured Context Mode binary (${configuredPath}) is missing or not executable.`,
|
|
184
163
|
};
|
|
185
164
|
}
|
|
186
|
-
|
|
187
|
-
|
|
165
|
+
const canonicalPath = await realpath(configuredPath).catch(() => configuredPath);
|
|
166
|
+
const launch =
|
|
167
|
+
checks.platform === "win32"
|
|
168
|
+
? await windowsLaunchDescriptor(canonicalPath, checks.nodePath, checks.isFile)
|
|
169
|
+
: { program: canonicalPath, args: [] };
|
|
170
|
+
if (!launch) {
|
|
188
171
|
return {
|
|
172
|
+
state: "missing",
|
|
189
173
|
code: "unsupported",
|
|
174
|
+
message: `The configured Context Mode binary (${canonicalPath}) is a Windows shim whose CLI module could not be found. Configure the absolute cli.mjs path instead.`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// 1.x starts a server but lacks the 2.x tool contracts the plugin and injected agents rely on.
|
|
178
|
+
const version = await checks.readVersion(launch);
|
|
179
|
+
if (version !== null && Number.parseInt(version, 10) < 2) {
|
|
180
|
+
return {
|
|
190
181
|
state: "missing",
|
|
191
|
-
|
|
182
|
+
code: "unsupported",
|
|
183
|
+
message: `The configured Context Mode binary is version ${version}; Context Mode 2 is required. Point it at a 2.x install or switch back to the bundled runtime.`,
|
|
192
184
|
};
|
|
193
185
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
return { state: "found", path: canonicalPath, source: "settings", launch };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const VERSION_PATTERN = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/;
|
|
190
|
+
/** `--version` results per entry file; status, audit and every RPC resolve the binary again. */
|
|
191
|
+
const cliVersions = new Map<string, { mtimeMs: number; version: string | null }>();
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The MCP handshake reports `dev` for npm builds, so the version comes from the package.json beside
|
|
195
|
+
* the CLI entry when it is a Context Mode install, else from `context-mode --version`, which only
|
|
196
|
+
* runs again once the entry file changes.
|
|
197
|
+
*/
|
|
198
|
+
export async function readContextModeVersion(
|
|
199
|
+
launch: LaunchDescriptor,
|
|
200
|
+
timeoutMs = 5_000,
|
|
201
|
+
): Promise<string | null> {
|
|
202
|
+
const entry = launch.args[0] ?? launch.program;
|
|
203
|
+
try {
|
|
204
|
+
const manifest = JSON.parse(await readFile(join(dirname(entry), "package.json"), "utf8"));
|
|
205
|
+
if (manifest?.name === "context-mode" && typeof manifest.version === "string") {
|
|
206
|
+
return manifest.version.slice(0, 128);
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
// Not a package directory; ask the CLI.
|
|
210
|
+
}
|
|
211
|
+
const mtimeMs = (await stat(entry).catch(() => null))?.mtimeMs;
|
|
212
|
+
const cached = cliVersions.get(entry);
|
|
213
|
+
if (cached && cached.mtimeMs === mtimeMs) return cached.version;
|
|
214
|
+
let version: string | null = null;
|
|
215
|
+
try {
|
|
216
|
+
const { stdout } = await promisify(execFile)(launch.program, [...launch.args, "--version"], {
|
|
217
|
+
timeout: timeoutMs,
|
|
218
|
+
windowsHide: true,
|
|
219
|
+
env: launch.env ? { ...process.env, ...launch.env } : undefined,
|
|
220
|
+
});
|
|
221
|
+
version = VERSION_PATTERN.exec(stdout)?.[0] ?? null;
|
|
222
|
+
} catch {
|
|
223
|
+
// Unknown version: not treated as 1.x.
|
|
224
|
+
}
|
|
225
|
+
// An unknown version is not cached: a timed-out probe must not let a 1.x binary past the gate.
|
|
226
|
+
if (mtimeMs !== undefined && version !== null) cliVersions.set(entry, { mtimeMs, version });
|
|
227
|
+
return version;
|
|
203
228
|
}
|