@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.
- package/CHANGELOG.md +87 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/SUPPORT.md +42 -0
- package/TESTING.md +150 -0
- package/client/composer-pill-settings.tsx +157 -0
- package/client/hub-icon.tsx +12 -0
- package/client/hub-popover.tsx +132 -0
- package/client/hub-status.ts +29 -0
- package/client/mcp-authorization.tsx +168 -0
- package/client/mcp-popover.tsx +155 -0
- package/client/memory-panel.tsx +76 -0
- package/client/memory-popover.tsx +74 -0
- package/client/omp-config-surface.tsx +1433 -0
- package/client/omp-doc-links.ts +117 -0
- package/client/omp-plugin-manager.tsx +1004 -0
- package/client/omp-store-picker.tsx +89 -0
- package/client/omp-store-state.ts +45 -0
- package/client/provider-diagnostics-state.ts +262 -0
- package/client/provider-icon.tsx +27 -0
- package/client/provider-image.tsx +66 -0
- package/client/quota-popover.tsx +155 -0
- package/client/quota-state.ts +140 -0
- package/client/sessions-popover.tsx +78 -0
- package/docs/alpha-release-checklist.md +68 -0
- package/docs/configuration.md +126 -0
- package/docs/core-provider-issue-audit.md +108 -0
- 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 +67 -0
- package/index.client.tsx +488 -0
- package/index.server.ts +81 -0
- package/package.json +84 -0
- package/paseo-plugin.json +5 -0
- package/scripts/prepare-dependencies.mjs +20 -0
- package/server/hub.ts +145 -0
- package/server/mcp-browser.ts +95 -0
- package/server/memory.ts +86 -0
- package/server/mutation-queue.ts +12 -0
- package/server/omp-config.ts +135 -0
- package/server/omp-plugins.ts +676 -0
- package/server/omp-settings.ts +499 -0
- package/server/paths.ts +181 -0
- package/server/provider/catalog.ts +172 -0
- package/server/provider/config-normalization.ts +148 -0
- package/server/provider/connection.ts +1196 -0
- package/server/provider/host-tools.ts +777 -0
- package/server/provider/image.ts +143 -0
- package/server/provider/mcp-transport.ts +394 -0
- package/server/provider/omp-rpc.ts +2806 -0
- package/server/provider/omp.svg +5 -0
- package/server/provider/profile-providers.ts +249 -0
- package/server/provider/provider-options.ts +27 -0
- package/server/provider/registration.ts +162 -0
- package/server/provider/security.ts +317 -0
- package/server/provider/session-descriptors.ts +736 -0
- package/server/provider/session.ts +4796 -0
- package/server/provider/settings.ts +78 -0
- package/server/provider/subsessions.ts +850 -0
- package/server/provider/timeline-projector.ts +1801 -0
- package/server/provider-diagnostics.ts +1143 -0
- package/server/quota.ts +55 -0
- package/server/sessions.ts +58 -0
- package/shared/composer-pill-settings.ts +28 -0
- package/shared/hub.ts +43 -0
- package/shared/mcp.ts +47 -0
- package/shared/memory.ts +24 -0
- package/shared/omp-config.ts +85 -0
- package/shared/omp-plugins.ts +264 -0
- package/shared/omp-settings.ts +214 -0
- package/shared/omp-store.ts +58 -0
- package/shared/provider-diagnostics.ts +126 -0
- package/shared/provider-image.ts +160 -0
- package/shared/quota.ts +23 -0
- package/shared/sessions.ts +24 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
5
|
+
|
|
6
|
+
export const OMP_SETTINGS_CATALOG_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
export const OmpSettingTypeSchema = z.enum([
|
|
9
|
+
"boolean",
|
|
10
|
+
"string",
|
|
11
|
+
"number",
|
|
12
|
+
"enum",
|
|
13
|
+
"array",
|
|
14
|
+
"record",
|
|
15
|
+
]);
|
|
16
|
+
export type OmpSettingType = z.infer<typeof OmpSettingTypeSchema>;
|
|
17
|
+
export const OmpScalarValueSchema = z.union([z.boolean(), z.number(), z.string()]);
|
|
18
|
+
export type OmpScalarValue = z.infer<typeof OmpScalarValueSchema>;
|
|
19
|
+
|
|
20
|
+
export const OmpSettingSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
path: z.string(),
|
|
23
|
+
type: OmpSettingTypeSchema,
|
|
24
|
+
description: z.string(),
|
|
25
|
+
value: z.unknown().optional(),
|
|
26
|
+
redacted: z.boolean().optional(),
|
|
27
|
+
configured: z.boolean().optional(),
|
|
28
|
+
workspaceOverride: z.boolean().optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
export type OmpSetting = z.infer<typeof OmpSettingSchema>;
|
|
32
|
+
|
|
33
|
+
export const OMP_SETTING_CATEGORIES = [
|
|
34
|
+
"appearance",
|
|
35
|
+
"model",
|
|
36
|
+
"interaction",
|
|
37
|
+
"context",
|
|
38
|
+
"memory",
|
|
39
|
+
"files",
|
|
40
|
+
"shell",
|
|
41
|
+
"tools",
|
|
42
|
+
"tasks",
|
|
43
|
+
"providers",
|
|
44
|
+
"general",
|
|
45
|
+
] as const;
|
|
46
|
+
export type OmpSettingCategory = (typeof OMP_SETTING_CATEGORIES)[number];
|
|
47
|
+
|
|
48
|
+
const CATEGORY_PREFIXES: Readonly<Record<OmpSettingCategory, readonly string[]>> = {
|
|
49
|
+
appearance: [
|
|
50
|
+
"theme.",
|
|
51
|
+
"symbolPreset",
|
|
52
|
+
"colorBlindMode",
|
|
53
|
+
"composer.",
|
|
54
|
+
"statusLine.",
|
|
55
|
+
"terminal.",
|
|
56
|
+
"tui.",
|
|
57
|
+
"display.",
|
|
58
|
+
"showHardwareCursor",
|
|
59
|
+
"images.",
|
|
60
|
+
],
|
|
61
|
+
model: [
|
|
62
|
+
"modelRoles",
|
|
63
|
+
"modelTags",
|
|
64
|
+
"modelRoleStorage",
|
|
65
|
+
"cycleOrder",
|
|
66
|
+
"enabledModels",
|
|
67
|
+
"defaultThinkingLevel",
|
|
68
|
+
"thinkingBudgets.",
|
|
69
|
+
"hideThinkingBlock",
|
|
70
|
+
"proseOnlyThinking",
|
|
71
|
+
"omitThinking",
|
|
72
|
+
"externalThinking",
|
|
73
|
+
"model.",
|
|
74
|
+
"inlineToolDescriptors",
|
|
75
|
+
"includeModelInPrompt",
|
|
76
|
+
"includeWorkspaceTree",
|
|
77
|
+
"personality",
|
|
78
|
+
"temperature",
|
|
79
|
+
"topP",
|
|
80
|
+
"topK",
|
|
81
|
+
"minP",
|
|
82
|
+
"presencePenalty",
|
|
83
|
+
"repetitionPenalty",
|
|
84
|
+
"textVerbosity",
|
|
85
|
+
"retry.",
|
|
86
|
+
"advisor.",
|
|
87
|
+
"prewalk.",
|
|
88
|
+
"tier.",
|
|
89
|
+
],
|
|
90
|
+
interaction: [
|
|
91
|
+
"autoResume",
|
|
92
|
+
"power.",
|
|
93
|
+
"steeringMode",
|
|
94
|
+
"ask.",
|
|
95
|
+
"stt.",
|
|
96
|
+
"speech.",
|
|
97
|
+
"live.",
|
|
98
|
+
"collab.",
|
|
99
|
+
"magicKeywords",
|
|
100
|
+
"git.",
|
|
101
|
+
],
|
|
102
|
+
context: ["compaction.", "context.", "contextPromotion.", "ttsr.", "recap.", "branchSummary."],
|
|
103
|
+
memory: ["memory.", "memories.", "mnemopi.", "hindsight.", "sharpshooter."],
|
|
104
|
+
files: ["edit.", "read.", "files.", "file.", "lsp.", "tree"],
|
|
105
|
+
shell: ["bash.", "eval.", "shell", "shellMinimizer."],
|
|
106
|
+
tools: [
|
|
107
|
+
"tools.",
|
|
108
|
+
"todo.",
|
|
109
|
+
"glob.",
|
|
110
|
+
"grep.",
|
|
111
|
+
"astGrep.",
|
|
112
|
+
"astEdit.",
|
|
113
|
+
"debug.",
|
|
114
|
+
"launch.",
|
|
115
|
+
"fetch.",
|
|
116
|
+
"vault.",
|
|
117
|
+
"github.",
|
|
118
|
+
"web_search.",
|
|
119
|
+
"browser.",
|
|
120
|
+
"computer.",
|
|
121
|
+
"checkpoint.",
|
|
122
|
+
"async.",
|
|
123
|
+
"irc.",
|
|
124
|
+
"mcp.",
|
|
125
|
+
"secrets.",
|
|
126
|
+
"extensionHandlers.",
|
|
127
|
+
"dev.",
|
|
128
|
+
],
|
|
129
|
+
tasks: [
|
|
130
|
+
"plan.",
|
|
131
|
+
"goal.",
|
|
132
|
+
"task.",
|
|
133
|
+
"tasks.",
|
|
134
|
+
"worktree.",
|
|
135
|
+
"skills.",
|
|
136
|
+
"commands.",
|
|
137
|
+
"extensions",
|
|
138
|
+
"disabledExtensions",
|
|
139
|
+
],
|
|
140
|
+
providers: [
|
|
141
|
+
"providers.",
|
|
142
|
+
"provider.",
|
|
143
|
+
"enabledProviders",
|
|
144
|
+
"disabledProviders",
|
|
145
|
+
"modelProviderOrder",
|
|
146
|
+
"exa.",
|
|
147
|
+
"searxng.",
|
|
148
|
+
"codexResets.",
|
|
149
|
+
],
|
|
150
|
+
general: [],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export function categorizeOmpSetting(path: string): OmpSettingCategory {
|
|
154
|
+
for (const category of OMP_SETTING_CATEGORIES) {
|
|
155
|
+
if (CATEGORY_PREFIXES[category].some((prefix) => path === prefix || path.startsWith(prefix))) {
|
|
156
|
+
return category;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return "general";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function formatOmpSettingLabel(path: string): string {
|
|
163
|
+
const leaf = path.split(".").at(-1) ?? path;
|
|
164
|
+
const words = leaf
|
|
165
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
166
|
+
.replace(/[_-]+/g, " ")
|
|
167
|
+
.trim();
|
|
168
|
+
return words ? `${words[0]?.toUpperCase() ?? ""}${words.slice(1)}` : path;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const listOmpSettings = defineRpc({
|
|
172
|
+
name: "paseo-omp.list-settings",
|
|
173
|
+
input: z
|
|
174
|
+
.object({ store: OmpStoreSchema.optional(), cwd: OmpWorkspaceCwdSchema.optional() })
|
|
175
|
+
.strict(),
|
|
176
|
+
output: z.object({
|
|
177
|
+
catalogVersion: z.literal(OMP_SETTINGS_CATALOG_VERSION),
|
|
178
|
+
revision: z.string().optional(),
|
|
179
|
+
path: z.string().optional(),
|
|
180
|
+
available: z.boolean(),
|
|
181
|
+
droppedCount: z.number().int().nonnegative(),
|
|
182
|
+
settings: z.array(OmpSettingSchema),
|
|
183
|
+
error: z.string().optional(),
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const OmpSettingChangeSchema = z.discriminatedUnion("operation", [
|
|
188
|
+
z.object({ operation: z.literal("set"), path: z.string().min(1), value: OmpScalarValueSchema }),
|
|
189
|
+
z.object({ operation: z.literal("reset"), path: z.string().min(1) }),
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
export const updateOmpSettings = defineRpc({
|
|
193
|
+
name: "paseo-omp.update-settings",
|
|
194
|
+
input: z.object({
|
|
195
|
+
store: OmpStoreSchema.optional(),
|
|
196
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
197
|
+
revision: z.string(),
|
|
198
|
+
changes: z.array(OmpSettingChangeSchema).min(1).max(100),
|
|
199
|
+
}),
|
|
200
|
+
output: z.object({
|
|
201
|
+
conflict: z.boolean(),
|
|
202
|
+
appliedPaths: z.array(z.string()),
|
|
203
|
+
failed: z.object({ path: z.string(), message: z.string() }).optional(),
|
|
204
|
+
catalog: z.object({
|
|
205
|
+
catalogVersion: z.literal(OMP_SETTINGS_CATALOG_VERSION),
|
|
206
|
+
available: z.boolean(),
|
|
207
|
+
revision: z.string().optional(),
|
|
208
|
+
path: z.string().optional(),
|
|
209
|
+
droppedCount: z.number().int().nonnegative(),
|
|
210
|
+
settings: z.array(OmpSettingSchema),
|
|
211
|
+
error: z.string().optional(),
|
|
212
|
+
}),
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const OMP_PROFILE_NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
5
|
+
const WINDOWS_RESERVED_PROFILE = /^(?:CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(?:\..*)?$/iu;
|
|
6
|
+
|
|
7
|
+
export function isOmpProfileName(value: string): boolean {
|
|
8
|
+
return (
|
|
9
|
+
value !== "default" &&
|
|
10
|
+
!value.endsWith(".") &&
|
|
11
|
+
OMP_PROFILE_NAME.test(value) &&
|
|
12
|
+
!WINDOWS_RESERVED_PROFILE.test(value)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const OmpProfileNameSchema = z.string().trim().refine(isOmpProfileName, {
|
|
17
|
+
message: "Invalid named OMP profile",
|
|
18
|
+
});
|
|
19
|
+
export const OmpStoreSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
profile: OmpProfileNameSchema.optional(),
|
|
22
|
+
agentDir: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(4096)
|
|
26
|
+
.refine((value) => !value.includes("\0"))
|
|
27
|
+
// Browser validation accepts absolute paths from any supported server OS;
|
|
28
|
+
// withOmpStore additionally applies node:path.isAbsolute on the server.
|
|
29
|
+
.refine(
|
|
30
|
+
(value) => /^(?:\/|[A-Za-z]:[\\/]|\\)/u.test(value),
|
|
31
|
+
"OMP agent directory must be absolute",
|
|
32
|
+
)
|
|
33
|
+
.optional(),
|
|
34
|
+
})
|
|
35
|
+
.strict()
|
|
36
|
+
.refine(
|
|
37
|
+
(value) => !(value.profile && value.agentDir),
|
|
38
|
+
"Choose a profile or agent directory, not both",
|
|
39
|
+
);
|
|
40
|
+
export type OmpStore = z.infer<typeof OmpStoreSchema>;
|
|
41
|
+
export const listOmpStores = defineRpc({
|
|
42
|
+
name: "paseo-omp.list-stores",
|
|
43
|
+
input: z.object({}).strict(),
|
|
44
|
+
output: z.object({ profiles: z.array(OmpProfileNameSchema).max(128) }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export function storeForProvider(provider: string | undefined): OmpStore | undefined {
|
|
48
|
+
if (!provider?.startsWith("omp-plugin-")) return;
|
|
49
|
+
const profile = provider.slice("omp-plugin-".length);
|
|
50
|
+
return isOmpProfileName(profile) ? { profile } : undefined;
|
|
51
|
+
}
|
|
52
|
+
export function storeLabel(store?: OmpStore): string {
|
|
53
|
+
return store?.profile
|
|
54
|
+
? `Profile: ${store.profile}`
|
|
55
|
+
: store?.agentDir
|
|
56
|
+
? "Custom agent directory"
|
|
57
|
+
: "Daemon default store";
|
|
58
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
5
|
+
|
|
6
|
+
// Health/compatibility facts about the omp CLI itself, surfaced on the global OMP page. This is
|
|
7
|
+
// an explicit allowlist, not a passthrough: filesystem locations are sanitized display labels
|
|
8
|
+
// (`~/...` or the constant `<custom path>`), never raw absolute override values; every remaining
|
|
9
|
+
// field is a classification enum, bounded/normalized number, or boolean derived from a positive
|
|
10
|
+
// documented grammar match. Raw stdout/stderr, environment variables, config file text, and
|
|
11
|
+
// arbitrary provider diagnostics never cross this boundary.
|
|
12
|
+
|
|
13
|
+
/** Outcome of the bounded `omp --version` probe. */
|
|
14
|
+
export const OmpVersionStatusSchema = z.enum([
|
|
15
|
+
"ok",
|
|
16
|
+
"not-found",
|
|
17
|
+
"unrunnable",
|
|
18
|
+
"timeout",
|
|
19
|
+
"probe-failed",
|
|
20
|
+
"malformed",
|
|
21
|
+
]);
|
|
22
|
+
export type OmpVersionStatus = z.infer<typeof OmpVersionStatusSchema>;
|
|
23
|
+
|
|
24
|
+
// Normalized, bounded fields parsed out of one canonical anchored version line
|
|
25
|
+
// (`omp/<major>.<minor>.<patch>[-<prerelease>]`) — never the raw stdout string, so no unbounded
|
|
26
|
+
// build metadata or unrelated text can ride along.
|
|
27
|
+
export const OmpVersionSchema = z.object({
|
|
28
|
+
major: z.number().int().nonnegative(),
|
|
29
|
+
minor: z.number().int().nonnegative(),
|
|
30
|
+
patch: z.number().int().nonnegative(),
|
|
31
|
+
prerelease: z.string().min(1).max(32).nullable(),
|
|
32
|
+
});
|
|
33
|
+
export type OmpVersion = z.infer<typeof OmpVersionSchema>;
|
|
34
|
+
|
|
35
|
+
/** Filesystem classification for a diagnostic root/file: distinguishes every failure mode. */
|
|
36
|
+
export const PathStateSchema = z.enum([
|
|
37
|
+
"available",
|
|
38
|
+
"missing",
|
|
39
|
+
"unreadable",
|
|
40
|
+
"invalid",
|
|
41
|
+
"wrong-type",
|
|
42
|
+
]);
|
|
43
|
+
export type PathState = z.infer<typeof PathStateSchema>;
|
|
44
|
+
|
|
45
|
+
// Mirrors OmpMemorySectionSchema.backend in shared/omp-config.ts. Duplicated as a literal rather
|
|
46
|
+
// than imported so this module's wire contract does not shift silently if that schema changes.
|
|
47
|
+
const MemoryBackendSchema = z.enum(["off", "local", "hindsight", "mnemopi", "sharpshooter"]);
|
|
48
|
+
|
|
49
|
+
export const OmpProcessDiagnosticsSchema = z.object({
|
|
50
|
+
/** "partial" means a project daemon directory or candidate metadata file could not be
|
|
51
|
+
* inspected; the count reflects only entries whose metadata was confirmed. */
|
|
52
|
+
status: z.enum(["ok", "partial", "unavailable", "unknown"]),
|
|
53
|
+
/** Metadata file count under the hub run root, never a count of verified live processes. */
|
|
54
|
+
trackedCount: z.number().int().nonnegative().nullable(),
|
|
55
|
+
/** Counts by recorded state only; optional for compatibility with older plugin hosts. */
|
|
56
|
+
activeCount: z.number().int().nonnegative().nullable().optional(),
|
|
57
|
+
historicalCount: z.number().int().nonnegative().nullable().optional(),
|
|
58
|
+
unknownCount: z.number().int().nonnegative().nullable().optional(),
|
|
59
|
+
});
|
|
60
|
+
export type OmpProcessDiagnostics = z.infer<typeof OmpProcessDiagnosticsSchema>;
|
|
61
|
+
|
|
62
|
+
// Reports safe facts from omp's own mcp.json manifest: bounded server count and parse/access
|
|
63
|
+
// status only. Server names, credentials, headers, env, URLs, and commands never cross the RPC.
|
|
64
|
+
export const OmpMcpDiagnosticsSchema = z.object({
|
|
65
|
+
status: z.enum(["configured", "unavailable", "unreadable", "invalid", "wrong-type"]),
|
|
66
|
+
serverCount: z.number().int().nonnegative().nullable(),
|
|
67
|
+
/** Null only when "configured"; otherwise a specific, path-backed explanation. */
|
|
68
|
+
reason: z.string().nullable(),
|
|
69
|
+
});
|
|
70
|
+
export type OmpMcpDiagnostics = z.infer<typeof OmpMcpDiagnosticsSchema>;
|
|
71
|
+
|
|
72
|
+
export const OmpLspDiagnosticsSchema = z.object({
|
|
73
|
+
status: z.enum(["supported", "not-advertised", "unknown"]),
|
|
74
|
+
});
|
|
75
|
+
export type OmpLspDiagnostics = z.infer<typeof OmpLspDiagnosticsSchema>;
|
|
76
|
+
|
|
77
|
+
export const OmpProviderHealthSchema = z.object({
|
|
78
|
+
binary: z.object({
|
|
79
|
+
/** Whether an executable file was found (env override or PATH), independent of a working
|
|
80
|
+
* `--version`. */
|
|
81
|
+
installed: z.boolean(),
|
|
82
|
+
/** Sanitized display label (`~/...` or `<custom path>`), never a raw absolute path. */
|
|
83
|
+
resolvedPath: z.string().nullable(),
|
|
84
|
+
version: OmpVersionSchema.nullable(),
|
|
85
|
+
versionStatus: OmpVersionStatusSchema,
|
|
86
|
+
/** True when process-tree termination/verification failed or the leader did not close by the
|
|
87
|
+
* bounded final deadline. */
|
|
88
|
+
processCleanupFailed: z.boolean(),
|
|
89
|
+
}),
|
|
90
|
+
rpcUi: z.object({
|
|
91
|
+
/** False when the binary was unavailable, so the probe was never attempted. */
|
|
92
|
+
checked: z.boolean(),
|
|
93
|
+
/** Null when the help probe failed, was empty, or was truncated — never a guessed false. */
|
|
94
|
+
supported: z.boolean().nullable(),
|
|
95
|
+
}),
|
|
96
|
+
lsp: OmpLspDiagnosticsSchema,
|
|
97
|
+
mcp: OmpMcpDiagnosticsSchema,
|
|
98
|
+
process: OmpProcessDiagnosticsSchema,
|
|
99
|
+
roots: z.object({
|
|
100
|
+
agentRoot: z.string(),
|
|
101
|
+
agentRootState: PathStateSchema,
|
|
102
|
+
configPath: z.string(),
|
|
103
|
+
configState: PathStateSchema,
|
|
104
|
+
sessionRoot: z.string(),
|
|
105
|
+
sessionRootState: PathStateSchema,
|
|
106
|
+
}),
|
|
107
|
+
databases: z.object({
|
|
108
|
+
agentDbState: PathStateSchema,
|
|
109
|
+
historyDbState: PathStateSchema,
|
|
110
|
+
}),
|
|
111
|
+
/** Null both when the config is unavailable and when it is available but unset — callers must
|
|
112
|
+
* check `roots.configState` to tell those apart. */
|
|
113
|
+
memoryBackend: MemoryBackendSchema.nullable(),
|
|
114
|
+
checkedAt: z.string(),
|
|
115
|
+
});
|
|
116
|
+
export type OmpProviderHealth = z.infer<typeof OmpProviderHealthSchema>;
|
|
117
|
+
|
|
118
|
+
export const getOmpProviderHealth = defineRpc({
|
|
119
|
+
name: "paseo-omp.get-provider-health",
|
|
120
|
+
input: z.object({
|
|
121
|
+
store: OmpStoreSchema.optional(),
|
|
122
|
+
force: z.boolean().optional(),
|
|
123
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
124
|
+
}),
|
|
125
|
+
output: OmpProviderHealthSchema,
|
|
126
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const MAX_IMAGE_ENCODED_BYTES = 8 * 1024 * 1024;
|
|
4
|
+
const MAX_IMAGE_DECODED_BYTES = 6 * 1024 * 1024;
|
|
5
|
+
const MAX_CUMULATIVE_ENCODED_BYTES = 12 * 1024 * 1024;
|
|
6
|
+
const MAX_CUMULATIVE_DECODED_BYTES = 9 * 1024 * 1024;
|
|
7
|
+
const MAX_IMAGE_TEXT_BYTES = 256 * 1024;
|
|
8
|
+
const MAX_IMAGE_DETAILS_BYTES = 256 * 1024;
|
|
9
|
+
const OMP_IMAGE_CALL_ID =
|
|
10
|
+
/^omp:(?:tool:\d+|assistant:\d+:[A-Za-z0-9_-]+:content:\d+:image|custom:[A-Za-z0-9_-]+):images$/u;
|
|
11
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
12
|
+
const OMP_IMAGE_DIMENSION_NOTE =
|
|
13
|
+
/^\[Image: original \d+x\d+, displayed at \d+x\d+\. Multiply coordinates by \d+(?:\.\d+)? to map to original image\.\]$/u;
|
|
14
|
+
|
|
15
|
+
function utf8Bytes(value: string): number {
|
|
16
|
+
let bytes = 0;
|
|
17
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
18
|
+
const code = value.charCodeAt(index);
|
|
19
|
+
if (code < 0x80) bytes += 1;
|
|
20
|
+
else if (code < 0x800) bytes += 2;
|
|
21
|
+
else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
|
|
22
|
+
const next = value.charCodeAt(index + 1);
|
|
23
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
24
|
+
bytes += 4;
|
|
25
|
+
index += 1;
|
|
26
|
+
} else bytes += 3;
|
|
27
|
+
} else bytes += 3;
|
|
28
|
+
}
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function boundedString(maxBytes: number) {
|
|
33
|
+
return z.string().refine((value) => utf8Bytes(value) <= maxBytes);
|
|
34
|
+
}
|
|
35
|
+
function decodedBase64Length(data: string): number | undefined {
|
|
36
|
+
if (
|
|
37
|
+
data.length === 0 ||
|
|
38
|
+
data.length % 4 !== 0 ||
|
|
39
|
+
data.length > MAX_IMAGE_ENCODED_BYTES ||
|
|
40
|
+
!/^[A-Za-z0-9+/]*={0,2}$/u.test(data)
|
|
41
|
+
) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
45
|
+
const decoded = (data.length / 4) * 3 - padding;
|
|
46
|
+
return decoded <= MAX_IMAGE_DECODED_BYTES ? decoded : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function decodedPrefix(data: string): number[] {
|
|
50
|
+
const bytes: number[] = [];
|
|
51
|
+
for (let offset = 0; offset < data.length && bytes.length < 12; offset += 4) {
|
|
52
|
+
const a = BASE64.indexOf(data[offset] ?? "");
|
|
53
|
+
const b = BASE64.indexOf(data[offset + 1] ?? "");
|
|
54
|
+
const c = data[offset + 2] === "=" ? 0 : BASE64.indexOf(data[offset + 2] ?? "");
|
|
55
|
+
const d = data[offset + 3] === "=" ? 0 : BASE64.indexOf(data[offset + 3] ?? "");
|
|
56
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) return [];
|
|
57
|
+
bytes.push((a << 2) | (b >> 4));
|
|
58
|
+
if (data[offset + 2] !== "=") bytes.push(((b & 15) << 4) | (c >> 2));
|
|
59
|
+
if (data[offset + 3] !== "=") bytes.push(((c & 3) << 6) | d);
|
|
60
|
+
}
|
|
61
|
+
return bytes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasExpectedHeader(mimeType: string, bytes: readonly number[]): boolean {
|
|
65
|
+
if (mimeType === "image/png") {
|
|
66
|
+
return [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every(
|
|
67
|
+
(byte, index) => bytes[index] === byte,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (mimeType === "image/jpeg") {
|
|
71
|
+
return bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;
|
|
72
|
+
}
|
|
73
|
+
if (mimeType === "image/webp") {
|
|
74
|
+
return (
|
|
75
|
+
bytes.length >= 12 &&
|
|
76
|
+
String.fromCharCode(...bytes.slice(0, 4)) === "RIFF" &&
|
|
77
|
+
String.fromCharCode(...bytes.slice(8, 12)) === "WEBP"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return (
|
|
81
|
+
bytes.length >= 6 && String.fromCharCode(...bytes.slice(0, 6)) in { GIF87a: true, GIF89a: true }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ompImageSchema = z.object({
|
|
86
|
+
id: z.string().regex(/^[A-Za-z0-9_-]{16}$/u),
|
|
87
|
+
data: z.string().max(MAX_IMAGE_ENCODED_BYTES),
|
|
88
|
+
mimeType: z.enum(["image/gif", "image/jpeg", "image/png", "image/webp"]),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const ompImageTimelineSchema = z
|
|
92
|
+
.object({
|
|
93
|
+
label: boundedString(256),
|
|
94
|
+
images: z.array(ompImageSchema).min(1).max(64),
|
|
95
|
+
text: boundedString(MAX_IMAGE_TEXT_BYTES).optional(),
|
|
96
|
+
details: z.json().optional(),
|
|
97
|
+
})
|
|
98
|
+
.superRefine((value, context) => {
|
|
99
|
+
let encodedBytes = 0;
|
|
100
|
+
let decodedBytes = 0;
|
|
101
|
+
for (const [index, image] of value.images.entries()) {
|
|
102
|
+
const decoded = decodedBase64Length(image.data);
|
|
103
|
+
if (decoded === undefined || !hasExpectedHeader(image.mimeType, decodedPrefix(image.data))) {
|
|
104
|
+
context.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
path: ["images", index, "data"],
|
|
107
|
+
message: "invalid image payload",
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
encodedBytes += image.data.length;
|
|
112
|
+
decodedBytes += decoded;
|
|
113
|
+
}
|
|
114
|
+
if (
|
|
115
|
+
encodedBytes > MAX_CUMULATIVE_ENCODED_BYTES ||
|
|
116
|
+
decodedBytes > MAX_CUMULATIVE_DECODED_BYTES
|
|
117
|
+
) {
|
|
118
|
+
context.addIssue({ code: "custom", path: ["images"], message: "image payload too large" });
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
value.details !== undefined &&
|
|
122
|
+
utf8Bytes(JSON.stringify(value.details)) > MAX_IMAGE_DETAILS_BYTES
|
|
123
|
+
) {
|
|
124
|
+
context.addIssue({ code: "custom", path: ["details"], message: "image details too large" });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export function visibleOmpImageText(text: string | undefined): string | undefined {
|
|
129
|
+
if (!text) return undefined;
|
|
130
|
+
const visible = text
|
|
131
|
+
.split("\n")
|
|
132
|
+
.filter((line) => !OMP_IMAGE_DIMENSION_NOTE.test(line.trim()))
|
|
133
|
+
.join("\n")
|
|
134
|
+
.trim();
|
|
135
|
+
return visible || undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const ompImageToolMetadataSchema = z.object({
|
|
139
|
+
ompImageOwner: z.literal("omp"),
|
|
140
|
+
ompImage: ompImageTimelineSchema,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export function transformOmpImageToolItem(item: { callId: string; metadata?: unknown }) {
|
|
144
|
+
if (!OMP_IMAGE_CALL_ID.test(item.callId)) return undefined;
|
|
145
|
+
const metadata = ompImageToolMetadataSchema.safeParse(item.metadata);
|
|
146
|
+
if (!metadata.success) return undefined;
|
|
147
|
+
return {
|
|
148
|
+
items: [
|
|
149
|
+
{
|
|
150
|
+
type: "plugin" as const,
|
|
151
|
+
kind: "omp-images",
|
|
152
|
+
id: item.callId,
|
|
153
|
+
version: 1,
|
|
154
|
+
data: metadata.data.ompImage,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type OmpImageTimelineData = z.infer<typeof ompImageTimelineSchema>;
|
package/shared/quota.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
4
|
+
|
|
5
|
+
// omp's usage_history stores recorded_at/resets_at as epoch milliseconds (confirmed against
|
|
6
|
+
// live rows: 13-digit values), not seconds. Any "time remaining" math must diff against
|
|
7
|
+
// Date.now() directly, never Date.now() / 1000.
|
|
8
|
+
export const OmpQuotaSchema = z.object({
|
|
9
|
+
provider: z.string(),
|
|
10
|
+
label: z.string(),
|
|
11
|
+
windowLabel: z.string().nullable(),
|
|
12
|
+
usedFraction: z.number().min(0).nullable(),
|
|
13
|
+
status: z.string().nullable(),
|
|
14
|
+
resetsAt: z.number().int().nullable(),
|
|
15
|
+
recordedAt: z.number().int(),
|
|
16
|
+
});
|
|
17
|
+
export type OmpQuota = z.infer<typeof OmpQuotaSchema>;
|
|
18
|
+
|
|
19
|
+
export const listOmpQuotas = defineRpc({
|
|
20
|
+
name: "paseo-omp.list-quotas",
|
|
21
|
+
input: z.object({ store: OmpStoreSchema.optional() }),
|
|
22
|
+
output: z.object({ quotas: z.array(OmpQuotaSchema) }),
|
|
23
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
4
|
+
|
|
5
|
+
const CwdSchema = z.string().min(1).max(4_096);
|
|
6
|
+
|
|
7
|
+
// omp's history.db stores created_at as epoch seconds (confirmed against live rows: 10-digit
|
|
8
|
+
// values), unlike agent.db's usage_history which stores milliseconds. Diff against
|
|
9
|
+
// Date.now() / 1_000, never Date.now() directly.
|
|
10
|
+
export const OmpSessionEntrySchema = z.object({
|
|
11
|
+
id: z.number().int(),
|
|
12
|
+
sessionId: z.string().nullable(),
|
|
13
|
+
title: z.string().nullable(),
|
|
14
|
+
prompt: z.string(),
|
|
15
|
+
truncated: z.boolean(),
|
|
16
|
+
createdAt: z.number().int(),
|
|
17
|
+
});
|
|
18
|
+
export type OmpSessionEntry = z.infer<typeof OmpSessionEntrySchema>;
|
|
19
|
+
|
|
20
|
+
export const listOmpSessions = defineRpc({
|
|
21
|
+
name: "paseo-omp.list-sessions",
|
|
22
|
+
input: z.object({ store: OmpStoreSchema.optional(), cwd: CwdSchema }),
|
|
23
|
+
output: z.object({ sessions: z.array(OmpSessionEntrySchema) }),
|
|
24
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
7
|
+
"types": ["node", "vitest/globals"],
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["**/*.ts", "**/*.tsx"]
|
|
16
|
+
}
|