@omercnet/paseo-omp 0.2.1-next.72.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +120 -0
  4. package/SUPPORT.md +42 -0
  5. package/TESTING.md +150 -0
  6. package/client/composer-pill-settings.tsx +157 -0
  7. package/client/hub-icon.tsx +12 -0
  8. package/client/hub-popover.tsx +132 -0
  9. package/client/hub-status.ts +29 -0
  10. package/client/mcp-authorization.tsx +168 -0
  11. package/client/mcp-popover.tsx +155 -0
  12. package/client/memory-panel.tsx +76 -0
  13. package/client/memory-popover.tsx +74 -0
  14. package/client/omp-config-surface.tsx +1433 -0
  15. package/client/omp-doc-links.ts +117 -0
  16. package/client/omp-plugin-manager.tsx +1004 -0
  17. package/client/omp-store-picker.tsx +89 -0
  18. package/client/omp-store-state.ts +45 -0
  19. package/client/provider-diagnostics-state.ts +262 -0
  20. package/client/provider-icon.tsx +27 -0
  21. package/client/provider-image.tsx +66 -0
  22. package/client/quota-popover.tsx +155 -0
  23. package/client/quota-state.ts +140 -0
  24. package/client/sessions-popover.tsx +78 -0
  25. package/docs/alpha-release-checklist.md +68 -0
  26. package/docs/configuration.md +126 -0
  27. package/docs/core-provider-issue-audit.md +108 -0
  28. package/docs/images/mcp-authorization-compact.png +0 -0
  29. package/docs/images/mcp-controls-wide.png +0 -0
  30. package/docs/images/plugin-manager.png +0 -0
  31. package/docs/images/workspace-settings.png +0 -0
  32. package/docs/installation.md +67 -0
  33. package/index.client.tsx +488 -0
  34. package/index.server.ts +81 -0
  35. package/package.json +84 -0
  36. package/paseo-plugin.json +5 -0
  37. package/scripts/prepare-dependencies.mjs +20 -0
  38. package/server/hub.ts +145 -0
  39. package/server/mcp-browser.ts +95 -0
  40. package/server/memory.ts +86 -0
  41. package/server/mutation-queue.ts +12 -0
  42. package/server/omp-config.ts +135 -0
  43. package/server/omp-plugins.ts +676 -0
  44. package/server/omp-settings.ts +499 -0
  45. package/server/paths.ts +181 -0
  46. package/server/provider/catalog.ts +172 -0
  47. package/server/provider/config-normalization.ts +148 -0
  48. package/server/provider/connection.ts +1196 -0
  49. package/server/provider/host-tools.ts +777 -0
  50. package/server/provider/image.ts +143 -0
  51. package/server/provider/mcp-transport.ts +394 -0
  52. package/server/provider/omp-rpc.ts +2806 -0
  53. package/server/provider/omp.svg +5 -0
  54. package/server/provider/profile-providers.ts +249 -0
  55. package/server/provider/provider-options.ts +27 -0
  56. package/server/provider/registration.ts +162 -0
  57. package/server/provider/security.ts +317 -0
  58. package/server/provider/session-descriptors.ts +736 -0
  59. package/server/provider/session.ts +4796 -0
  60. package/server/provider/settings.ts +78 -0
  61. package/server/provider/subsessions.ts +850 -0
  62. package/server/provider/timeline-projector.ts +1801 -0
  63. package/server/provider-diagnostics.ts +1143 -0
  64. package/server/quota.ts +55 -0
  65. package/server/sessions.ts +58 -0
  66. package/shared/composer-pill-settings.ts +28 -0
  67. package/shared/hub.ts +43 -0
  68. package/shared/mcp.ts +47 -0
  69. package/shared/memory.ts +24 -0
  70. package/shared/omp-config.ts +85 -0
  71. package/shared/omp-plugins.ts +264 -0
  72. package/shared/omp-settings.ts +214 -0
  73. package/shared/omp-store.ts +58 -0
  74. package/shared/provider-diagnostics.ts +126 -0
  75. package/shared/provider-image.ts +160 -0
  76. package/shared/quota.ts +23 -0
  77. package/shared/sessions.ts +24 -0
  78. package/tsconfig.json +16 -0
