@omercnet/paseo-omp 0.2.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 (63) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/SUPPORT.md +40 -0
  5. package/TESTING.md +147 -0
  6. package/client/hub-icon.tsx +12 -0
  7. package/client/hub-popover.tsx +132 -0
  8. package/client/hub-status.ts +29 -0
  9. package/client/memory-panel.tsx +71 -0
  10. package/client/memory-popover.tsx +70 -0
  11. package/client/omp-config-surface.tsx +1274 -0
  12. package/client/omp-doc-links.ts +117 -0
  13. package/client/omp-plugin-manager.tsx +833 -0
  14. package/client/provider-diagnostics-state.ts +250 -0
  15. package/client/provider-icon.tsx +27 -0
  16. package/client/provider-image.tsx +66 -0
  17. package/client/quota-popover.tsx +150 -0
  18. package/client/quota-state.ts +131 -0
  19. package/client/sessions-popover.tsx +73 -0
  20. package/docs/alpha-release-checklist.md +70 -0
  21. package/docs/configuration.md +122 -0
  22. package/docs/core-provider-issue-audit.md +108 -0
  23. package/docs/installation.md +73 -0
  24. package/index.client.tsx +272 -0
  25. package/index.server.ts +51 -0
  26. package/package.json +84 -0
  27. package/paseo-plugin.json +5 -0
  28. package/server/hub.ts +145 -0
  29. package/server/memory.ts +86 -0
  30. package/server/mutation-queue.ts +12 -0
  31. package/server/omp-config.ts +126 -0
  32. package/server/omp-plugins.ts +627 -0
  33. package/server/omp-settings.ts +291 -0
  34. package/server/paths.ts +64 -0
  35. package/server/provider/catalog.ts +173 -0
  36. package/server/provider/config-normalization.ts +148 -0
  37. package/server/provider/connection.ts +992 -0
  38. package/server/provider/host-tools.ts +706 -0
  39. package/server/provider/image.ts +143 -0
  40. package/server/provider/mcp-transport.ts +394 -0
  41. package/server/provider/omp-rpc.ts +2739 -0
  42. package/server/provider/omp.svg +5 -0
  43. package/server/provider/provider-options.ts +27 -0
  44. package/server/provider/registration.ts +151 -0
  45. package/server/provider/security.ts +317 -0
  46. package/server/provider/session-descriptors.ts +431 -0
  47. package/server/provider/session.ts +4451 -0
  48. package/server/provider/settings.ts +78 -0
  49. package/server/provider/subsessions.ts +847 -0
  50. package/server/provider/timeline-projector.ts +1764 -0
  51. package/server/provider-diagnostics.ts +1057 -0
  52. package/server/quota.ts +54 -0
  53. package/server/sessions.ts +58 -0
  54. package/shared/hub.ts +43 -0
  55. package/shared/memory.ts +23 -0
  56. package/shared/omp-config.ts +81 -0
  57. package/shared/omp-plugins.ts +223 -0
  58. package/shared/omp-settings.ts +207 -0
  59. package/shared/provider-diagnostics.ts +117 -0
  60. package/shared/provider-image.ts +160 -0
  61. package/shared/quota.ts +22 -0
  62. package/shared/sessions.ts +23 -0
  63. package/tsconfig.json +16 -0
