@omercnet/paseo-omp 0.2.1 → 0.3.0-next.91.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/CHANGELOG.md +26 -0
- package/README.md +25 -13
- package/SUPPORT.md +6 -2
- package/TESTING.md +21 -18
- package/client/composer-pill-settings.tsx +157 -0
- package/client/external-url.ts +15 -0
- package/client/mcp-authorization.tsx +169 -0
- package/client/mcp-popover.tsx +155 -0
- package/client/memory-panel.tsx +8 -3
- package/client/memory-popover.tsx +8 -4
- package/client/omp-config-surface.tsx +189 -29
- package/client/omp-plugin-manager.tsx +302 -131
- package/client/omp-store-picker.tsx +89 -0
- package/client/omp-store-state.ts +45 -0
- package/client/paseo-types.ts +9 -0
- package/client/provider-diagnostics-state.ts +18 -7
- package/client/quota-popover.tsx +8 -3
- package/client/quota-state.ts +16 -7
- package/client/sessions-popover.tsx +8 -3
- package/docs/alpha-release-checklist.md +6 -8
- package/docs/configuration.md +8 -4
- package/docs/core-provider-issue-audit.md +3 -2
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +35 -19
- package/index.client.tsx +339 -123
- package/index.server.ts +44 -14
- package/package.json +7 -8
- package/paseo-plugin.json +2 -2
- package/scripts/prepare-dependencies.mjs +24 -0
- package/server/mcp-browser.ts +95 -0
- package/server/memory.ts +2 -2
- package/server/omp-config.ts +16 -7
- package/server/omp-plugins.ts +70 -21
- package/server/omp-settings.ts +232 -24
- package/server/paths.ts +128 -11
- package/server/provider/catalog.ts +3 -4
- package/server/provider/connection.ts +213 -9
- package/server/provider/host-tools.ts +71 -0
- package/server/provider/omp-rpc.ts +82 -15
- package/server/provider/profile-providers.ts +249 -0
- package/server/provider/registration.ts +11 -0
- package/server/provider/session-descriptors.ts +306 -1
- package/server/provider/session.ts +704 -249
- package/server/provider/subsessions.ts +4 -1
- package/server/provider/timeline-projector.ts +70 -33
- package/server/provider-diagnostics.ts +122 -36
- package/server/quota.ts +3 -2
- package/server/sessions.ts +2 -2
- package/shared/composer-pill-settings.ts +28 -0
- package/shared/external-url.ts +21 -0
- package/shared/hub.ts +3 -3
- package/shared/mcp.ts +47 -0
- package/shared/memory.ts +2 -1
- package/shared/omp-config.ts +5 -1
- package/shared/omp-plugins.ts +74 -33
- package/shared/omp-settings.ts +8 -1
- package/shared/omp-store.ts +58 -0
- package/shared/provider-diagnostics.ts +12 -3
- package/shared/quota.ts +2 -1
- package/shared/sessions.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-next.91.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"SUPPORT.md",
|
|
37
37
|
"TESTING.md",
|
|
38
38
|
"docs",
|
|
39
|
+
"scripts/prepare-dependencies.mjs",
|
|
39
40
|
"index.client.tsx",
|
|
40
41
|
"index.server.ts",
|
|
41
42
|
"client",
|
|
@@ -51,26 +52,24 @@
|
|
|
51
52
|
"test:coverage": "PASEO_OMP_COVERAGE=1 vitest run --coverage",
|
|
52
53
|
"test:integration:docker": "node --import tsx scripts/test-docker-host-tools.ts",
|
|
53
54
|
"test:integration:wsl": "node --import tsx scripts/test-wsl-host-tools.ts",
|
|
55
|
+
"test:integration:canary": "node --import tsx scripts/test-canary.ts",
|
|
54
56
|
"test:integration:install": "node --import tsx scripts/test-package-install.ts",
|
|
55
|
-
"package:release": "node --import tsx scripts/package-release.ts",
|
|
56
57
|
"typecheck": "tsc --noEmit"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
60
|
+
"@getpaseo/protocol": "0.9.0-beta.1",
|
|
59
61
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
60
62
|
"yaml": "^2.9.0"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
63
65
|
"@biomejs/biome": "^2.5.10",
|
|
64
|
-
"@getpaseo/cli": "0.
|
|
65
|
-
"@getpaseo/client": "0.
|
|
66
|
-
"@getpaseo/plugin": "0.
|
|
67
|
-
"@getpaseo/protocol": "0.8.0",
|
|
66
|
+
"@getpaseo/cli": "0.9.0-beta.1",
|
|
67
|
+
"@getpaseo/client": "0.9.0-beta.1",
|
|
68
|
+
"@getpaseo/plugin": "0.9.0-beta.1",
|
|
68
69
|
"@tanstack/react-query": "^5.102.3",
|
|
69
70
|
"@types/node": "^24.10.1",
|
|
70
71
|
"@types/react": "^19.2.18",
|
|
71
|
-
"esbuild": "^0.28.0",
|
|
72
72
|
"@vitest/coverage-v8": "^5.0.0",
|
|
73
|
-
"fflate": "^0.8.3",
|
|
74
73
|
"react": "19.1.0",
|
|
75
74
|
"react-native": "0.81.5",
|
|
76
75
|
"tsx": "^4.20.6",
|
package/paseo-plugin.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
|
|
8
|
+
export function prepareDependencies(root = projectRoot, execute = execFileSync) {
|
|
9
|
+
if (!existsSync(join(root, "package-lock.json"))) return false;
|
|
10
|
+
|
|
11
|
+
execute(
|
|
12
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
13
|
+
["ci", "--omit=dev", "--ignore-scripts"],
|
|
14
|
+
{
|
|
15
|
+
cwd: root,
|
|
16
|
+
stdio: "inherit",
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
23
|
+
prepareDependencies();
|
|
24
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { RpcInput } from "@getpaseo/plugin";
|
|
3
|
+
import type { openOmpMcpAuthorizationInPaseoBrowser } from "../shared/mcp";
|
|
4
|
+
import { OmpPublicError } from "./provider/security";
|
|
5
|
+
|
|
6
|
+
const MAX_REGISTERED_SESSIONS = 32;
|
|
7
|
+
const MAX_AUTHORIZATIONS_PER_SESSION = 16;
|
|
8
|
+
|
|
9
|
+
type BrowserAuthorizationOpener = (url: string) => Promise<void>;
|
|
10
|
+
|
|
11
|
+
type RegisteredSession = {
|
|
12
|
+
token: symbol;
|
|
13
|
+
open: BrowserAuthorizationOpener;
|
|
14
|
+
authorizations: Set<string>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type RegisteredAuthorization = {
|
|
18
|
+
agentId: string;
|
|
19
|
+
sessionToken: symbol;
|
|
20
|
+
url: string;
|
|
21
|
+
open: BrowserAuthorizationOpener;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface OmpBrowserAuthorizationRegistration {
|
|
25
|
+
issue(url: string): string | undefined;
|
|
26
|
+
remove(): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class OmpBrowserAuthorizationRegistry {
|
|
30
|
+
private readonly sessions = new Map<string, RegisteredSession>();
|
|
31
|
+
private readonly authorizations = new Map<string, RegisteredAuthorization>();
|
|
32
|
+
|
|
33
|
+
register(agentId: string, open: BrowserAuthorizationOpener): OmpBrowserAuthorizationRegistration {
|
|
34
|
+
if (this.sessions.has(agentId)) {
|
|
35
|
+
throw new OmpPublicError("OMP browser authorization is already registered for this agent");
|
|
36
|
+
}
|
|
37
|
+
if (this.sessions.size >= MAX_REGISTERED_SESSIONS) {
|
|
38
|
+
throw new OmpPublicError("OMP browser authorization session limit reached");
|
|
39
|
+
}
|
|
40
|
+
const session: RegisteredSession = { token: Symbol(agentId), open, authorizations: new Set() };
|
|
41
|
+
this.sessions.set(agentId, session);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
issue: (url) => {
|
|
45
|
+
if (this.sessions.get(agentId) !== session) return;
|
|
46
|
+
if (session.authorizations.size >= MAX_AUTHORIZATIONS_PER_SESSION) {
|
|
47
|
+
const oldest = session.authorizations.values().next().value;
|
|
48
|
+
if (oldest) {
|
|
49
|
+
session.authorizations.delete(oldest);
|
|
50
|
+
this.authorizations.delete(oldest);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const authorizationToken = randomUUID();
|
|
54
|
+
session.authorizations.add(authorizationToken);
|
|
55
|
+
this.authorizations.set(authorizationToken, {
|
|
56
|
+
agentId,
|
|
57
|
+
sessionToken: session.token,
|
|
58
|
+
url,
|
|
59
|
+
open,
|
|
60
|
+
});
|
|
61
|
+
return authorizationToken;
|
|
62
|
+
},
|
|
63
|
+
remove: () => {
|
|
64
|
+
if (this.sessions.get(agentId) !== session) return;
|
|
65
|
+
this.sessions.delete(agentId);
|
|
66
|
+
for (const authorizationToken of session.authorizations) {
|
|
67
|
+
this.authorizations.delete(authorizationToken);
|
|
68
|
+
}
|
|
69
|
+
session.authorizations.clear();
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async open(authorizationToken: string): Promise<void> {
|
|
75
|
+
const authorization = this.authorizations.get(authorizationToken);
|
|
76
|
+
const session = authorization ? this.sessions.get(authorization.agentId) : undefined;
|
|
77
|
+
if (!authorization || session?.token !== authorization.sessionToken) {
|
|
78
|
+
throw new OmpPublicError("The OMP browser authorization is no longer available");
|
|
79
|
+
}
|
|
80
|
+
await authorization.open(authorization.url);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clear(): void {
|
|
84
|
+
this.authorizations.clear();
|
|
85
|
+
this.sessions.clear();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function resolveOpenOmpMcpAuthorizationInPaseoBrowser(
|
|
90
|
+
input: RpcInput<typeof openOmpMcpAuthorizationInPaseoBrowser>,
|
|
91
|
+
registry: OmpBrowserAuthorizationRegistry,
|
|
92
|
+
): Promise<{ opened: true }> {
|
|
93
|
+
await registry.open(input.authorizationToken);
|
|
94
|
+
return { opened: true };
|
|
95
|
+
}
|
package/server/memory.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
4
4
|
import type { RpcInput } from "@getpaseo/plugin";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import type { listOmpMemory, OmpMemoryFact } from "../shared/memory";
|
|
7
|
-
import {
|
|
7
|
+
import { ompStateDir } from "./paths";
|
|
8
8
|
|
|
9
9
|
const FactRowSchema = z.object({
|
|
10
10
|
id: z.string(),
|
|
@@ -82,5 +82,5 @@ export async function listOmpMemoryFrom(
|
|
|
82
82
|
export async function resolveListOmpMemory({
|
|
83
83
|
cwd,
|
|
84
84
|
}: RpcInput<typeof listOmpMemory>): Promise<{ bank: string | null; facts: OmpMemoryFact[] }> {
|
|
85
|
-
return listOmpMemoryFrom(join(
|
|
85
|
+
return listOmpMemoryFrom(join(ompStateDir(), "memories", "mnemopi", "banks"), cwd);
|
|
86
86
|
}
|
package/server/omp-config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
3
|
import type { RpcInput } from "@getpaseo/plugin";
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
5
|
import type { ZodType } from "zod";
|
|
@@ -26,8 +26,11 @@ const CONFIG_FILENAMES = ["config.yml", "config.yaml"] as const;
|
|
|
26
26
|
|
|
27
27
|
type OmpConfigResult = { path: string; available: boolean; config: OmpConfig | null };
|
|
28
28
|
|
|
29
|
-
async function readFirstExisting(
|
|
30
|
-
|
|
29
|
+
async function readFirstExisting(
|
|
30
|
+
dir: string,
|
|
31
|
+
filenames: readonly string[] = CONFIG_FILENAMES,
|
|
32
|
+
): Promise<{ path: string; text: string } | undefined> {
|
|
33
|
+
for (const filename of filenames) {
|
|
31
34
|
const path = join(dir, filename);
|
|
32
35
|
try {
|
|
33
36
|
return { path, text: await readFile(path, "utf8") };
|
|
@@ -106,9 +109,12 @@ export function parseOmpConfig(raw: unknown): OmpConfig {
|
|
|
106
109
|
return config;
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
export async function readOmpConfigFrom(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
export async function readOmpConfigFrom(
|
|
113
|
+
dir: string,
|
|
114
|
+
filenames: readonly string[] = CONFIG_FILENAMES,
|
|
115
|
+
): Promise<OmpConfigResult> {
|
|
116
|
+
const defaultPath = join(dir, filenames[0] ?? CONFIG_FILENAMES[0]);
|
|
117
|
+
const found = await readFirstExisting(dir, filenames);
|
|
112
118
|
if (!found) return { path: defaultPath, available: false, config: null };
|
|
113
119
|
try {
|
|
114
120
|
const raw: unknown = parseYaml(found.text);
|
|
@@ -120,7 +126,10 @@ export async function readOmpConfigFrom(dir: string): Promise<OmpConfigResult> {
|
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
export async function resolveListOmpConfig(
|
|
123
|
-
|
|
129
|
+
input: RpcInput<typeof listOmpConfig>,
|
|
124
130
|
): Promise<OmpConfigResult> {
|
|
131
|
+
if (input.cwd && isAbsolute(input.cwd) && !input.cwd.includes("\0")) {
|
|
132
|
+
return readOmpConfigFrom(join(input.cwd, ".omp"), [CONFIG_FILENAMES[0]]);
|
|
133
|
+
}
|
|
125
134
|
return readOmpConfigFrom(ompAgentDir());
|
|
126
135
|
}
|
package/server/omp-plugins.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
|
-
import { delimiter, join } from "node:path";
|
|
2
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
3
3
|
import type { RpcInput } from "@getpaseo/plugin";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type OmpPluginState,
|
|
20
20
|
} from "../shared/omp-plugins";
|
|
21
21
|
import { SerialMutationQueue } from "./mutation-queue";
|
|
22
|
+
import { currentOmpEnvironment } from "./paths";
|
|
22
23
|
import {
|
|
23
24
|
type BoundedRun,
|
|
24
25
|
buildStatefulCommandEnv,
|
|
@@ -78,9 +79,14 @@ export interface OmpPluginDependencies {
|
|
|
78
79
|
args: readonly string[],
|
|
79
80
|
outputLimit: number,
|
|
80
81
|
timeoutMs: number,
|
|
82
|
+
cwd?: string,
|
|
81
83
|
): Promise<BoundedRun>;
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
function validWorkspaceCwd(cwd: string | undefined): boolean {
|
|
87
|
+
return cwd === undefined || (isAbsolute(cwd) && !cwd.includes("\0"));
|
|
88
|
+
}
|
|
89
|
+
|
|
84
90
|
function boundedText(value: unknown, maximum: number): string | null {
|
|
85
91
|
if (typeof value !== "string" || value.length === 0 || value.length > maximum) return null;
|
|
86
92
|
if (/[\0\r\n]/u.test(value)) return null;
|
|
@@ -130,6 +136,7 @@ function parseNpmPlugin(raw: unknown): OmpInstalledPlugin | null {
|
|
|
130
136
|
enabled: candidate.data.enabled,
|
|
131
137
|
shadowed: false,
|
|
132
138
|
path,
|
|
139
|
+
configAmbiguous: false,
|
|
133
140
|
description,
|
|
134
141
|
enabledFeatures,
|
|
135
142
|
availableFeatures,
|
|
@@ -167,6 +174,7 @@ function parseMarketplacePlugin(raw: unknown): OmpInstalledPlugin | null {
|
|
|
167
174
|
shadowed: candidate.data.shadowedBy === "project",
|
|
168
175
|
path,
|
|
169
176
|
description: null,
|
|
177
|
+
configAmbiguous: false,
|
|
170
178
|
enabledFeatures: [],
|
|
171
179
|
availableFeatures: [],
|
|
172
180
|
configurable: false,
|
|
@@ -185,13 +193,24 @@ function pluginAliases(plugin: OmpInstalledPlugin): string[] {
|
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
function markAmbiguousPlugins(plugins: OmpInstalledPlugin[]): OmpInstalledPlugin[] {
|
|
188
|
-
const
|
|
196
|
+
const lifecycleCounts = new Map<string, number>();
|
|
197
|
+
const configCounts = new Map<string, number>();
|
|
198
|
+
const lifecycleKey = (plugin: OmpInstalledPlugin) =>
|
|
199
|
+
plugin.source === "marketplace"
|
|
200
|
+
? `marketplace:${plugin.scope}:${plugin.id}`
|
|
201
|
+
: `npm:${plugin.id}`;
|
|
189
202
|
for (const plugin of plugins) {
|
|
190
|
-
|
|
203
|
+
const key = lifecycleKey(plugin);
|
|
204
|
+
lifecycleCounts.set(key, (lifecycleCounts.get(key) ?? 0) + 1);
|
|
205
|
+
if (plugin.packageName) {
|
|
206
|
+
configCounts.set(plugin.packageName, (configCounts.get(plugin.packageName) ?? 0) + 1);
|
|
207
|
+
}
|
|
191
208
|
}
|
|
192
209
|
return plugins.map((plugin) => ({
|
|
193
210
|
...plugin,
|
|
194
|
-
ambiguous:
|
|
211
|
+
ambiguous: (lifecycleCounts.get(lifecycleKey(plugin)) ?? 0) > 1,
|
|
212
|
+
configAmbiguous:
|
|
213
|
+
plugin.packageName !== undefined && (configCounts.get(plugin.packageName) ?? 0) > 1,
|
|
195
214
|
}));
|
|
196
215
|
}
|
|
197
216
|
|
|
@@ -237,12 +256,12 @@ function readFailure(result: BoundedRun): string {
|
|
|
237
256
|
|
|
238
257
|
async function resolveOmpExecutable(): Promise<string | null> {
|
|
239
258
|
return resolveExecutablePath(
|
|
240
|
-
|
|
241
|
-
(
|
|
259
|
+
currentOmpEnvironment().OMP_COMMAND ?? "omp",
|
|
260
|
+
(currentOmpEnvironment().PATH ?? "").split(delimiter),
|
|
242
261
|
{
|
|
243
262
|
cwd: process.cwd(),
|
|
244
263
|
platform: process.platform,
|
|
245
|
-
pathExt:
|
|
264
|
+
pathExt: currentOmpEnvironment().PATHEXT ?? WINDOWS_DEFAULT_PATHEXT,
|
|
246
265
|
},
|
|
247
266
|
);
|
|
248
267
|
}
|
|
@@ -252,16 +271,17 @@ async function runOmpPlugin(
|
|
|
252
271
|
args: readonly string[],
|
|
253
272
|
outputLimit: number,
|
|
254
273
|
timeoutMs: number,
|
|
274
|
+
cwd = process.cwd(),
|
|
255
275
|
): Promise<BoundedRun> {
|
|
256
276
|
return runBounded(
|
|
257
277
|
defaultSpawn,
|
|
258
278
|
executable,
|
|
259
279
|
args,
|
|
260
|
-
buildStatefulCommandEnv(
|
|
280
|
+
buildStatefulCommandEnv(currentOmpEnvironment()),
|
|
261
281
|
timeoutMs,
|
|
262
282
|
KILL_GRACE_MS,
|
|
263
283
|
outputLimit,
|
|
264
|
-
|
|
284
|
+
cwd,
|
|
265
285
|
);
|
|
266
286
|
}
|
|
267
287
|
|
|
@@ -270,7 +290,7 @@ async function enrichMarketplaceConfiguration(
|
|
|
270
290
|
): Promise<OmpInstalledPlugin[]> {
|
|
271
291
|
const enriched = await Promise.all(
|
|
272
292
|
plugins.map(async (plugin) => {
|
|
273
|
-
if (plugin.source !== "marketplace" ||
|
|
293
|
+
if (plugin.source !== "marketplace" || !plugin.path) return plugin;
|
|
274
294
|
try {
|
|
275
295
|
const metadataPath = join(plugin.path, "package.json");
|
|
276
296
|
const metadata = await stat(metadataPath);
|
|
@@ -301,6 +321,7 @@ const DEFAULT_DEPENDENCIES: OmpPluginDependencies = {
|
|
|
301
321
|
async function loadPluginState(
|
|
302
322
|
executable: string | null | undefined,
|
|
303
323
|
dependencies: OmpPluginDependencies,
|
|
324
|
+
cwd?: string,
|
|
304
325
|
): Promise<OmpPluginState> {
|
|
305
326
|
const resolved = executable === undefined ? await dependencies.resolveExecutable() : executable;
|
|
306
327
|
if (!resolved) return unavailableState("The OMP executable could not be resolved.");
|
|
@@ -309,6 +330,7 @@ async function loadPluginState(
|
|
|
309
330
|
["plugin", "list", "--json"],
|
|
310
331
|
LIST_OUTPUT_LIMIT,
|
|
311
332
|
READ_TIMEOUT_MS,
|
|
333
|
+
cwd,
|
|
312
334
|
);
|
|
313
335
|
if (!runSucceeded(result)) return unavailableState(readFailure(result));
|
|
314
336
|
try {
|
|
@@ -324,10 +346,11 @@ async function loadPluginState(
|
|
|
324
346
|
}
|
|
325
347
|
|
|
326
348
|
export async function listOmpPluginsWithDependencies(
|
|
327
|
-
|
|
349
|
+
input: RpcInput<typeof listOmpPlugins>,
|
|
328
350
|
dependencies: OmpPluginDependencies,
|
|
329
351
|
): Promise<OmpPluginState> {
|
|
330
|
-
return
|
|
352
|
+
if (!validWorkspaceCwd(input.cwd)) return unavailableState("The workspace path is invalid.");
|
|
353
|
+
return loadPluginState(undefined, dependencies, input.cwd);
|
|
331
354
|
}
|
|
332
355
|
|
|
333
356
|
export async function resolveListOmpPlugins(
|
|
@@ -417,12 +440,14 @@ async function loadPluginConfig(
|
|
|
417
440
|
executable: string,
|
|
418
441
|
plugin: string,
|
|
419
442
|
dependencies: OmpPluginDependencies,
|
|
443
|
+
cwd?: string,
|
|
420
444
|
): Promise<OmpPluginConfigState> {
|
|
421
445
|
const result = await dependencies.runPlugin(
|
|
422
446
|
executable,
|
|
423
447
|
["plugin", "config", "list", plugin, "--json"],
|
|
424
448
|
CONFIG_OUTPUT_LIMIT,
|
|
425
449
|
READ_TIMEOUT_MS,
|
|
450
|
+
cwd,
|
|
426
451
|
);
|
|
427
452
|
if (!runSucceeded(result)) {
|
|
428
453
|
return unavailableConfig(
|
|
@@ -443,9 +468,12 @@ export async function inspectOmpPluginConfigWithDependencies(
|
|
|
443
468
|
input: RpcInput<typeof inspectOmpPluginConfig>,
|
|
444
469
|
dependencies: OmpPluginDependencies,
|
|
445
470
|
): Promise<OmpPluginConfigState> {
|
|
471
|
+
if (!validWorkspaceCwd(input.cwd)) {
|
|
472
|
+
return unavailableConfig(input.plugin, "The workspace path is invalid.");
|
|
473
|
+
}
|
|
446
474
|
const executable = await dependencies.resolveExecutable();
|
|
447
475
|
return executable
|
|
448
|
-
? loadPluginConfig(executable, input.plugin, dependencies)
|
|
476
|
+
? loadPluginConfig(executable, input.plugin, dependencies, input.cwd)
|
|
449
477
|
: unavailableConfig(input.plugin, "The OMP executable could not be resolved.");
|
|
450
478
|
}
|
|
451
479
|
|
|
@@ -492,13 +520,17 @@ export async function mutateOmpPluginConfigWithDependencies(
|
|
|
492
520
|
input: RpcInput<typeof mutateOmpPluginConfig>,
|
|
493
521
|
dependencies: OmpPluginDependencies,
|
|
494
522
|
): Promise<{ ok: boolean; message: string; config: OmpPluginConfigState }> {
|
|
523
|
+
if (!validWorkspaceCwd(input.cwd)) {
|
|
524
|
+
const config = unavailableConfig(input.plugin, "The workspace path is invalid.");
|
|
525
|
+
return { ok: false, message: "The workspace path is invalid.", config };
|
|
526
|
+
}
|
|
495
527
|
const executable = await dependencies.resolveExecutable();
|
|
496
528
|
if (!executable) {
|
|
497
529
|
const config = unavailableConfig(input.plugin, "The OMP executable could not be resolved.");
|
|
498
530
|
return { ok: false, message: "The OMP executable could not be resolved.", config };
|
|
499
531
|
}
|
|
500
532
|
|
|
501
|
-
const current = await loadPluginConfig(executable, input.plugin, dependencies);
|
|
533
|
+
const current = await loadPluginConfig(executable, input.plugin, dependencies, input.cwd);
|
|
502
534
|
const setting = current.settings.find((candidate) => candidate.key === input.key);
|
|
503
535
|
if (!current.available || !setting) {
|
|
504
536
|
return {
|
|
@@ -526,8 +558,9 @@ export async function mutateOmpPluginConfigWithDependencies(
|
|
|
526
558
|
buildOmpPluginConfigMutationArgs(input),
|
|
527
559
|
MUTATION_OUTPUT_LIMIT,
|
|
528
560
|
READ_TIMEOUT_MS,
|
|
561
|
+
input.cwd,
|
|
529
562
|
);
|
|
530
|
-
const config = await loadPluginConfig(executable, input.plugin, dependencies);
|
|
563
|
+
const config = await loadPluginConfig(executable, input.plugin, dependencies, input.cwd);
|
|
531
564
|
const mutationSucceeded = runSucceeded(mutation);
|
|
532
565
|
const ok = mutationSucceeded && config.available;
|
|
533
566
|
return {
|
|
@@ -553,7 +586,7 @@ export function resolveMutateOmpPluginConfig(
|
|
|
553
586
|
|
|
554
587
|
export function buildOmpPluginMutationArgs(input: OmpPluginMutation): string[] {
|
|
555
588
|
const target = input.action === "install" ? input.source : input.plugin;
|
|
556
|
-
return ["plugin", input.action, target, "--scope", "user", "--json"];
|
|
589
|
+
return ["plugin", input.action, target, "--scope", input.scope ?? "user", "--json"];
|
|
557
590
|
}
|
|
558
591
|
|
|
559
592
|
function mutationFailure(result: BoundedRun): string {
|
|
@@ -583,6 +616,10 @@ export async function mutateOmpPluginWithDependencies(
|
|
|
583
616
|
input: RpcInput<typeof mutateOmpPlugin>,
|
|
584
617
|
dependencies: OmpPluginDependencies,
|
|
585
618
|
): Promise<{ ok: boolean; message: string; state: OmpPluginState }> {
|
|
619
|
+
if (!validWorkspaceCwd(input.cwd)) {
|
|
620
|
+
const state = unavailableState("The workspace path is invalid.");
|
|
621
|
+
return { ok: false, message: "The workspace path is invalid.", state };
|
|
622
|
+
}
|
|
586
623
|
const executable = await dependencies.resolveExecutable();
|
|
587
624
|
if (!executable) {
|
|
588
625
|
const state = unavailableState("The OMP executable could not be resolved.");
|
|
@@ -590,15 +627,26 @@ export async function mutateOmpPluginWithDependencies(
|
|
|
590
627
|
}
|
|
591
628
|
|
|
592
629
|
if (input.action !== "install") {
|
|
593
|
-
const before = await loadPluginState(executable, dependencies);
|
|
630
|
+
const before = await loadPluginState(executable, dependencies, input.cwd);
|
|
594
631
|
if (!before.available) {
|
|
595
632
|
return { ok: false, message: "OMP plugin state is unavailable.", state: before };
|
|
596
633
|
}
|
|
597
|
-
const matches = before.plugins.filter(
|
|
598
|
-
|
|
634
|
+
const matches = before.plugins.filter(
|
|
635
|
+
(plugin) =>
|
|
636
|
+
pluginAliases(plugin).includes(input.plugin) &&
|
|
637
|
+
(input.scope === undefined || plugin.scope === input.scope),
|
|
638
|
+
);
|
|
639
|
+
if (matches.length !== 1 || matches[0]?.ambiguous) {
|
|
640
|
+
return {
|
|
641
|
+
ok: false,
|
|
642
|
+
message: "The plugin target is ambiguous in this scope.",
|
|
643
|
+
state: before,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
if (matches[0]?.scope === "project" && !input.cwd) {
|
|
599
647
|
return {
|
|
600
648
|
ok: false,
|
|
601
|
-
message: "
|
|
649
|
+
message: "Project-scoped plugin actions require a workspace.",
|
|
602
650
|
state: before,
|
|
603
651
|
};
|
|
604
652
|
}
|
|
@@ -609,8 +657,9 @@ export async function mutateOmpPluginWithDependencies(
|
|
|
609
657
|
buildOmpPluginMutationArgs(input),
|
|
610
658
|
MUTATION_OUTPUT_LIMIT,
|
|
611
659
|
MUTATION_TIMEOUT_MS,
|
|
660
|
+
input.cwd,
|
|
612
661
|
);
|
|
613
|
-
const state = await loadPluginState(executable, dependencies);
|
|
662
|
+
const state = await loadPluginState(executable, dependencies, input.cwd);
|
|
614
663
|
return {
|
|
615
664
|
ok: runSucceeded(mutation),
|
|
616
665
|
message: runSucceeded(mutation) ? mutationSuccess(input.action) : mutationFailure(mutation),
|