@@ -0,0 +1,172 @@
1
+ import { createHash } from "node:crypto";
2
+ import type {
3
+ ProviderCatalog,
4
+ ProviderMode,
5
+ ProviderModel,
6
+ ProviderThinkingOption,
7
+ } from "@getpaseo/plugin/server/provider";
8
+ import type { NormalizedOmpStartOptions } from "./config-normalization";
9
+ import type { OmpModel, OmpRuntime, OmpRuntimeSession } from "./omp-rpc";
10
+ import {
11
+ configuredOutputRedactionValues,
12
+ OmpCleanupFailure,
13
+ OmpPublicDataSerializer,
14
+ OmpPublicError,
15
+ } from "./security";
16
+
17
+ export const OMP_MODES: readonly ProviderMode[] = [
18
+ {
19
+ id: "full",
20
+ label: "Full Access",
21
+ description: "Runs all tools without approval prompts.",
22
+ icon: "ShieldOff",
23
+ colorTier: "dangerous",
24
+ isUnattended: true,
25
+ },
26
+ {
27
+ id: "write",
28
+ label: "Write Approval",
29
+ description: "Runs reads without approval; writes require approval.",
30
+ icon: "ShieldAlert",
31
+ colorTier: "moderate",
32
+ },
33
+ {
34
+ id: "ask",
35
+ label: "Always Ask",
36
+ description: "Requires approval for write and execution tools.",
37
+ icon: "ShieldCheck",
38
+ colorTier: "safe",
39
+ },
40
+ ];
41
+
42
+ const THINKING_OPTIONS: readonly ProviderThinkingOption[] = [
43
+ { id: "off", label: "Off", description: "No extra reasoning" },
44
+ { id: "minimal", label: "Minimal", description: "Light reasoning" },
45
+ { id: "low", label: "Low", description: "Faster reasoning" },
46
+ { id: "medium", label: "Medium", description: "Balanced reasoning", isDefault: true },
47
+ { id: "high", label: "High", description: "Deeper reasoning" },
48
+ { id: "xhigh", label: "XHigh", description: "Extra-high reasoning" },
49
+ { id: "max", label: "Max", description: "Maximum reasoning" },
50
+ ];
51
+
52
+ export function nativeOmpModelId(model: OmpModel): string {
53
+ if (model.provider.includes("/")) {
54
+ throw new OmpPublicError("OMP reported an invalid model provider");
55
+ }
56
+ return `${model.provider}/${model.id}`;
57
+ }
58
+
59
+ export function ompModelId(model: OmpModel): string {
60
+ const nativeIdentity = `${Buffer.byteLength(model.provider, "utf8")}:${model.provider}${Buffer.byteLength(model.id, "utf8")}:${model.id}`;
61
+ return `omp:model:${createHash("sha256").update(nativeIdentity).digest("hex")}`;
62
+ }
63
+
64
+ export function mapOmpModels(
65
+ models: readonly OmpModel[],
66
+ serializer = new OmpPublicDataSerializer(),
67
+ ): ProviderModel[] {
68
+ const seenIds = new Map<string, string>();
69
+ return models.map((model) => {
70
+ const thinkingOptions = thinkingForModel(model);
71
+ const id = ompModelId(model);
72
+ const nativeIdentity = nativeOmpModelId(model);
73
+ const existing = seenIds.get(id);
74
+ if (existing !== undefined && existing !== nativeIdentity) {
75
+ throw new Error("OMP model identity collision");
76
+ }
77
+ if (existing !== undefined) throw new Error("OMP reported a duplicate model identity");
78
+ seenIds.set(id, nativeIdentity);
79
+ const provider = serializer.text(model.provider, 256);
80
+ const modelId = serializer.text(model.id, 256);
81
+ const name = model.name ? serializer.text(model.name, 256) : modelId;
82
+ return {
83
+ id,
84
+ label: `${provider}/${name}`,
85
+ description: `${provider}/${modelId}`,
86
+ ...(typeof model.contextWindow === "number"
87
+ ? { contextWindowMaxTokens: model.contextWindow }
88
+ : {}),
89
+ ...(thinkingOptions.length > 0
90
+ ? {
91
+ thinkingOptions,
92
+ defaultThinkingOptionId:
93
+ thinkingOptions.find((option) => option.isDefault)?.id ?? thinkingOptions[0]?.id,
94
+ }
95
+ : {}),
96
+ metadata: { provider, modelId },
97
+ };
98
+ });
99
+ }
100
+
101
+ export function thinkingForModel(model: OmpModel | null | undefined): ProviderThinkingOption[] {
102
+ if (!model?.reasoning) return [];
103
+ const efforts = model.thinking?.efforts ?? [];
104
+ const supported = THINKING_OPTIONS.filter((option) => efforts.includes(option.id));
105
+ const defaultLevel = model.thinking?.defaultLevel;
106
+ const selectedDefault = supported.some((option) => option.id === defaultLevel)
107
+ ? defaultLevel
108
+ : supported[0]?.id;
109
+ return supported.map((option) => ({ ...option, isDefault: option.id === selectedDefault }));
110
+ }
111
+
112
+ async function closeCatalogSession(session: OmpRuntimeSession): Promise<void> {
113
+ const cleanup = session.close();
114
+ try {
115
+ await cleanup;
116
+ } catch {
117
+ throw new OmpCleanupFailure("OMP catalog cleanup failed", cleanup);
118
+ }
119
+ }
120
+
121
+ export async function discoverOmpCatalog(
122
+ runtime: OmpRuntime,
123
+ options: NormalizedOmpStartOptions,
124
+ signal?: AbortSignal,
125
+ environment?: NodeJS.ProcessEnv,
126
+ ): Promise<ProviderCatalog> {
127
+ const session = await runtime.startSession({
128
+ ...options,
129
+ signal,
130
+ environment,
131
+ });
132
+ try {
133
+ const [nativeModels, state] = await Promise.all([
134
+ session.getAvailableModels(),
135
+ session.getState(),
136
+ ]);
137
+ const configuredValues = configuredOutputRedactionValues(
138
+ options.outputRedaction ?? "none",
139
+ options.env,
140
+ );
141
+ const serializer = new OmpPublicDataSerializer(
142
+ options.outputRedaction === "configured-values"
143
+ ? [...configuredValues, ...(session.inheritedRedactionValues ?? [])]
144
+ : configuredValues,
145
+ );
146
+ const models = mapOmpModels(nativeModels, serializer);
147
+ if (models.length === 0) throw new Error("OMP reported no available models");
148
+ const defaultModel = state.model ? ompModelId(state.model) : models[0]?.id;
149
+ const currentModel = state.model
150
+ ? nativeModels.find(
151
+ (model) => model.provider === state.model?.provider && model.id === state.model.id,
152
+ )
153
+ : nativeModels[0];
154
+ if (state.model && !currentModel) throw new Error("OMP reported an unadvertised active model");
155
+ const thinkingOptions = thinkingForModel(currentModel);
156
+ const defaultThinkingOption = thinkingOptions.some(
157
+ (option) => option.id === state.thinkingLevel,
158
+ )
159
+ ? state.thinkingLevel
160
+ : undefined;
161
+ return {
162
+ models,
163
+ modes: OMP_MODES,
164
+ thinkingOptions,
165
+ defaultModel,
166
+ defaultMode: "full",
167
+ ...(defaultThinkingOption ? { defaultThinkingOption } : {}),
168
+ };
169
+ } finally {
170
+ await closeCatalogSession(session);
171
+ }
172
+ }
@@ -0,0 +1,148 @@
1
+ import type { ProviderSessionConfig } from "@getpaseo/plugin/server/provider";
2
+ import type { OmpStartOptions } from "./omp-rpc";
3
+
4
+ type ProviderCatalogOptionsCompat = {
5
+ scope: "global" | "workspace";
6
+ cwd?: string;
7
+ force?: boolean;
8
+ providerOptions?: Readonly<Record<string, unknown>>;
9
+ settings?: Readonly<Record<string, unknown>>;
10
+ };
11
+
12
+ import { parseOmpProviderOptions } from "./provider-options";
13
+ import { OmpPublicError } from "./security";
14
+ import { OmpModeSchema } from "./settings";
15
+
16
+ const OMP_BUILTIN_TOOL_NAMES = [
17
+ "read",
18
+ "bash",
19
+ "edit",
20
+ "ast_grep",
21
+ "ast_edit",
22
+ "ask",
23
+ "debug",
24
+ "eval",
25
+ "github",
26
+ "glob",
27
+ "grep",
28
+ "lsp",
29
+ "checkpoint",
30
+ "rewind",
31
+ "security_scan",
32
+ "task",
33
+ "hub",
34
+ "todo",
35
+ "web_search",
36
+ "write",
37
+ "memory_edit",
38
+ "retain",
39
+ "recall",
40
+ "reflect",
41
+ "learn",
42
+ "manage_skill",
43
+ ] as const;
44
+ const OMP_BUILTIN_TOOL_NAME_SET: Readonly<Record<string, true>> = Object.fromEntries(
45
+ OMP_BUILTIN_TOOL_NAMES.map((name) => [name, true]),
46
+ );
47
+
48
+ function allowedOmpTools(deniedTools: readonly string[] | undefined): string[] | undefined {
49
+ if (!deniedTools?.length) return;
50
+ const denied = new Set(deniedTools.map((name) => name.trim().toLowerCase()));
51
+ const unsupported = [...denied].filter((name) => OMP_BUILTIN_TOOL_NAME_SET[name] !== true);
52
+ if (unsupported.length > 0) {
53
+ throw new OmpPublicError(
54
+ `OMP cannot enforce unknown denied tools: ${unsupported.sort().join(", ")}`,
55
+ );
56
+ }
57
+ return OMP_BUILTIN_TOOL_NAMES.filter((name) => !denied.has(name));
58
+ }
59
+
60
+ export type NormalizedOmpStartOptions = Omit<OmpStartOptions, "environment" | "signal">;
61
+ export type OmpRecoveryOptions = Omit<OmpStartOptions, "resumeSessionId" | "signal">;
62
+ export function normalizeOmpCatalogOptions(
63
+ options: ProviderCatalogOptionsCompat,
64
+ cwd: string,
65
+ ): Omit<OmpStartOptions, "environment" | "signal"> {
66
+ const providerOptions = parseOmpProviderOptions(options.providerOptions);
67
+ const params = providerOptions.params ?? {};
68
+ return {
69
+ cwd,
70
+ mode: "full",
71
+ noSession: true,
72
+ ...(providerOptions.command ? { command: providerOptions.command } : {}),
73
+ ...(providerOptions.env ? { env: providerOptions.env } : {}),
74
+ ...(providerOptions.inheritEnv ? { inheritEnv: providerOptions.inheritEnv } : {}),
75
+ outputRedaction: providerOptions.outputRedaction,
76
+ ...(params.sessionDir ? { sessionDir: params.sessionDir } : {}),
77
+ ...(params.rpcTimeoutMs
78
+ ? { readyTimeoutMs: params.rpcTimeoutMs, requestTimeoutMs: params.rpcTimeoutMs }
79
+ : {}),
80
+ ...((params.smolModel || params.slowModel || params.planModel) && {
81
+ roleModels: {
82
+ ...(params.smolModel ? { smol: params.smolModel } : {}),
83
+ ...(params.slowModel ? { slow: params.slowModel } : {}),
84
+ ...(params.planModel ? { plan: params.planModel } : {}),
85
+ },
86
+ }),
87
+ };
88
+ }
89
+ /** Replace only runtime-committed selection fields on an immutable recovery template. */
90
+ export function withCommittedOmpSelection(
91
+ template: OmpRecoveryOptions,
92
+ selection: Readonly<{ model?: string; thinkingOption?: string }>,
93
+ ): OmpRecoveryOptions {
94
+ const next = { ...template };
95
+ if (selection.model === undefined) delete next.model;
96
+ else next.model = selection.model;
97
+ if (selection.thinkingOption === undefined) delete next.thinkingOption;
98
+ else next.thinkingOption = selection.thinkingOption;
99
+ return next;
100
+ }
101
+
102
+ /** Convert the public plugin session envelope into the native OMP launch contract. */
103
+ export function normalizeOmpSessionConfig(
104
+ config: ProviderSessionConfig,
105
+ permissionSupported = false,
106
+ ): NormalizedOmpStartOptions {
107
+ if (Object.keys(config.settings).length > 0) {
108
+ throw new OmpPublicError("OMP does not expose live provider settings");
109
+ }
110
+ const parsedMode = OmpModeSchema.safeParse(config.mode ?? "full");
111
+ if (!parsedMode.success) {
112
+ throw new OmpPublicError(`Unsupported OMP mode '${String(config.mode)}'`);
113
+ }
114
+ if (parsedMode.data !== "full" && !permissionSupported) {
115
+ throw new OmpPublicError(
116
+ `OMP mode '${parsedMode.data}' requires negotiated permission support`,
117
+ );
118
+ }
119
+ const options = parseOmpProviderOptions(config.providerOptions);
120
+ const deniedTools = (config as ProviderSessionConfig & { deniedTools?: readonly string[] })
121
+ .deniedTools;
122
+ const tools = allowedOmpTools(deniedTools);
123
+ const params = options.params ?? {};
124
+ const env = { ...options.env, ...config.env };
125
+ return {
126
+ cwd: config.cwd,
127
+ ...(options.command ? { command: options.command } : {}),
128
+ ...(Object.keys(env).length > 0 ? { env } : {}),
129
+ ...(options.inheritEnv ? { inheritEnv: options.inheritEnv } : {}),
130
+ outputRedaction: options.outputRedaction,
131
+ mode: parsedMode.data,
132
+ thinkingOption: config.thinkingOption,
133
+ systemPrompt: config.systemPrompt,
134
+ noSession: !config.persist,
135
+ ...(params.sessionDir ? { sessionDir: params.sessionDir } : {}),
136
+ ...(params.rpcTimeoutMs
137
+ ? { readyTimeoutMs: params.rpcTimeoutMs, requestTimeoutMs: params.rpcTimeoutMs }
138
+ : {}),
139
+ ...((params.smolModel || params.slowModel || params.planModel) && {
140
+ roleModels: {
141
+ ...(params.smolModel ? { smol: params.smolModel } : {}),
142
+ ...(params.slowModel ? { slow: params.slowModel } : {}),
143
+ ...(params.planModel ? { plan: params.planModel } : {}),
144
+ },
145
+ }),
146
+ ...(tools ? { tools } : {}),
147
+ };
148
+ }