@@ -0,0 +1,54 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { RpcInput } from "@getpaseo/plugin";
3
+ import { z } from "zod";
4
+ import type { listOmpQuotas, OmpQuota } from "../shared/quota";
5
+ import { ompAgentDir } from "./paths";
6
+
7
+ const QuotaRowSchema = z.object({
8
+ provider: z.string(),
9
+ label: z.string(),
10
+ windowLabel: z.string().nullable(),
11
+ usedFraction: z.number().min(0).nullable(),
12
+ status: z.string().nullable(),
13
+ resetsAt: z.number().int().nullable(),
14
+ recordedAt: z.number().int(),
15
+ });
16
+
17
+ export function listOmpQuotasFrom(path: string): OmpQuota[] {
18
+ try {
19
+ const database = new DatabaseSync(path, { readOnly: true, timeout: 500 });
20
+ try {
21
+ const rows = database
22
+ .prepare(
23
+ `SELECT provider, label, window_label AS windowLabel, used_fraction AS usedFraction,
24
+ status, resets_at AS resetsAt, recorded_at AS recordedAt
25
+ FROM (
26
+ SELECT provider, account_key, limit_id, label, window_label, used_fraction, status,
27
+ resets_at, recorded_at, id,
28
+ ROW_NUMBER() OVER (
29
+ PARTITION BY provider, account_key, limit_id
30
+ ORDER BY recorded_at DESC, id DESC
31
+ ) AS position
32
+ FROM usage_history
33
+ )
34
+ WHERE position = 1
35
+ ORDER BY COALESCE(usedFraction, -1) DESC, provider, label`,
36
+ )
37
+ .all();
38
+ return rows.flatMap((row) => {
39
+ const parsed = QuotaRowSchema.safeParse(row);
40
+ return parsed.success ? [parsed.data] : [];
41
+ });
42
+ } finally {
43
+ database.close();
44
+ }
45
+ } catch {
46
+ return [];
47
+ }
48
+ }
49
+
50
+ export function resolveListOmpQuotas(_input: RpcInput<typeof listOmpQuotas>): {
51
+ quotas: OmpQuota[];
52
+ } {
53
+ return { quotas: listOmpQuotasFrom(`${ompAgentDir()}/agent.db`) };
54
+ }
@@ -0,0 +1,58 @@
1
+ import { join } from "node:path";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import type { RpcInput } from "@getpaseo/plugin";
4
+ import { z } from "zod";
5
+ import type { listOmpSessions, OmpSessionEntry } from "../shared/sessions";
6
+ import { ompAgentDir } from "./paths";
7
+
8
+ const PROMPT_LIMIT = 400;
9
+ const ROW_LIMIT = 100;
10
+
11
+ const SessionRowSchema = z.object({
12
+ id: z.number().int(),
13
+ sessionId: z.string().nullable(),
14
+ title: z.string().nullable(),
15
+ prompt: z.string(),
16
+ createdAt: z.number().int(),
17
+ });
18
+
19
+ export function listOmpSessionsFrom(path: string, cwd: string): OmpSessionEntry[] {
20
+ try {
21
+ const database = new DatabaseSync(path, { readOnly: true, timeout: 500 });
22
+ try {
23
+ const rows = database
24
+ .prepare(
25
+ `SELECT h.id AS id, h.session_id AS sessionId, t.title AS title,
26
+ h.prompt AS prompt, h.created_at AS createdAt
27
+ FROM history h
28
+ LEFT JOIN session_titles t ON t.session_id = h.session_id
29
+ WHERE h.cwd = ?
30
+ ORDER BY h.created_at DESC, h.id DESC
31
+ LIMIT ?`,
32
+ )
33
+ .all(cwd, ROW_LIMIT);
34
+ return rows.flatMap((row) => {
35
+ const parsed = SessionRowSchema.safeParse(row);
36
+ if (!parsed.success) return [];
37
+ const truncated = parsed.data.prompt.length > PROMPT_LIMIT;
38
+ return [
39
+ {
40
+ ...parsed.data,
41
+ prompt: truncated ? parsed.data.prompt.slice(0, PROMPT_LIMIT) : parsed.data.prompt,
42
+ truncated,
43
+ },
44
+ ];
45
+ });
46
+ } finally {
47
+ database.close();
48
+ }
49
+ } catch {
50
+ return [];
51
+ }
52
+ }
53
+
54
+ export function resolveListOmpSessions({ cwd }: RpcInput<typeof listOmpSessions>): {
55
+ sessions: OmpSessionEntry[];
56
+ } {
57
+ return { sessions: listOmpSessionsFrom(join(ompAgentDir(), "history.db"), cwd) };
58
+ }
package/shared/hub.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ // Mirrors the on-disk shape omp's hub writes under
5
+ // ~/.omp/run/daemons/<projectHash>/daemons/<name>/meta.json. That layout is an internal,
6
+ // unversioned implementation detail of the omp harness, not a published API, so every field
7
+ // here is optional-safe on the server side and this schema is intentionally permissive
8
+ // (state is a free-form string, not a fixed enum) to avoid rejecting shapes we have not seen.
9
+ export const HubProcessSchema = z.object({
10
+ name: z.string(),
11
+ application: z.string(),
12
+ args: z.array(z.string()),
13
+ cwd: z.string(),
14
+ state: z.string(),
15
+ owner: z.string().nullable(),
16
+ restartCount: z.number().int().nonnegative(),
17
+ persist: z.boolean(),
18
+ detached: z.boolean(),
19
+ createdAt: z.number().nullable(),
20
+ startedAt: z.number().nullable(),
21
+ readyAt: z.number().nullable(),
22
+ exitedAt: z.number().nullable(),
23
+ exitCode: z.number().nullable(),
24
+ });
25
+ export type HubProcess = z.infer<typeof HubProcessSchema>;
26
+ const CwdSchema = z.string().min(1).max(4_096);
27
+ const ProcessNameSchema = z
28
+ .string()
29
+ .min(1)
30
+ .max(128)
31
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
32
+
33
+ export const listHubProcesses = defineRpc({
34
+ name: "paseo-omp.list-processes",
35
+ input: z.object({ cwd: CwdSchema }),
36
+ output: z.object({ processes: z.array(HubProcessSchema) }),
37
+ });
38
+
39
+ export const tailHubLog = defineRpc({
40
+ name: "paseo-omp.tail-log",
41
+ input: z.object({ cwd: CwdSchema, name: ProcessNameSchema }),
42
+ output: z.object({ content: z.string(), truncated: z.boolean() }),
43
+ });
@@ -0,0 +1,23 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ const CwdSchema = z.string().min(1).max(4_096);
5
+
6
+ export const OmpMemoryFactSchema = z.object({
7
+ id: z.string(),
8
+ subject: z.string(),
9
+ predicate: z.string(),
10
+ object: z.string(),
11
+ confidence: z.number().min(0).max(1),
12
+ timestamp: z.string().nullable(),
13
+ });
14
+ export type OmpMemoryFact = z.infer<typeof OmpMemoryFactSchema>;
15
+
16
+ export const listOmpMemory = defineRpc({
17
+ name: "paseo-omp.list-memory",
18
+ input: z.object({ cwd: CwdSchema }),
19
+ output: z.object({
20
+ bank: z.string().nullable(),
21
+ facts: z.array(OmpMemoryFactSchema),
22
+ }),
23
+ });
@@ -0,0 +1,81 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ // Mirrors the safe, non-secret subset of omp's on-disk ~/.omp/agent/config.yml. That file is an
5
+ // internal, unversioned config format owned by the omp harness (source: omp's
6
+ // settings-schema.ts), not a published API, so this is an explicit allowlist rather than a
7
+ // passthrough: every section below has been checked against that schema's `credential: true`
8
+ // markers and carries none. Sections the schema marks as credential-bearing (auth broker
9
+ // tokens, mnemopi/hindsight embedding and LLM API keys, searxng basic-auth, blob-destination
10
+ // headers) are deliberately absent and must stay that way. A field not listed here is never
11
+ // read, rendered, or forwarded across the RPC boundary — server/omp-config.ts parses each
12
+ // section independently and omits it entirely if it fails to match, rather than guessing or
13
+ // widening the schema.
14
+
15
+ export const OmpModelRolesSchema = z.record(z.string(), z.string());
16
+
17
+ export const OmpFallbackChainsSchema = z.record(z.string(), z.array(z.string()));
18
+
19
+ export const OmpThemeSectionSchema = z.object({
20
+ dark: z.string().optional(),
21
+ light: z.string().optional(),
22
+ });
23
+
24
+ export const OmpMemorySectionSchema = z.object({
25
+ backend: z.enum(["off", "local", "hindsight", "mnemopi", "sharpshooter"]).optional(),
26
+ });
27
+
28
+ export const OmpGithubCacheSectionSchema = z.object({
29
+ enabled: z.boolean().optional(),
30
+ softTtlSec: z.number().optional(),
31
+ hardTtlSec: z.number().optional(),
32
+ });
33
+
34
+ export const OmpGithubSectionSchema = z.object({
35
+ enabled: z.boolean().optional(),
36
+ cache: OmpGithubCacheSectionSchema.optional(),
37
+ });
38
+
39
+ export const OmpRetrySectionSchema = z.object({
40
+ enabled: z.boolean().optional(),
41
+ maxRetries: z.number().optional(),
42
+ baseDelayMs: z.number().optional(),
43
+ maxDelayMs: z.number().optional(),
44
+ waitForUsageReset: z.boolean().optional(),
45
+ modelFallback: z.boolean().optional(),
46
+ usageAwareFallback: z.boolean().optional(),
47
+ usageReservePct: z.number().optional(),
48
+ usageReservePolicy: z.enum(["confirm", "auto", "fail-closed"]).optional(),
49
+ fallbackRevertPolicy: z.enum(["cooldown-expiry", "never"]).optional(),
50
+ fallbackChains: OmpFallbackChainsSchema.optional(),
51
+ });
52
+
53
+ export const OmpDevSectionSchema = z.object({
54
+ autoqaConsent: z.enum(["unset", "granted", "denied"]).optional(),
55
+ });
56
+
57
+ export const OmpConfigSchema = z.object({
58
+ setupVersion: z.number().optional(),
59
+ symbolPreset: z.enum(["unicode", "nerd", "ascii"]).optional(),
60
+ defaultThinkingLevel: z.string().optional(),
61
+ theme: OmpThemeSectionSchema.optional(),
62
+ memory: OmpMemorySectionSchema.optional(),
63
+ github: OmpGithubSectionSchema.optional(),
64
+ disabledProviders: z.array(z.string()).optional(),
65
+ modelProviderOrder: z.array(z.string()).optional(),
66
+ modelRoles: OmpModelRolesSchema.optional(),
67
+ enabledModels: z.array(z.string()).optional(),
68
+ retry: OmpRetrySectionSchema.optional(),
69
+ dev: OmpDevSectionSchema.optional(),
70
+ });
71
+ export type OmpConfig = z.infer<typeof OmpConfigSchema>;
72
+
73
+ export const listOmpConfig = defineRpc({
74
+ name: "paseo-omp.list-config",
75
+ input: z.object({}),
76
+ output: z.object({
77
+ path: z.string(),
78
+ available: z.boolean(),
79
+ config: OmpConfigSchema.nullable(),
80
+ }),
81
+ });
@@ -0,0 +1,223 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ export const OMP_PLUGIN_LIMIT = 256;
5
+ export const OMP_PLUGIN_ARGUMENT_LIMIT = 512;
6
+
7
+ const SAFE_NPM_PACKAGE = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u;
8
+ const SAFE_MARKETPLACE_ID =
9
+ /^[a-z0-9](?:[a-z0-9.-]{0,62}[a-z0-9])?@[a-z0-9](?:[a-z0-9.-]{0,62}[a-z0-9])?$/u;
10
+
11
+ function utf8ByteLength(value: string): number {
12
+ let bytes = 0;
13
+ for (const character of value) {
14
+ const codePoint = character.codePointAt(0) ?? 0;
15
+ bytes += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
16
+ }
17
+ return bytes;
18
+ }
19
+
20
+ function hasUnsafeArgumentCharacter(value: string): boolean {
21
+ for (const character of value) {
22
+ const code = character.codePointAt(0) ?? 0;
23
+ if (code <= 0x1f || code === 0x7f) return true;
24
+ }
25
+ return false;
26
+ }
27
+
28
+ export const OmpPluginNameSchema = z
29
+ .string()
30
+ .min(1)
31
+ .max(214)
32
+ .regex(SAFE_NPM_PACKAGE, "Expected an installed OMP package name");
33
+
34
+ export const OmpMarketplacePluginIdSchema = z
35
+ .string()
36
+ .min(3)
37
+ .max(128)
38
+ .regex(SAFE_MARKETPLACE_ID, "Expected name@marketplace");
39
+
40
+ export const OmpPluginTargetSchema = z.union([OmpPluginNameSchema, OmpMarketplacePluginIdSchema]);
41
+
42
+ export const OmpPluginInstallSourceSchema = z
43
+ .string()
44
+ .min(1)
45
+ .max(OMP_PLUGIN_ARGUMENT_LIMIT)
46
+ .refine(
47
+ (value) => utf8ByteLength(value) <= OMP_PLUGIN_ARGUMENT_LIMIT,
48
+ "Plugin source is too large",
49
+ )
50
+ .refine((value) => value.trim() === value, "Plugin source must not have surrounding whitespace")
51
+ .refine(
52
+ (value) => !hasUnsafeArgumentCharacter(value),
53
+ "Plugin source contains an unsafe character",
54
+ )
55
+ .refine((value) => !value.startsWith("-"), "Plugin source must not be an option");
56
+
57
+ export const OmpPluginScopeSchema = z.enum(["user", "project"]);
58
+ export type OmpPluginScope = z.infer<typeof OmpPluginScopeSchema>;
59
+
60
+ export const OmpInstalledPluginSchema = z
61
+ .object({
62
+ id: z.string().min(1).max(214),
63
+ packageName: OmpPluginNameSchema.optional(),
64
+ version: z.string().min(1).max(128).nullable(),
65
+ source: z.enum(["npm", "marketplace"]),
66
+ scope: OmpPluginScopeSchema.nullable(),
67
+ enabled: z.boolean(),
68
+ shadowed: z.boolean(),
69
+ path: z.string().min(1).max(4_096).nullable(),
70
+ description: z.string().max(1_024).nullable(),
71
+ enabledFeatures: z.array(z.string().min(1).max(128)).max(128),
72
+ availableFeatures: z.array(z.string().min(1).max(128)).max(128),
73
+ configurable: z.boolean(),
74
+ ambiguous: z.boolean(),
75
+ usesDefaultFeatures: z.boolean(),
76
+ })
77
+ .strict();
78
+ export type OmpInstalledPlugin = z.infer<typeof OmpInstalledPluginSchema>;
79
+
80
+ export const OmpPluginStateSchema = z
81
+ .object({
82
+ available: z.boolean(),
83
+ plugins: z.array(OmpInstalledPluginSchema).max(OMP_PLUGIN_LIMIT),
84
+ droppedCount: z.number().int().nonnegative(),
85
+ error: z.string().max(256).optional(),
86
+ })
87
+ .strict();
88
+ export type OmpPluginState = z.infer<typeof OmpPluginStateSchema>;
89
+
90
+ export const listOmpPlugins = defineRpc({
91
+ name: "paseo-omp.list-plugins",
92
+ input: z.object({}).strict(),
93
+ output: OmpPluginStateSchema,
94
+ });
95
+
96
+ export const OmpPluginConfigSettingSchema = z
97
+ .object({
98
+ key: z.string().min(1).max(128),
99
+ type: z.enum(["string", "number", "boolean", "enum"]),
100
+ description: z.string().max(512),
101
+ configured: z.boolean(),
102
+ secret: z.boolean(),
103
+ enumValues: z.array(z.string().min(1).max(256)).max(128),
104
+ minimum: z.number().finite().optional(),
105
+ maximum: z.number().finite().optional(),
106
+ step: z.number().finite().positive().optional(),
107
+ })
108
+ .strict();
109
+ export type OmpPluginConfigSetting = z.infer<typeof OmpPluginConfigSettingSchema>;
110
+
111
+ export const OmpPluginConfigStateSchema = z
112
+ .object({
113
+ available: z.boolean(),
114
+ plugin: OmpPluginNameSchema,
115
+ settings: z.array(OmpPluginConfigSettingSchema).max(OMP_PLUGIN_LIMIT),
116
+ droppedCount: z.number().int().nonnegative(),
117
+ error: z.string().max(256).optional(),
118
+ })
119
+ .strict();
120
+ export type OmpPluginConfigState = z.infer<typeof OmpPluginConfigStateSchema>;
121
+
122
+ export const inspectOmpPluginConfig = defineRpc({
123
+ name: "paseo-omp.inspect-plugin-config",
124
+ input: z.object({ plugin: OmpPluginNameSchema }).strict(),
125
+ output: OmpPluginConfigStateSchema,
126
+ });
127
+
128
+ export const OmpPluginConfigKeySchema = z
129
+ .string()
130
+ .min(1)
131
+ .max(128)
132
+ .refine((value) => value.trim() === value, "Setting key must not have surrounding whitespace")
133
+ .refine((value) => !hasUnsafeArgumentCharacter(value), "Setting key contains an unsafe character")
134
+ .refine((value) => !value.startsWith("-"), "Setting key must not be an option");
135
+
136
+ export const OmpPluginConfigStringValueSchema = z
137
+ .string()
138
+ .min(1)
139
+ .max(4_096)
140
+ .refine((value) => utf8ByteLength(value) <= 4_096, "Setting value is too large")
141
+ .refine(
142
+ (value) => !hasUnsafeArgumentCharacter(value),
143
+ "Setting value contains an unsafe character",
144
+ )
145
+ .refine((value) => !value.startsWith("-"), "Setting value must not be an option");
146
+
147
+ export const OmpPluginConfigMutationSchema = z.discriminatedUnion("action", [
148
+ z
149
+ .object({
150
+ action: z.literal("set"),
151
+ plugin: OmpPluginNameSchema,
152
+ key: OmpPluginConfigKeySchema,
153
+ value: z.union([OmpPluginConfigStringValueSchema, z.number().finite(), z.boolean()]),
154
+ })
155
+ .strict(),
156
+ z
157
+ .object({
158
+ action: z.literal("delete"),
159
+ plugin: OmpPluginNameSchema,
160
+ key: OmpPluginConfigKeySchema,
161
+ })
162
+ .strict(),
163
+ ]);
164
+ export type OmpPluginConfigMutation = z.infer<typeof OmpPluginConfigMutationSchema>;
165
+
166
+ export const mutateOmpPluginConfig = defineRpc({
167
+ name: "paseo-omp.mutate-plugin-config",
168
+ input: OmpPluginConfigMutationSchema,
169
+ output: z
170
+ .object({
171
+ ok: z.boolean(),
172
+ message: z.string().min(1).max(256),
173
+ config: OmpPluginConfigStateSchema,
174
+ })
175
+ .strict(),
176
+ });
177
+
178
+ const ScopedMutationShape = {
179
+ scope: z.literal("user").optional(),
180
+ };
181
+
182
+ export const OmpPluginMutationSchema = z.discriminatedUnion("action", [
183
+ z
184
+ .object({
185
+ action: z.literal("install"),
186
+ source: OmpPluginInstallSourceSchema,
187
+ ...ScopedMutationShape,
188
+ })
189
+ .strict(),
190
+ z
191
+ .object({ action: z.literal("enable"), plugin: OmpPluginTargetSchema, ...ScopedMutationShape })
192
+ .strict(),
193
+ z
194
+ .object({ action: z.literal("disable"), plugin: OmpPluginTargetSchema, ...ScopedMutationShape })
195
+ .strict(),
196
+ z
197
+ .object({
198
+ action: z.literal("uninstall"),
199
+ plugin: OmpPluginTargetSchema,
200
+ ...ScopedMutationShape,
201
+ })
202
+ .strict(),
203
+ z
204
+ .object({
205
+ action: z.literal("upgrade"),
206
+ plugin: OmpMarketplacePluginIdSchema,
207
+ ...ScopedMutationShape,
208
+ })
209
+ .strict(),
210
+ ]);
211
+ export type OmpPluginMutation = z.infer<typeof OmpPluginMutationSchema>;
212
+
213
+ export const mutateOmpPlugin = defineRpc({
214
+ name: "paseo-omp.mutate-plugin",
215
+ input: OmpPluginMutationSchema,
216
+ output: z
217
+ .object({
218
+ ok: z.boolean(),
219
+ message: z.string().min(1).max(256),
220
+ state: OmpPluginStateSchema,
221
+ })
222
+ .strict(),
223
+ });
@@ -0,0 +1,207 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+
4
+ export const OMP_SETTINGS_CATALOG_VERSION = 1;
5
+
6
+ export const OmpSettingTypeSchema = z.enum([
7
+ "boolean",
8
+ "string",
9
+ "number",
10
+ "enum",
11
+ "array",
12
+ "record",
13
+ ]);
14
+ export type OmpSettingType = z.infer<typeof OmpSettingTypeSchema>;
15
+ export const OmpScalarValueSchema = z.union([z.boolean(), z.number(), z.string()]);
16
+ export type OmpScalarValue = z.infer<typeof OmpScalarValueSchema>;
17
+
18
+ export const OmpSettingSchema = z
19
+ .object({
20
+ path: z.string(),
21
+ type: OmpSettingTypeSchema,
22
+ description: z.string(),
23
+ value: z.unknown().optional(),
24
+ redacted: z.boolean().optional(),
25
+ configured: z.boolean().optional(),
26
+ })
27
+ .strict();
28
+ export type OmpSetting = z.infer<typeof OmpSettingSchema>;
29
+
30
+ export const OMP_SETTING_CATEGORIES = [
31
+ "appearance",
32
+ "model",
33
+ "interaction",
34
+ "context",
35
+ "memory",
36
+ "files",
37
+ "shell",
38
+ "tools",
39
+ "tasks",
40
+ "providers",
41
+ "general",
42
+ ] as const;
43
+ export type OmpSettingCategory = (typeof OMP_SETTING_CATEGORIES)[number];
44
+
45
+ const CATEGORY_PREFIXES: Readonly<Record<OmpSettingCategory, readonly string[]>> = {
46
+ appearance: [
47
+ "theme.",
48
+ "symbolPreset",
49
+ "colorBlindMode",
50
+ "composer.",
51
+ "statusLine.",
52
+ "terminal.",
53
+ "tui.",
54
+ "display.",
55
+ "showHardwareCursor",
56
+ "images.",
57
+ ],
58
+ model: [
59
+ "modelRoles",
60
+ "modelTags",
61
+ "modelRoleStorage",
62
+ "cycleOrder",
63
+ "enabledModels",
64
+ "defaultThinkingLevel",
65
+ "thinkingBudgets.",
66
+ "hideThinkingBlock",
67
+ "proseOnlyThinking",
68
+ "omitThinking",
69
+ "externalThinking",
70
+ "model.",
71
+ "inlineToolDescriptors",
72
+ "includeModelInPrompt",
73
+ "includeWorkspaceTree",
74
+ "personality",
75
+ "temperature",
76
+ "topP",
77
+ "topK",
78
+ "minP",
79
+ "presencePenalty",
80
+ "repetitionPenalty",
81
+ "textVerbosity",
82
+ "retry.",
83
+ "advisor.",
84
+ "prewalk.",
85
+ "tier.",
86
+ ],
87
+ interaction: [
88
+ "autoResume",
89
+ "power.",
90
+ "steeringMode",
91
+ "ask.",
92
+ "stt.",
93
+ "speech.",
94
+ "live.",
95
+ "collab.",
96
+ "magicKeywords",
97
+ "git.",
98
+ ],
99
+ context: ["compaction.", "context.", "contextPromotion.", "ttsr.", "recap.", "branchSummary."],
100
+ memory: ["memory.", "memories.", "mnemopi.", "hindsight.", "sharpshooter."],
101
+ files: ["edit.", "read.", "files.", "file.", "lsp.", "tree"],
102
+ shell: ["bash.", "eval.", "shell", "shellMinimizer."],
103
+ tools: [
104
+ "tools.",
105
+ "todo.",
106
+ "glob.",
107
+ "grep.",
108
+ "astGrep.",
109
+ "astEdit.",
110
+ "debug.",
111
+ "launch.",
112
+ "fetch.",
113
+ "vault.",
114
+ "github.",
115
+ "web_search.",
116
+ "browser.",
117
+ "computer.",
118
+ "checkpoint.",
119
+ "async.",
120
+ "irc.",
121
+ "mcp.",
122
+ "secrets.",
123
+ "extensionHandlers.",
124
+ "dev.",
125
+ ],
126
+ tasks: [
127
+ "plan.",
128
+ "goal.",
129
+ "task.",
130
+ "tasks.",
131
+ "worktree.",
132
+ "skills.",
133
+ "commands.",
134
+ "extensions",
135
+ "disabledExtensions",
136
+ ],
137
+ providers: [
138
+ "providers.",
139
+ "provider.",
140
+ "enabledProviders",
141
+ "disabledProviders",
142
+ "modelProviderOrder",
143
+ "exa.",
144
+ "searxng.",
145
+ "codexResets.",
146
+ ],
147
+ general: [],
148
+ };
149
+
150
+ export function categorizeOmpSetting(path: string): OmpSettingCategory {
151
+ for (const category of OMP_SETTING_CATEGORIES) {
152
+ if (CATEGORY_PREFIXES[category].some((prefix) => path === prefix || path.startsWith(prefix))) {
153
+ return category;
154
+ }
155
+ }
156
+ return "general";
157
+ }
158
+
159
+ export function formatOmpSettingLabel(path: string): string {
160
+ const leaf = path.split(".").at(-1) ?? path;
161
+ const words = leaf
162
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
163
+ .replace(/[_-]+/g, " ")
164
+ .trim();
165
+ return words ? `${words[0]?.toUpperCase() ?? ""}${words.slice(1)}` : path;
166
+ }
167
+
168
+ export const listOmpSettings = defineRpc({
169
+ name: "paseo-omp.list-settings",
170
+ input: z.object({}),
171
+ output: z.object({
172
+ catalogVersion: z.literal(OMP_SETTINGS_CATALOG_VERSION),
173
+ revision: z.string().optional(),
174
+ path: z.string().optional(),
175
+ available: z.boolean(),
176
+ droppedCount: z.number().int().nonnegative(),
177
+ settings: z.array(OmpSettingSchema),
178
+ error: z.string().optional(),
179
+ }),
180
+ });
181
+
182
+ const OmpSettingChangeSchema = z.discriminatedUnion("operation", [
183
+ z.object({ operation: z.literal("set"), path: z.string().min(1), value: OmpScalarValueSchema }),
184
+ z.object({ operation: z.literal("reset"), path: z.string().min(1) }),
185
+ ]);
186
+
187
+ export const updateOmpSettings = defineRpc({
188
+ name: "paseo-omp.update-settings",
189
+ input: z.object({
190
+ revision: z.string(),
191
+ changes: z.array(OmpSettingChangeSchema).min(1).max(100),
192
+ }),
193
+ output: z.object({
194
+ conflict: z.boolean(),
195
+ appliedPaths: z.array(z.string()),
196
+ failed: z.object({ path: z.string(), message: z.string() }).optional(),
197
+ catalog: z.object({
198
+ catalogVersion: z.literal(OMP_SETTINGS_CATALOG_VERSION),
199
+ available: z.boolean(),
200
+ revision: z.string().optional(),
201
+ path: z.string().optional(),
202
+ droppedCount: z.number().int().nonnegative(),
203
+ settings: z.array(OmpSettingSchema),
204
+ error: z.string().optional(),
205
+ }),
206
+ }),
207
+ });