@omercnet/paseo-omp 0.2.1 → 0.3.0-next.100.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 +7 -3
- 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 +248 -17
- package/server/provider/host-tools.ts +294 -26
- package/server/provider/omp-rpc.ts +498 -72
- package/server/provider/profile-providers.ts +249 -0
- package/server/provider/registration.ts +11 -0
- package/server/provider/security.ts +8 -10
- package/server/provider/session-descriptors.ts +306 -1
- package/server/provider/session.ts +704 -249
- package/server/provider/subsessions.ts +25 -2
- package/server/provider/timeline-projector.ts +104 -44
- 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
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
OmpVersionStatus,
|
|
18
18
|
PathState,
|
|
19
19
|
} from "../shared/provider-diagnostics";
|
|
20
|
-
import { ompAgentDir } from "./paths";
|
|
20
|
+
import { currentOmpEnvironment, ompAgentDir, ompDataDir, ompSessionDir } from "./paths";
|
|
21
21
|
|
|
22
22
|
const VERSION_TIMEOUT_MS = 3_000;
|
|
23
23
|
const HELP_TIMEOUT_MS = 3_000;
|
|
@@ -190,7 +190,11 @@ export async function killWindowsProcessTree(
|
|
|
190
190
|
|
|
191
191
|
let child: ProbeChildProcess;
|
|
192
192
|
try {
|
|
193
|
-
child = spawnFn(
|
|
193
|
+
child = spawnFn(
|
|
194
|
+
taskkillPath,
|
|
195
|
+
["/pid", String(pid), "/t", "/f"],
|
|
196
|
+
buildProbeEnv(currentOmpEnvironment()),
|
|
197
|
+
);
|
|
194
198
|
} catch {
|
|
195
199
|
return false;
|
|
196
200
|
}
|
|
@@ -243,7 +247,7 @@ export function defaultSpawn(
|
|
|
243
247
|
return killWindowsProcessTree(
|
|
244
248
|
child.pid,
|
|
245
249
|
defaultSpawn,
|
|
246
|
-
|
|
250
|
+
currentOmpEnvironment().SystemRoot ?? WINDOWS_DEFAULT_SYSTEM_ROOT,
|
|
247
251
|
graceMs,
|
|
248
252
|
);
|
|
249
253
|
}
|
|
@@ -569,7 +573,7 @@ export async function probeOmpAvailability(options: OmpAvailabilityProbeOptions)
|
|
|
569
573
|
status: "missing" | "unrunnable" | "incompatible" | "available";
|
|
570
574
|
diagnostic?: string;
|
|
571
575
|
}> {
|
|
572
|
-
const environment = options.environment ??
|
|
576
|
+
const environment = options.environment ?? currentOmpEnvironment();
|
|
573
577
|
const [command, ...prefixArgs] = options.command;
|
|
574
578
|
const resolvedPath = await resolveExecutablePath(
|
|
575
579
|
command,
|
|
@@ -810,31 +814,57 @@ async function computeMcpDiagnostics(agentDir: string): Promise<OmpMcpDiagnostic
|
|
|
810
814
|
export interface ProcessDiagnosticsFs {
|
|
811
815
|
readdir(path: string): Promise<string[]>;
|
|
812
816
|
lstat(path: string): Promise<Stats>;
|
|
817
|
+
readMetadata(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
813
818
|
}
|
|
814
819
|
|
|
815
|
-
const processDiagnosticsFs: ProcessDiagnosticsFs = {
|
|
820
|
+
const processDiagnosticsFs: ProcessDiagnosticsFs = {
|
|
821
|
+
readdir,
|
|
822
|
+
lstat,
|
|
823
|
+
readMetadata: readBoundedNoSymlinkFile,
|
|
824
|
+
};
|
|
825
|
+
const MAX_PROCESS_METADATA_BYTES = 64 * 1024;
|
|
826
|
+
const MAX_PROCESS_METADATA_ENTRIES = 4_096;
|
|
827
|
+
const ProcessStateSchema = z.object({
|
|
828
|
+
daemon: z.object({
|
|
829
|
+
state: z.string().max(32),
|
|
830
|
+
exitedAt: z.number().finite().nonnegative().nullish(),
|
|
831
|
+
}),
|
|
832
|
+
});
|
|
833
|
+
const ACTIVE_PROCESS_STATES = new Set(["starting", "running", "ready", "restarting"]);
|
|
834
|
+
const HISTORICAL_PROCESS_STATES = new Set(["exited", "failed", "stopped"]);
|
|
816
835
|
|
|
817
836
|
/**
|
|
818
|
-
*
|
|
819
|
-
*
|
|
837
|
+
* Classifies bounded, regular, non-symlink Hub metadata. A recorded active state is not a
|
|
838
|
+
* liveness check. Historical files remain useful evidence and are never counted as running.
|
|
839
|
+
* Only aggregate counts leave this reader; metadata arguments, paths and values are discarded.
|
|
820
840
|
*/
|
|
821
841
|
export async function computeProcessDiagnostics(
|
|
822
842
|
hubRunRoot: string,
|
|
823
843
|
fs: ProcessDiagnosticsFs = processDiagnosticsFs,
|
|
824
844
|
): Promise<OmpProcessDiagnostics> {
|
|
845
|
+
const unavailableCounts = {
|
|
846
|
+
trackedCount: null,
|
|
847
|
+
activeCount: null,
|
|
848
|
+
historicalCount: null,
|
|
849
|
+
unknownCount: null,
|
|
850
|
+
};
|
|
825
851
|
let projectHashes: string[];
|
|
826
852
|
try {
|
|
827
853
|
const rootStats = await fs.lstat(hubRunRoot);
|
|
828
854
|
if (rootStats.isSymbolicLink() || !rootStats.isDirectory()) {
|
|
829
|
-
return { status: "unavailable",
|
|
855
|
+
return { status: "unavailable", ...unavailableCounts };
|
|
830
856
|
}
|
|
831
857
|
projectHashes = await fs.readdir(hubRunRoot);
|
|
832
858
|
} catch (error) {
|
|
833
|
-
return { status: isEnoent(error) ? "unavailable" : "unknown",
|
|
859
|
+
return { status: isEnoent(error) ? "unavailable" : "unknown", ...unavailableCounts };
|
|
834
860
|
}
|
|
835
861
|
let trackedCount = 0;
|
|
836
|
-
let
|
|
837
|
-
|
|
862
|
+
let activeCount = 0;
|
|
863
|
+
let historicalCount = 0;
|
|
864
|
+
let unknownCount = 0;
|
|
865
|
+
let partial = projectHashes.length > MAX_PROCESS_METADATA_ENTRIES;
|
|
866
|
+
let visitedEntries = 0;
|
|
867
|
+
for (const hash of projectHashes.slice(0, MAX_PROCESS_METADATA_ENTRIES)) {
|
|
838
868
|
const projectDir = join(hubRunRoot, hash);
|
|
839
869
|
const daemonsDir = join(projectDir, "daemons");
|
|
840
870
|
try {
|
|
@@ -855,15 +885,51 @@ export async function computeProcessDiagnostics(
|
|
|
855
885
|
continue;
|
|
856
886
|
}
|
|
857
887
|
for (const name of daemonNames) {
|
|
888
|
+
if (++visitedEntries > MAX_PROCESS_METADATA_ENTRIES) {
|
|
889
|
+
partial = true;
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
858
892
|
try {
|
|
859
|
-
const
|
|
860
|
-
|
|
893
|
+
const daemonDir = join(daemonsDir, name);
|
|
894
|
+
const directoryStats = await fs.lstat(daemonDir);
|
|
895
|
+
if (directoryStats.isSymbolicLink() || !directoryStats.isDirectory()) continue;
|
|
896
|
+
const metaPath = join(daemonDir, "meta.json");
|
|
897
|
+
const metaStats = await fs.lstat(metaPath);
|
|
898
|
+
if (metaStats.isSymbolicLink() || !metaStats.isFile()) continue;
|
|
899
|
+
trackedCount += 1;
|
|
900
|
+
let category: "active" | "historical" | "unknown" = "unknown";
|
|
901
|
+
try {
|
|
902
|
+
const file = await fs.readMetadata(metaPath, MAX_PROCESS_METADATA_BYTES);
|
|
903
|
+
if (file.state === "available") {
|
|
904
|
+
const parsed = ProcessStateSchema.safeParse(JSON.parse(file.text));
|
|
905
|
+
if (parsed.success) {
|
|
906
|
+
const { state, exitedAt } = parsed.data.daemon;
|
|
907
|
+
if (HISTORICAL_PROCESS_STATES.has(state) || exitedAt != null) category = "historical";
|
|
908
|
+
else if (ACTIVE_PROCESS_STATES.has(state)) category = "active";
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
} catch {
|
|
912
|
+
// Read/access/parse errors have an honest unknown bucket, never a guessed active state.
|
|
913
|
+
}
|
|
914
|
+
if (category === "historical") historicalCount += 1;
|
|
915
|
+
else if (category === "active") activeCount += 1;
|
|
916
|
+
else {
|
|
917
|
+
unknownCount += 1;
|
|
918
|
+
partial = true;
|
|
919
|
+
}
|
|
861
920
|
} catch (error) {
|
|
862
921
|
if (!isEnoent(error)) partial = true;
|
|
863
922
|
}
|
|
864
923
|
}
|
|
924
|
+
if (visitedEntries > MAX_PROCESS_METADATA_ENTRIES) break;
|
|
865
925
|
}
|
|
866
|
-
return {
|
|
926
|
+
return {
|
|
927
|
+
status: partial ? "partial" : "ok",
|
|
928
|
+
trackedCount,
|
|
929
|
+
activeCount,
|
|
930
|
+
historicalCount,
|
|
931
|
+
unknownCount,
|
|
932
|
+
};
|
|
867
933
|
}
|
|
868
934
|
|
|
869
935
|
function homeRelative(path: string, homeDir: string): string | null {
|
|
@@ -880,16 +946,23 @@ function sanitizeRootPath(path: string, homeDir: string): string {
|
|
|
880
946
|
return homeRelative(path, homeDir) ?? "<custom path>";
|
|
881
947
|
}
|
|
882
948
|
|
|
883
|
-
function sanitizeDerivedPath(
|
|
949
|
+
function sanitizeDerivedPath(
|
|
950
|
+
rawRoot: string,
|
|
951
|
+
sanitizedRoot: string,
|
|
952
|
+
fullPath: string,
|
|
953
|
+
fallbackHomeDir?: string,
|
|
954
|
+
): string {
|
|
884
955
|
const suffix = relative(rawRoot, fullPath);
|
|
885
956
|
if (suffix === "" || suffix === ".." || suffix.startsWith(`..${sep}`) || isAbsolute(suffix)) {
|
|
886
|
-
return sanitizedRoot;
|
|
957
|
+
return fallbackHomeDir ? sanitizeRootPath(fullPath, fallbackHomeDir) : sanitizedRoot;
|
|
887
958
|
}
|
|
888
959
|
return `${sanitizedRoot}/${suffix.split(sep).join("/")}`;
|
|
889
960
|
}
|
|
890
961
|
|
|
891
962
|
export interface ProviderDiagnosticsDeps {
|
|
892
963
|
agentDir: string;
|
|
964
|
+
dataDir?: string;
|
|
965
|
+
sessionDir?: string;
|
|
893
966
|
command: string;
|
|
894
967
|
pathDirs: readonly string[];
|
|
895
968
|
cwd: string;
|
|
@@ -910,6 +983,8 @@ export async function computeOmpProviderHealth(
|
|
|
910
983
|
deps: ProviderDiagnosticsDeps,
|
|
911
984
|
): Promise<OmpProviderHealth> {
|
|
912
985
|
const agentDir = resolve(deps.agentDir);
|
|
986
|
+
const dataDir = resolve(deps.dataDir ?? agentDir);
|
|
987
|
+
const sessionRoot = resolve(deps.sessionDir ?? join(dataDir, SESSION_DIR_NAME));
|
|
913
988
|
const homeDir = resolve(deps.homeDir);
|
|
914
989
|
const versionTimeoutMs = deps.versionTimeoutMs ?? VERSION_TIMEOUT_MS;
|
|
915
990
|
const helpTimeoutMs = deps.helpTimeoutMs ?? HELP_TIMEOUT_MS;
|
|
@@ -935,6 +1010,7 @@ export async function computeOmpProviderHealth(
|
|
|
935
1010
|
versionTimeoutMs,
|
|
936
1011
|
killGraceMs,
|
|
937
1012
|
maxVersionBytes,
|
|
1013
|
+
deps.cwd,
|
|
938
1014
|
)
|
|
939
1015
|
: null,
|
|
940
1016
|
installed
|
|
@@ -946,6 +1022,7 @@ export async function computeOmpProviderHealth(
|
|
|
946
1022
|
helpTimeoutMs,
|
|
947
1023
|
killGraceMs,
|
|
948
1024
|
maxHelpBytes,
|
|
1025
|
+
deps.cwd,
|
|
949
1026
|
)
|
|
950
1027
|
: null,
|
|
951
1028
|
]);
|
|
@@ -957,9 +1034,8 @@ export async function computeOmpProviderHealth(
|
|
|
957
1034
|
const lsp = helpRun ? computeLspDiagnostics(helpRun) : { status: "unknown" as const };
|
|
958
1035
|
const processCleanupFailed = Boolean(versionRun?.cleanupFailed || helpRun?.cleanupFailed);
|
|
959
1036
|
|
|
960
|
-
const agentDbPath = join(
|
|
961
|
-
const historyDbPath = join(
|
|
962
|
-
const sessionRoot = join(agentDir, SESSION_DIR_NAME);
|
|
1037
|
+
const agentDbPath = join(dataDir, AGENT_DB_FILENAME);
|
|
1038
|
+
const historyDbPath = join(dataDir, HISTORY_DB_FILENAME);
|
|
963
1039
|
const [
|
|
964
1040
|
configResult,
|
|
965
1041
|
agentRootState,
|
|
@@ -1000,7 +1076,7 @@ export async function computeOmpProviderHealth(
|
|
|
1000
1076
|
agentRootState,
|
|
1001
1077
|
configPath: sanitizeDerivedPath(agentDir, sanitizedAgentRoot, configResult.path),
|
|
1002
1078
|
configState,
|
|
1003
|
-
sessionRoot: sanitizeDerivedPath(agentDir, sanitizedAgentRoot, sessionRoot),
|
|
1079
|
+
sessionRoot: sanitizeDerivedPath(agentDir, sanitizedAgentRoot, sessionRoot, homeDir),
|
|
1004
1080
|
sessionRootState,
|
|
1005
1081
|
},
|
|
1006
1082
|
databases: {
|
|
@@ -1013,11 +1089,11 @@ export async function computeOmpProviderHealth(
|
|
|
1013
1089
|
};
|
|
1014
1090
|
}
|
|
1015
1091
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1092
|
+
const cachedHealth = new Map<string, { value: OmpProviderHealth; expiresAt: number }>();
|
|
1093
|
+
const inFlightHealth = new Map<string, Promise<OmpProviderHealth>>();
|
|
1018
1094
|
|
|
1019
|
-
function defaultHubRunRoot(): string {
|
|
1020
|
-
return
|
|
1095
|
+
function defaultHubRunRoot(environment: NodeJS.ProcessEnv = currentOmpEnvironment()): string {
|
|
1096
|
+
return environment.PASEO_OMP_RUN_DIR ?? join(homedir(), ".omp", "run", "daemons");
|
|
1021
1097
|
}
|
|
1022
1098
|
|
|
1023
1099
|
/**
|
|
@@ -1029,29 +1105,39 @@ function defaultHubRunRoot(): string {
|
|
|
1029
1105
|
export async function resolveGetOmpProviderHealth(
|
|
1030
1106
|
input: RpcInput<typeof getOmpProviderHealth>,
|
|
1031
1107
|
): Promise<OmpProviderHealth> {
|
|
1108
|
+
const cwd = input.cwd ?? process.cwd();
|
|
1109
|
+
const environment = currentOmpEnvironment();
|
|
1110
|
+
const agentDir = ompAgentDir(environment);
|
|
1111
|
+
const dataDir = ompDataDir(environment);
|
|
1112
|
+
const sessionDir = ompSessionDir(environment);
|
|
1032
1113
|
const now = Date.now();
|
|
1033
|
-
|
|
1034
|
-
|
|
1114
|
+
const key = JSON.stringify([cwd, agentDir, dataDir, sessionDir]);
|
|
1115
|
+
const cached = cachedHealth.get(key);
|
|
1116
|
+
if (!input.force && cached && cached.expiresAt > now) return cached.value;
|
|
1117
|
+
const inFlight = inFlightHealth.get(key);
|
|
1118
|
+
if (inFlight) return inFlight;
|
|
1035
1119
|
|
|
1036
1120
|
const computation = computeOmpProviderHealth({
|
|
1037
|
-
agentDir
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1121
|
+
agentDir,
|
|
1122
|
+
dataDir,
|
|
1123
|
+
sessionDir,
|
|
1124
|
+
command: environment.OMP_COMMAND ?? "omp",
|
|
1125
|
+
pathDirs: (environment.PATH ?? "").split(delimiter),
|
|
1126
|
+
cwd,
|
|
1041
1127
|
platform: process.platform,
|
|
1042
|
-
pathExt:
|
|
1043
|
-
env:
|
|
1128
|
+
pathExt: environment.PATHEXT ?? WINDOWS_DEFAULT_PATHEXT,
|
|
1129
|
+
env: environment,
|
|
1044
1130
|
spawnFn: defaultSpawn,
|
|
1045
|
-
hubRunRoot: defaultHubRunRoot(),
|
|
1131
|
+
hubRunRoot: defaultHubRunRoot(environment),
|
|
1046
1132
|
homeDir: homedir(),
|
|
1047
1133
|
})
|
|
1048
1134
|
.then((value) => {
|
|
1049
|
-
cachedHealth
|
|
1135
|
+
cachedHealth.set(key, { value, expiresAt: Date.now() + HEALTH_CACHE_TTL_MS });
|
|
1050
1136
|
return value;
|
|
1051
1137
|
})
|
|
1052
1138
|
.finally(() => {
|
|
1053
|
-
inFlightHealth
|
|
1139
|
+
inFlightHealth.delete(key);
|
|
1054
1140
|
});
|
|
1055
|
-
inFlightHealth
|
|
1141
|
+
inFlightHealth.set(key, computation);
|
|
1056
1142
|
return computation;
|
|
1057
1143
|
}
|
package/server/quota.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { DatabaseSync } from "node:sqlite";
|
|
2
3
|
import type { RpcInput } from "@getpaseo/plugin";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import type { listOmpQuotas, OmpQuota } from "../shared/quota";
|
|
5
|
-
import {
|
|
6
|
+
import { ompDataDir } from "./paths";
|
|
6
7
|
|
|
7
8
|
const QuotaRowSchema = z.object({
|
|
8
9
|
provider: z.string(),
|
|
@@ -50,5 +51,5 @@ export function listOmpQuotasFrom(path: string): OmpQuota[] {
|
|
|
50
51
|
export function resolveListOmpQuotas(_input: RpcInput<typeof listOmpQuotas>): {
|
|
51
52
|
quotas: OmpQuota[];
|
|
52
53
|
} {
|
|
53
|
-
return { quotas: listOmpQuotasFrom(
|
|
54
|
+
return { quotas: listOmpQuotasFrom(join(ompDataDir(), "agent.db")) };
|
|
54
55
|
}
|
package/server/sessions.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
3
3
|
import type { RpcInput } from "@getpaseo/plugin";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import type { listOmpSessions, OmpSessionEntry } from "../shared/sessions";
|
|
6
|
-
import {
|
|
6
|
+
import { ompDataDir } from "./paths";
|
|
7
7
|
|
|
8
8
|
const PROMPT_LIMIT = 400;
|
|
9
9
|
const ROW_LIMIT = 100;
|
|
@@ -54,5 +54,5 @@ export function listOmpSessionsFrom(path: string, cwd: string): OmpSessionEntry[
|
|
|
54
54
|
export function resolveListOmpSessions({ cwd }: RpcInput<typeof listOmpSessions>): {
|
|
55
55
|
sessions: OmpSessionEntry[];
|
|
56
56
|
} {
|
|
57
|
-
return { sessions: listOmpSessionsFrom(join(
|
|
57
|
+
return { sessions: listOmpSessionsFrom(join(ompDataDir(), "history.db"), cwd) };
|
|
58
58
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineSettings } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_COMPOSER_PILL_SETTINGS = {
|
|
5
|
+
mcp: true,
|
|
6
|
+
hub: true,
|
|
7
|
+
memory: true,
|
|
8
|
+
sessions: true,
|
|
9
|
+
quota: true,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export const composerPillSettingsSchema = z.object({
|
|
13
|
+
mcp: z.boolean().default(DEFAULT_COMPOSER_PILL_SETTINGS.mcp),
|
|
14
|
+
hub: z.boolean().default(DEFAULT_COMPOSER_PILL_SETTINGS.hub),
|
|
15
|
+
memory: z.boolean().default(DEFAULT_COMPOSER_PILL_SETTINGS.memory),
|
|
16
|
+
sessions: z.boolean().default(DEFAULT_COMPOSER_PILL_SETTINGS.sessions),
|
|
17
|
+
quota: z.boolean().default(DEFAULT_COMPOSER_PILL_SETTINGS.quota),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type ComposerPillSettings = z.infer<typeof composerPillSettingsSchema>;
|
|
21
|
+
export type ComposerPillKey = keyof ComposerPillSettings;
|
|
22
|
+
|
|
23
|
+
export const composerPillSettings = defineSettings({
|
|
24
|
+
id: "composer-pills",
|
|
25
|
+
scope: "host",
|
|
26
|
+
version: 1,
|
|
27
|
+
schema: composerPillSettingsSchema,
|
|
28
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ExternalUrlOpener = (url: string) => Promise<void>;
|
|
2
|
+
|
|
3
|
+
export function selectExternalUrlOpener(
|
|
4
|
+
paseoOpener: ExternalUrlOpener | undefined,
|
|
5
|
+
fallback: ExternalUrlOpener,
|
|
6
|
+
): ExternalUrlOpener {
|
|
7
|
+
return paseoOpener ?? fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function validatedHttpUrl(value: string): string {
|
|
11
|
+
let url: URL;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(value);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("Only absolute HTTP(S) URLs are supported.");
|
|
16
|
+
}
|
|
17
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
18
|
+
throw new Error("Only absolute HTTP(S) URLs are supported.");
|
|
19
|
+
}
|
|
20
|
+
return url.href;
|
|
21
|
+
}
|
package/shared/hub.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const HubProcessSchema = z.object({
|
|
|
23
23
|
exitCode: z.number().nullable(),
|
|
24
24
|
});
|
|
25
25
|
export type HubProcess = z.infer<typeof HubProcessSchema>;
|
|
26
|
-
const
|
|
26
|
+
export const OmpWorkspaceCwdSchema = z.string().min(1).max(4_096);
|
|
27
27
|
const ProcessNameSchema = z
|
|
28
28
|
.string()
|
|
29
29
|
.min(1)
|
|
@@ -32,12 +32,12 @@ const ProcessNameSchema = z
|
|
|
32
32
|
|
|
33
33
|
export const listHubProcesses = defineRpc({
|
|
34
34
|
name: "paseo-omp.list-processes",
|
|
35
|
-
input: z.object({ cwd:
|
|
35
|
+
input: z.object({ cwd: OmpWorkspaceCwdSchema }),
|
|
36
36
|
output: z.object({ processes: z.array(HubProcessSchema) }),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
export const tailHubLog = defineRpc({
|
|
40
40
|
name: "paseo-omp.tail-log",
|
|
41
|
-
input: z.object({ cwd:
|
|
41
|
+
input: z.object({ cwd: OmpWorkspaceCwdSchema, name: ProcessNameSchema }),
|
|
42
42
|
output: z.object({ content: z.string(), truncated: z.boolean() }),
|
|
43
43
|
});
|
package/shared/mcp.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const OMP_MCP_AUTH_TIMELINE_KIND = "omp-mcp-authorization";
|
|
5
|
+
const OmpMcpAuthorizationUrlSchema = z
|
|
6
|
+
.string()
|
|
7
|
+
.max(16_384)
|
|
8
|
+
.refine((value) => {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(value);
|
|
11
|
+
return (
|
|
12
|
+
(url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password
|
|
13
|
+
);
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}, "Authorization URL must be an HTTP URL without embedded credentials");
|
|
18
|
+
|
|
19
|
+
export const ompMcpAuthorizationTimelineSchema = z.object({
|
|
20
|
+
url: OmpMcpAuthorizationUrlSchema,
|
|
21
|
+
instructions: z
|
|
22
|
+
.string()
|
|
23
|
+
.max(64 * 1024)
|
|
24
|
+
.optional(),
|
|
25
|
+
loopbackCallback: z.boolean(),
|
|
26
|
+
browserAuthorizationToken: z.string().uuid().optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type OmpMcpAuthorizationTimeline = z.infer<typeof ompMcpAuthorizationTimelineSchema>;
|
|
30
|
+
export const openOmpMcpAuthorizationInPaseoBrowser = defineRpc({
|
|
31
|
+
name: "paseo-omp.open-mcp-authorization-in-browser",
|
|
32
|
+
input: z.object({ authorizationToken: z.string().uuid() }),
|
|
33
|
+
output: z.object({ opened: z.literal(true) }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const OMP_MCP_SERVER_NAME = /^[a-zA-Z0-9_.:-]{1,100}$/u;
|
|
37
|
+
|
|
38
|
+
export type OmpMcpServerAction = "test" | "reauth" | "enable" | "disable";
|
|
39
|
+
|
|
40
|
+
export function buildOmpMcpServerCommand(
|
|
41
|
+
action: OmpMcpServerAction,
|
|
42
|
+
serverName: string,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
const name = serverName.trim();
|
|
45
|
+
if (!OMP_MCP_SERVER_NAME.test(name)) return;
|
|
46
|
+
return `/mcp ${action} ${name}`;
|
|
47
|
+
}
|
package/shared/memory.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineRpc } from "@getpaseo/plugin";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
3
4
|
|
|
4
5
|
const CwdSchema = z.string().min(1).max(4_096);
|
|
5
6
|
|
|
@@ -15,7 +16,7 @@ export type OmpMemoryFact = z.infer<typeof OmpMemoryFactSchema>;
|
|
|
15
16
|
|
|
16
17
|
export const listOmpMemory = defineRpc({
|
|
17
18
|
name: "paseo-omp.list-memory",
|
|
18
|
-
input: z.object({ cwd: CwdSchema }),
|
|
19
|
+
input: z.object({ store: OmpStoreSchema.optional(), cwd: CwdSchema }),
|
|
19
20
|
output: z.object({
|
|
20
21
|
bank: z.string().nullable(),
|
|
21
22
|
facts: z.array(OmpMemoryFactSchema),
|
package/shared/omp-config.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineRpc } from "@getpaseo/plugin";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
3
5
|
|
|
4
6
|
// Mirrors the safe, non-secret subset of omp's on-disk ~/.omp/agent/config.yml. That file is an
|
|
5
7
|
// internal, unversioned config format owned by the omp harness (source: omp's
|
|
@@ -72,7 +74,9 @@ export type OmpConfig = z.infer<typeof OmpConfigSchema>;
|
|
|
72
74
|
|
|
73
75
|
export const listOmpConfig = defineRpc({
|
|
74
76
|
name: "paseo-omp.list-config",
|
|
75
|
-
input: z
|
|
77
|
+
input: z
|
|
78
|
+
.object({ store: OmpStoreSchema.optional(), cwd: OmpWorkspaceCwdSchema.optional() })
|
|
79
|
+
.strict(),
|
|
76
80
|
output: z.object({
|
|
77
81
|
path: z.string(),
|
|
78
82
|
available: z.boolean(),
|
package/shared/omp-plugins.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineRpc } from "@getpaseo/plugin";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
3
5
|
|
|
4
6
|
export const OMP_PLUGIN_LIMIT = 256;
|
|
5
7
|
export const OMP_PLUGIN_ARGUMENT_LIMIT = 512;
|
|
@@ -72,6 +74,7 @@ export const OmpInstalledPluginSchema = z
|
|
|
72
74
|
availableFeatures: z.array(z.string().min(1).max(128)).max(128),
|
|
73
75
|
configurable: z.boolean(),
|
|
74
76
|
ambiguous: z.boolean(),
|
|
77
|
+
configAmbiguous: z.boolean(),
|
|
75
78
|
usesDefaultFeatures: z.boolean(),
|
|
76
79
|
})
|
|
77
80
|
.strict();
|
|
@@ -89,7 +92,9 @@ export type OmpPluginState = z.infer<typeof OmpPluginStateSchema>;
|
|
|
89
92
|
|
|
90
93
|
export const listOmpPlugins = defineRpc({
|
|
91
94
|
name: "paseo-omp.list-plugins",
|
|
92
|
-
input: z
|
|
95
|
+
input: z
|
|
96
|
+
.object({ store: OmpStoreSchema.optional(), cwd: OmpWorkspaceCwdSchema.optional() })
|
|
97
|
+
.strict(),
|
|
93
98
|
output: OmpPluginStateSchema,
|
|
94
99
|
});
|
|
95
100
|
|
|
@@ -121,7 +126,13 @@ export type OmpPluginConfigState = z.infer<typeof OmpPluginConfigStateSchema>;
|
|
|
121
126
|
|
|
122
127
|
export const inspectOmpPluginConfig = defineRpc({
|
|
123
128
|
name: "paseo-omp.inspect-plugin-config",
|
|
124
|
-
input: z
|
|
129
|
+
input: z
|
|
130
|
+
.object({
|
|
131
|
+
store: OmpStoreSchema.optional(),
|
|
132
|
+
plugin: OmpPluginNameSchema,
|
|
133
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
134
|
+
})
|
|
135
|
+
.strict(),
|
|
125
136
|
output: OmpPluginConfigStateSchema,
|
|
126
137
|
});
|
|
127
138
|
|
|
@@ -151,6 +162,8 @@ export const OmpPluginConfigMutationSchema = z.discriminatedUnion("action", [
|
|
|
151
162
|
plugin: OmpPluginNameSchema,
|
|
152
163
|
key: OmpPluginConfigKeySchema,
|
|
153
164
|
value: z.union([OmpPluginConfigStringValueSchema, z.number().finite(), z.boolean()]),
|
|
165
|
+
store: OmpStoreSchema.optional(),
|
|
166
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
154
167
|
})
|
|
155
168
|
.strict(),
|
|
156
169
|
z
|
|
@@ -158,6 +171,8 @@ export const OmpPluginConfigMutationSchema = z.discriminatedUnion("action", [
|
|
|
158
171
|
action: z.literal("delete"),
|
|
159
172
|
plugin: OmpPluginNameSchema,
|
|
160
173
|
key: OmpPluginConfigKeySchema,
|
|
174
|
+
store: OmpStoreSchema.optional(),
|
|
175
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
161
176
|
})
|
|
162
177
|
.strict(),
|
|
163
178
|
]);
|
|
@@ -176,38 +191,64 @@ export const mutateOmpPluginConfig = defineRpc({
|
|
|
176
191
|
});
|
|
177
192
|
|
|
178
193
|
const ScopedMutationShape = {
|
|
179
|
-
scope:
|
|
194
|
+
scope: OmpPluginScopeSchema.optional(),
|
|
195
|
+
store: OmpStoreSchema.optional(),
|
|
196
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
180
197
|
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
198
|
+
export const OmpPluginMutationSchema = z
|
|
199
|
+
.discriminatedUnion("action", [
|
|
200
|
+
z
|
|
201
|
+
.object({
|
|
202
|
+
action: z.literal("install"),
|
|
203
|
+
source: OmpPluginInstallSourceSchema,
|
|
204
|
+
...ScopedMutationShape,
|
|
205
|
+
})
|
|
206
|
+
.strict(),
|
|
207
|
+
z
|
|
208
|
+
.object({
|
|
209
|
+
action: z.literal("enable"),
|
|
210
|
+
plugin: OmpPluginTargetSchema,
|
|
211
|
+
...ScopedMutationShape,
|
|
212
|
+
})
|
|
213
|
+
.strict(),
|
|
214
|
+
z
|
|
215
|
+
.object({
|
|
216
|
+
action: z.literal("disable"),
|
|
217
|
+
plugin: OmpPluginTargetSchema,
|
|
218
|
+
...ScopedMutationShape,
|
|
219
|
+
})
|
|
220
|
+
.strict(),
|
|
221
|
+
z
|
|
222
|
+
.object({
|
|
223
|
+
action: z.literal("uninstall"),
|
|
224
|
+
plugin: OmpPluginTargetSchema,
|
|
225
|
+
...ScopedMutationShape,
|
|
226
|
+
})
|
|
227
|
+
.strict(),
|
|
228
|
+
z
|
|
229
|
+
.object({
|
|
230
|
+
action: z.literal("upgrade"),
|
|
231
|
+
plugin: OmpMarketplacePluginIdSchema,
|
|
232
|
+
...ScopedMutationShape,
|
|
233
|
+
})
|
|
234
|
+
.strict(),
|
|
235
|
+
])
|
|
236
|
+
.superRefine((input, context) => {
|
|
237
|
+
if (input.scope === "project" && input.cwd === undefined) {
|
|
238
|
+
context.addIssue({
|
|
239
|
+
code: "custom",
|
|
240
|
+
message: "Project-scoped plugin actions require a workspace",
|
|
241
|
+
path: ["cwd"],
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (input.action === "install" && input.scope === "project") {
|
|
245
|
+
context.addIssue({
|
|
246
|
+
code: "custom",
|
|
247
|
+
message: "Project-scoped installation is not supported through this API",
|
|
248
|
+
path: ["scope"],
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
211
252
|
export type OmpPluginMutation = z.infer<typeof OmpPluginMutationSchema>;
|
|
212
253
|
|
|
213
254
|
export const mutateOmpPlugin = defineRpc({
|