@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
package/index.client.tsx
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import type { PaseoAgentListResult, PaseoApi } from "@getpaseo/client";
|
|
2
|
+
import { settingsRpc } from "@getpaseo/plugin";
|
|
3
|
+
import type {
|
|
4
|
+
PluginButtonRegistration,
|
|
5
|
+
PluginClientContext,
|
|
6
|
+
PluginSurfaceProps,
|
|
7
|
+
} from "@getpaseo/plugin/client";
|
|
8
|
+
import { OmpIcon } from "./client/hub-icon";
|
|
9
|
+
import { HubPopover } from "./client/hub-popover";
|
|
10
|
+
import { summarizeHubProcesses } from "./client/hub-status";
|
|
11
|
+
import { OmpMcpAuthorizationCard } from "./client/mcp-authorization";
|
|
12
|
+
import { McpPopover } from "./client/mcp-popover";
|
|
13
|
+
import { OmpMemoryPanel } from "./client/memory-panel";
|
|
14
|
+
import { MemoryPopover } from "./client/memory-popover";
|
|
15
|
+
import { OmpConfigSurface, OmpWorkspacePanel } from "./client/omp-config-surface";
|
|
16
|
+
import {
|
|
17
|
+
createStoreQuotaLoader,
|
|
18
|
+
isOmpPluginProvider,
|
|
19
|
+
isOmpProvider,
|
|
20
|
+
ompStoreKey,
|
|
21
|
+
} from "./client/omp-store-state";
|
|
22
|
+
import { quotaProviderIcon } from "./client/provider-icon";
|
|
23
|
+
import { OmpImageTimeline } from "./client/provider-image";
|
|
24
|
+
import { QuotaPopover } from "./client/quota-popover";
|
|
25
|
+
import {
|
|
26
|
+
type QuotaSeverity,
|
|
27
|
+
quotaProviderFromSession,
|
|
28
|
+
quotaSeverityForProvider,
|
|
29
|
+
quotaSummaryForProvider,
|
|
30
|
+
} from "./client/quota-state";
|
|
31
|
+
import { SessionsPopover } from "./client/sessions-popover";
|
|
32
|
+
import {
|
|
33
|
+
type ComposerPillSettings,
|
|
34
|
+
composerPillSettings,
|
|
35
|
+
composerPillSettingsSchema,
|
|
36
|
+
} from "./shared/composer-pill-settings";
|
|
37
|
+
import { listHubProcesses } from "./shared/hub";
|
|
38
|
+
import { OMP_MCP_AUTH_TIMELINE_KIND, ompMcpAuthorizationTimelineSchema } from "./shared/mcp";
|
|
39
|
+
import { type OmpStore, storeForProvider } from "./shared/omp-store";
|
|
40
|
+
import { ompImageTimelineSchema, transformOmpImageToolItem } from "./shared/provider-image";
|
|
41
|
+
import { listOmpQuotas } from "./shared/quota";
|
|
42
|
+
|
|
43
|
+
const PAGE_LIMIT = 200;
|
|
44
|
+
const MAX_PAGES = 10;
|
|
45
|
+
const STATUS_POLL_MS = 4_000;
|
|
46
|
+
const QUOTA_POLL_MS = 30_000;
|
|
47
|
+
const RECONCILE_DEBOUNCE_MS = 250;
|
|
48
|
+
const SETTINGS_POLL_MS = 15_000;
|
|
49
|
+
const composerPillSettingsRpc = settingsRpc(composerPillSettings.id);
|
|
50
|
+
|
|
51
|
+
type AgentEntry = PaseoAgentListResult["entries"][number];
|
|
52
|
+
|
|
53
|
+
type PillEntry = {
|
|
54
|
+
store?: OmpStore;
|
|
55
|
+
cwd: string;
|
|
56
|
+
provider: string;
|
|
57
|
+
workspaceId: string;
|
|
58
|
+
quotaProvider: string | null;
|
|
59
|
+
quotaSeverity: QuotaSeverity;
|
|
60
|
+
hub?: PluginButtonRegistration;
|
|
61
|
+
memory?: PluginButtonRegistration;
|
|
62
|
+
sessions?: PluginButtonRegistration;
|
|
63
|
+
quota?: PluginButtonRegistration;
|
|
64
|
+
mcp?: PluginButtonRegistration;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function samePillSettings(
|
|
68
|
+
left: ComposerPillSettings | undefined,
|
|
69
|
+
right: ComposerPillSettings,
|
|
70
|
+
): boolean {
|
|
71
|
+
return (
|
|
72
|
+
left?.mcp === right.mcp &&
|
|
73
|
+
left.hub === right.hub &&
|
|
74
|
+
left.memory === right.memory &&
|
|
75
|
+
left.sessions === right.sessions &&
|
|
76
|
+
left.quota === right.quota
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function removePills(entry: PillEntry): void {
|
|
81
|
+
entry.hub?.remove();
|
|
82
|
+
entry.memory?.remove();
|
|
83
|
+
entry.sessions?.remove();
|
|
84
|
+
entry.quota?.remove();
|
|
85
|
+
entry.mcp?.remove();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadAgents(paseo: PaseoApi): Promise<AgentEntry[]> {
|
|
89
|
+
const entries: AgentEntry[] = [];
|
|
90
|
+
let cursor: string | undefined;
|
|
91
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
92
|
+
const result = await paseo.agents.list({
|
|
93
|
+
sort: [{ key: "updated_at", direction: "desc" }],
|
|
94
|
+
page: { limit: PAGE_LIMIT, ...(cursor ? { cursor } : {}) },
|
|
95
|
+
});
|
|
96
|
+
entries.push(...result.entries);
|
|
97
|
+
cursor = result.pageInfo.hasMore ? (result.pageInfo.nextCursor ?? undefined) : undefined;
|
|
98
|
+
if (!cursor) break;
|
|
99
|
+
}
|
|
100
|
+
return entries;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default function contribute(client: PluginClientContext) {
|
|
104
|
+
const pills = new Map<string, PillEntry>();
|
|
105
|
+
let preferences: ComposerPillSettings | undefined;
|
|
106
|
+
let disposed = false;
|
|
107
|
+
let reconcileTimer: ReturnType<typeof setTimeout> | undefined;
|
|
108
|
+
let reconcileRunning = false;
|
|
109
|
+
let reconcileQueued = false;
|
|
110
|
+
let hubRefreshRunning = false;
|
|
111
|
+
let quotaRefreshRunning = false;
|
|
112
|
+
let settingsReadRunning = false;
|
|
113
|
+
let settingsGeneration = 0;
|
|
114
|
+
|
|
115
|
+
function applyComposerPillSettings(next: ComposerPillSettings): void {
|
|
116
|
+
if (samePillSettings(preferences, next)) return;
|
|
117
|
+
preferences = next;
|
|
118
|
+
settingsGeneration += 1;
|
|
119
|
+
scheduleReconcile();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ConfigSurface(props: PluginSurfaceProps) {
|
|
123
|
+
return <OmpConfigSurface {...props} onComposerPillSettingsChange={applyComposerPillSettings} />;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const removeMemoryPanel = client.addWorkspacePanel({
|
|
127
|
+
id: "memory",
|
|
128
|
+
title: "OMP Memory",
|
|
129
|
+
icon: "Brain",
|
|
130
|
+
context: "workspace",
|
|
131
|
+
locations: ["explorer"],
|
|
132
|
+
Component: OmpMemoryPanel,
|
|
133
|
+
});
|
|
134
|
+
const removeOpenMemory = client.addCommandCenterItem({
|
|
135
|
+
id: "open-memory",
|
|
136
|
+
title: "Open OMP Memory",
|
|
137
|
+
icon: "Brain",
|
|
138
|
+
keywords: ["omp", "memory", "facts", "recall"],
|
|
139
|
+
context: "workspace",
|
|
140
|
+
onSelect({ openPanel }) {
|
|
141
|
+
openPanel("memory", { location: "explorer" });
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
const removeWorkspacePanel = client.addWorkspacePanel({
|
|
145
|
+
id: "workspace",
|
|
146
|
+
title: "OMP",
|
|
147
|
+
icon: "Settings",
|
|
148
|
+
context: "workspace",
|
|
149
|
+
locations: ["workspace", "explorer"],
|
|
150
|
+
Component: OmpWorkspacePanel,
|
|
151
|
+
});
|
|
152
|
+
const removeOpenWorkspace = client.addCommandCenterItem({
|
|
153
|
+
id: "open-workspace",
|
|
154
|
+
title: "Open Workspace OMP",
|
|
155
|
+
icon: "Settings",
|
|
156
|
+
keywords: ["omp", "workspace", "config", "plugins", "diagnostics"],
|
|
157
|
+
context: "workspace",
|
|
158
|
+
onSelect({ openPanel }) {
|
|
159
|
+
openPanel("workspace", { location: "workspace" });
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
const removeConfigSurface = client.addSurface("config", ConfigSurface);
|
|
163
|
+
const removeConfigSidebarItem = client.addSidebarItem({
|
|
164
|
+
id: "config",
|
|
165
|
+
title: "OMP",
|
|
166
|
+
icon: "Settings",
|
|
167
|
+
surface: "config",
|
|
168
|
+
});
|
|
169
|
+
const removeOpenConfig = client.addCommandCenterItem({
|
|
170
|
+
id: "open-config",
|
|
171
|
+
title: "Open OMP",
|
|
172
|
+
icon: "Settings",
|
|
173
|
+
keywords: ["omp", "config", "settings", "models", "providers", "composer", "pills"],
|
|
174
|
+
context: "global",
|
|
175
|
+
onSelect({ openSurface }) {
|
|
176
|
+
openSurface("config");
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const removeImageRenderer = client.addTimelineRenderer({
|
|
180
|
+
kind: "omp-images",
|
|
181
|
+
version: 1,
|
|
182
|
+
schema: ompImageTimelineSchema,
|
|
183
|
+
Component: OmpImageTimeline,
|
|
184
|
+
});
|
|
185
|
+
const removeMcpAuthorizationRenderer = client.addTimelineRenderer({
|
|
186
|
+
kind: OMP_MCP_AUTH_TIMELINE_KIND,
|
|
187
|
+
version: 1,
|
|
188
|
+
schema: ompMcpAuthorizationTimelineSchema,
|
|
189
|
+
Component: OmpMcpAuthorizationCard,
|
|
190
|
+
});
|
|
191
|
+
const removeImageTransformer = client.addTimelineTransformer({
|
|
192
|
+
id: "omp-images",
|
|
193
|
+
query: { itemType: "tool_call" },
|
|
194
|
+
transform({ item }) {
|
|
195
|
+
return transformOmpImageToolItem(item);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
const loadStoreQuotas = createStoreQuotaLoader(
|
|
199
|
+
(input) => client.rpc(listOmpQuotas, input),
|
|
200
|
+
QUOTA_POLL_MS,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
function syncAgentPills(entry: PillEntry, agent: AgentEntry["agent"]): void {
|
|
204
|
+
const settings = preferences;
|
|
205
|
+
if (!settings) return;
|
|
206
|
+
|
|
207
|
+
if (settings.mcp && isOmpPluginProvider(agent.provider)) {
|
|
208
|
+
entry.mcp ??= client.addComposerPill({
|
|
209
|
+
id: "mcp",
|
|
210
|
+
workspaceId: entry.workspaceId,
|
|
211
|
+
agentId: agent.id,
|
|
212
|
+
button: {
|
|
213
|
+
title: "Manage OMP MCP servers",
|
|
214
|
+
icon: "Plug",
|
|
215
|
+
label: "MCP",
|
|
216
|
+
behavior: { kind: "popover", Content: McpPopover },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
entry.mcp?.remove();
|
|
221
|
+
entry.mcp = undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (settings.hub) {
|
|
225
|
+
entry.hub ??= client.addComposerPill({
|
|
226
|
+
id: "hub",
|
|
227
|
+
workspaceId: entry.workspaceId,
|
|
228
|
+
agentId: agent.id,
|
|
229
|
+
button: {
|
|
230
|
+
title: "Hub processes",
|
|
231
|
+
icon: OmpIcon,
|
|
232
|
+
label: "Hub",
|
|
233
|
+
visible: false,
|
|
234
|
+
behavior: { kind: "popover", Content: HubPopover },
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
} else {
|
|
238
|
+
entry.hub?.remove();
|
|
239
|
+
entry.hub = undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (settings.memory) {
|
|
243
|
+
entry.memory ??= client.addComposerPill({
|
|
244
|
+
id: "memory",
|
|
245
|
+
workspaceId: entry.workspaceId,
|
|
246
|
+
agentId: agent.id,
|
|
247
|
+
button: {
|
|
248
|
+
title: "OMP workspace memory",
|
|
249
|
+
icon: "Brain",
|
|
250
|
+
label: "Memory",
|
|
251
|
+
behavior: { kind: "popover", Content: MemoryPopover },
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
entry.memory?.remove();
|
|
256
|
+
entry.memory = undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (settings.sessions) {
|
|
260
|
+
entry.sessions ??= client.addComposerPill({
|
|
261
|
+
id: "sessions",
|
|
262
|
+
workspaceId: entry.workspaceId,
|
|
263
|
+
agentId: agent.id,
|
|
264
|
+
button: {
|
|
265
|
+
title: "OMP session history",
|
|
266
|
+
icon: "History",
|
|
267
|
+
label: "Sessions",
|
|
268
|
+
behavior: { kind: "popover", Content: SessionsPopover },
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
entry.sessions?.remove();
|
|
273
|
+
entry.sessions = undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (settings.quota) {
|
|
277
|
+
entry.quota ??= client.addComposerPill({
|
|
278
|
+
id: "quota",
|
|
279
|
+
workspaceId: entry.workspaceId,
|
|
280
|
+
agentId: agent.id,
|
|
281
|
+
button: {
|
|
282
|
+
title: "OMP provider quotas",
|
|
283
|
+
icon: quotaProviderIcon(entry.quotaProvider, "unknown"),
|
|
284
|
+
label: "Quota",
|
|
285
|
+
visible: false,
|
|
286
|
+
behavior: { kind: "popover", Content: QuotaPopover },
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
entry.quota?.remove();
|
|
291
|
+
entry.quota = undefined;
|
|
292
|
+
entry.quotaSeverity = "unknown";
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function reconcileOnce(): Promise<void> {
|
|
297
|
+
if (!preferences) return;
|
|
298
|
+
const agents = await loadAgents(client.paseo);
|
|
299
|
+
if (disposed || !preferences) return;
|
|
300
|
+
const activeIds = new Set<string>();
|
|
301
|
+
for (const { agent } of agents) {
|
|
302
|
+
if (agent.archivedAt || !agent.workspaceId || !agent.cwd) continue;
|
|
303
|
+
activeIds.add(agent.id);
|
|
304
|
+
const quotaProvider = quotaProviderFromSession(agent.provider, agent.model);
|
|
305
|
+
const store = storeForProvider(agent.provider);
|
|
306
|
+
let entry = pills.get(agent.id);
|
|
307
|
+
if (
|
|
308
|
+
!entry ||
|
|
309
|
+
entry.cwd !== agent.cwd ||
|
|
310
|
+
entry.provider !== agent.provider ||
|
|
311
|
+
entry.workspaceId !== agent.workspaceId ||
|
|
312
|
+
entry.quotaProvider !== quotaProvider ||
|
|
313
|
+
ompStoreKey(entry.store) !== ompStoreKey(store)
|
|
314
|
+
) {
|
|
315
|
+
if (entry) removePills(entry);
|
|
316
|
+
entry = {
|
|
317
|
+
store,
|
|
318
|
+
cwd: agent.cwd,
|
|
319
|
+
provider: agent.provider,
|
|
320
|
+
workspaceId: agent.workspaceId,
|
|
321
|
+
quotaProvider,
|
|
322
|
+
quotaSeverity: "unknown",
|
|
323
|
+
};
|
|
324
|
+
pills.set(agent.id, entry);
|
|
325
|
+
}
|
|
326
|
+
syncAgentPills(entry, agent);
|
|
327
|
+
}
|
|
328
|
+
for (const [agentId, entry] of pills) {
|
|
329
|
+
if (activeIds.has(agentId)) continue;
|
|
330
|
+
removePills(entry);
|
|
331
|
+
pills.delete(agentId);
|
|
332
|
+
}
|
|
333
|
+
await Promise.all([refreshHubStatus(), refreshQuotaStatus()]);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function reconcile(): Promise<void> {
|
|
337
|
+
if (reconcileRunning) {
|
|
338
|
+
reconcileQueued = true;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
reconcileRunning = true;
|
|
342
|
+
try {
|
|
343
|
+
do {
|
|
344
|
+
reconcileQueued = false;
|
|
345
|
+
await reconcileOnce();
|
|
346
|
+
} while (reconcileQueued && !disposed);
|
|
347
|
+
} finally {
|
|
348
|
+
reconcileRunning = false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function refreshHubStatus(): Promise<void> {
|
|
353
|
+
if (hubRefreshRunning || !preferences?.hub) return;
|
|
354
|
+
const pillsByCwd = new Map<
|
|
355
|
+
string,
|
|
356
|
+
Array<{ agentId: string; entry: PillEntry; handle: PluginButtonRegistration }>
|
|
357
|
+
>();
|
|
358
|
+
for (const [agentId, entry] of pills) {
|
|
359
|
+
if (!entry.hub) continue;
|
|
360
|
+
const target = { agentId, entry, handle: entry.hub };
|
|
361
|
+
const group = pillsByCwd.get(entry.cwd);
|
|
362
|
+
if (group) group.push(target);
|
|
363
|
+
else pillsByCwd.set(entry.cwd, [target]);
|
|
364
|
+
}
|
|
365
|
+
if (pillsByCwd.size === 0) return;
|
|
366
|
+
hubRefreshRunning = true;
|
|
367
|
+
try {
|
|
368
|
+
await Promise.all(
|
|
369
|
+
[...pillsByCwd].map(async ([cwd, targets]) => {
|
|
370
|
+
try {
|
|
371
|
+
const result = await client.rpc(listHubProcesses, { cwd });
|
|
372
|
+
if (disposed || !preferences?.hub) return;
|
|
373
|
+
const summary = summarizeHubProcesses(result.processes);
|
|
374
|
+
for (const { agentId, entry, handle } of targets) {
|
|
375
|
+
const current = pills.get(agentId);
|
|
376
|
+
if (current === entry && current.hub === handle) handle.update(summary);
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
// Preserve the last known state through temporary host and workspace failures.
|
|
380
|
+
}
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
} finally {
|
|
384
|
+
hubRefreshRunning = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function refreshQuotaStatus(): Promise<void> {
|
|
389
|
+
if (quotaRefreshRunning || !preferences?.quota) return;
|
|
390
|
+
const groups = new Map<
|
|
391
|
+
string,
|
|
392
|
+
Array<{ agentId: string; entry: PillEntry; handle: PluginButtonRegistration }>
|
|
393
|
+
>();
|
|
394
|
+
for (const [agentId, entry] of pills) {
|
|
395
|
+
if (!entry.quota || !isOmpProvider(entry.provider)) continue;
|
|
396
|
+
const target = { agentId, entry, handle: entry.quota };
|
|
397
|
+
const key = ompStoreKey(entry.store);
|
|
398
|
+
const group = groups.get(key);
|
|
399
|
+
if (group) group.push(target);
|
|
400
|
+
else groups.set(key, [target]);
|
|
401
|
+
}
|
|
402
|
+
if (groups.size === 0) return;
|
|
403
|
+
quotaRefreshRunning = true;
|
|
404
|
+
try {
|
|
405
|
+
await Promise.all(
|
|
406
|
+
[...groups.values()].map(async (targets) => {
|
|
407
|
+
try {
|
|
408
|
+
const result = await loadStoreQuotas(targets[0].entry.store);
|
|
409
|
+
if (disposed || !preferences?.quota) return;
|
|
410
|
+
for (const { agentId, entry, handle } of targets) {
|
|
411
|
+
const current = pills.get(agentId);
|
|
412
|
+
if (current !== entry || current.quota !== handle) continue;
|
|
413
|
+
const severity = quotaSeverityForProvider(result.quotas, entry.quotaProvider, true);
|
|
414
|
+
handle.update({
|
|
415
|
+
...quotaSummaryForProvider(result.quotas, entry.quotaProvider, true),
|
|
416
|
+
...(severity === entry.quotaSeverity
|
|
417
|
+
? {}
|
|
418
|
+
: { icon: quotaProviderIcon(entry.quotaProvider, severity) }),
|
|
419
|
+
});
|
|
420
|
+
entry.quotaSeverity = severity;
|
|
421
|
+
}
|
|
422
|
+
} catch {
|
|
423
|
+
// Retain only this store's last result. Failure never falls back to another profile.
|
|
424
|
+
}
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
427
|
+
} finally {
|
|
428
|
+
quotaRefreshRunning = false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function refreshComposerPillSettings(): Promise<void> {
|
|
433
|
+
if (settingsReadRunning || disposed) return;
|
|
434
|
+
settingsReadRunning = true;
|
|
435
|
+
const generation = settingsGeneration;
|
|
436
|
+
try {
|
|
437
|
+
const result = await client.rpc(composerPillSettingsRpc.read, {});
|
|
438
|
+
if (disposed || generation !== settingsGeneration || result.status !== "ready") return;
|
|
439
|
+
const parsed = composerPillSettingsSchema.safeParse(result.values);
|
|
440
|
+
if (parsed.success) applyComposerPillSettings(parsed.data);
|
|
441
|
+
} catch {
|
|
442
|
+
// Keep the last known preferences. The sidebar exposes read and validation failures.
|
|
443
|
+
} finally {
|
|
444
|
+
settingsReadRunning = false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function scheduleReconcile(): void {
|
|
449
|
+
clearTimeout(reconcileTimer);
|
|
450
|
+
reconcileTimer = setTimeout(() => {
|
|
451
|
+
void reconcile().catch(() => {});
|
|
452
|
+
}, RECONCILE_DEBOUNCE_MS);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const unsubscribeAgents = client.paseo.agents.subscribe(scheduleReconcile);
|
|
456
|
+
const hubPoll = setInterval(() => {
|
|
457
|
+
void refreshHubStatus();
|
|
458
|
+
}, STATUS_POLL_MS);
|
|
459
|
+
const quotaPoll = setInterval(() => {
|
|
460
|
+
void refreshQuotaStatus();
|
|
461
|
+
}, QUOTA_POLL_MS);
|
|
462
|
+
const settingsPoll = setInterval(() => {
|
|
463
|
+
void refreshComposerPillSettings();
|
|
464
|
+
}, SETTINGS_POLL_MS);
|
|
465
|
+
void refreshComposerPillSettings();
|
|
466
|
+
|
|
467
|
+
return () => {
|
|
468
|
+
disposed = true;
|
|
469
|
+
settingsGeneration += 1;
|
|
470
|
+
unsubscribeAgents();
|
|
471
|
+
clearTimeout(reconcileTimer);
|
|
472
|
+
clearInterval(hubPoll);
|
|
473
|
+
clearInterval(quotaPoll);
|
|
474
|
+
clearInterval(settingsPoll);
|
|
475
|
+
for (const entry of pills.values()) removePills(entry);
|
|
476
|
+
pills.clear();
|
|
477
|
+
removeImageRenderer();
|
|
478
|
+
removeMcpAuthorizationRenderer();
|
|
479
|
+
removeImageTransformer();
|
|
480
|
+
removeOpenConfig();
|
|
481
|
+
removeConfigSidebarItem();
|
|
482
|
+
removeConfigSurface();
|
|
483
|
+
removeOpenWorkspace();
|
|
484
|
+
removeWorkspacePanel();
|
|
485
|
+
removeOpenMemory();
|
|
486
|
+
removeMemoryPanel();
|
|
487
|
+
};
|
|
488
|
+
}
|
package/index.server.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
|
+
import { resolveListHubProcesses, resolveTailHubLog } from "./server/hub";
|
|
3
|
+
import {
|
|
4
|
+
OmpBrowserAuthorizationRegistry,
|
|
5
|
+
resolveOpenOmpMcpAuthorizationInPaseoBrowser,
|
|
6
|
+
} from "./server/mcp-browser";
|
|
7
|
+
import { resolveListOmpMemory } from "./server/memory";
|
|
8
|
+
import { resolveListOmpConfig } from "./server/omp-config";
|
|
9
|
+
import {
|
|
10
|
+
resolveInspectOmpPluginConfig,
|
|
11
|
+
resolveListOmpPlugins,
|
|
12
|
+
resolveMutateOmpPlugin,
|
|
13
|
+
resolveMutateOmpPluginConfig,
|
|
14
|
+
} from "./server/omp-plugins";
|
|
15
|
+
import { resolveListOmpSettings, resolveUpdateOmpSettings } from "./server/omp-settings";
|
|
16
|
+
import { withOmpStore } from "./server/paths";
|
|
17
|
+
import { withOmpWorkspaceIdentity } from "./server/provider/host-tools";
|
|
18
|
+
import {
|
|
19
|
+
createProfileOmpProvider,
|
|
20
|
+
discoverOmpProfiles,
|
|
21
|
+
discoverOmpProfilesSync,
|
|
22
|
+
} from "./server/provider/profile-providers";
|
|
23
|
+
import { createOmpProvider } from "./server/provider/registration";
|
|
24
|
+
import { resolveGetOmpProviderHealth } from "./server/provider-diagnostics";
|
|
25
|
+
import { resolveListOmpQuotas } from "./server/quota";
|
|
26
|
+
import { resolveListOmpSessions } from "./server/sessions";
|
|
27
|
+
import { composerPillSettings } from "./shared/composer-pill-settings";
|
|
28
|
+
import { listHubProcesses, tailHubLog } from "./shared/hub";
|
|
29
|
+
import { openOmpMcpAuthorizationInPaseoBrowser } from "./shared/mcp";
|
|
30
|
+
import { listOmpMemory } from "./shared/memory";
|
|
31
|
+
import { listOmpConfig } from "./shared/omp-config";
|
|
32
|
+
import {
|
|
33
|
+
inspectOmpPluginConfig,
|
|
34
|
+
listOmpPlugins,
|
|
35
|
+
mutateOmpPlugin,
|
|
36
|
+
mutateOmpPluginConfig,
|
|
37
|
+
} from "./shared/omp-plugins";
|
|
38
|
+
import { listOmpSettings, updateOmpSettings } from "./shared/omp-settings";
|
|
39
|
+
import { listOmpStores, type OmpStore } from "./shared/omp-store";
|
|
40
|
+
import { getOmpProviderHealth } from "./shared/provider-diagnostics";
|
|
41
|
+
import { listOmpQuotas } from "./shared/quota";
|
|
42
|
+
import { listOmpSessions } from "./shared/sessions";
|
|
43
|
+
|
|
44
|
+
function scoped<T extends { store?: OmpStore }, R>(handler: (input: T) => R) {
|
|
45
|
+
return (input: T): R => withOmpStore(input.store, () => handler(input));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default function contribute(server: PluginServerContext) {
|
|
49
|
+
server.registerSettings(composerPillSettings);
|
|
50
|
+
const browserAuthorizationRegistry = new OmpBrowserAuthorizationRegistry();
|
|
51
|
+
const profiles = discoverOmpProfilesSync();
|
|
52
|
+
server.handle(listOmpStores, async () => ({ profiles: await discoverOmpProfiles() }));
|
|
53
|
+
for (const profile of profiles) {
|
|
54
|
+
server.registerProvider(createProfileOmpProvider(profile, { browserAuthorizationRegistry }));
|
|
55
|
+
}
|
|
56
|
+
server.handle(listHubProcesses, resolveListHubProcesses);
|
|
57
|
+
server.handle(tailHubLog, resolveTailHubLog);
|
|
58
|
+
server.handle(listOmpQuotas, scoped(resolveListOmpQuotas));
|
|
59
|
+
server.handle(listOmpMemory, scoped(resolveListOmpMemory));
|
|
60
|
+
server.handle(listOmpSessions, scoped(resolveListOmpSessions));
|
|
61
|
+
server.handle(listOmpConfig, scoped(resolveListOmpConfig));
|
|
62
|
+
server.handle(listOmpPlugins, scoped(resolveListOmpPlugins));
|
|
63
|
+
server.handle(inspectOmpPluginConfig, scoped(resolveInspectOmpPluginConfig));
|
|
64
|
+
server.handle(mutateOmpPlugin, scoped(resolveMutateOmpPlugin));
|
|
65
|
+
server.handle(mutateOmpPluginConfig, scoped(resolveMutateOmpPluginConfig));
|
|
66
|
+
server.handle(listOmpSettings, scoped(resolveListOmpSettings));
|
|
67
|
+
server.handle(updateOmpSettings, scoped(resolveUpdateOmpSettings));
|
|
68
|
+
server.handle(getOmpProviderHealth, scoped(resolveGetOmpProviderHealth));
|
|
69
|
+
server.handle(openOmpMcpAuthorizationInPaseoBrowser, (input) =>
|
|
70
|
+
resolveOpenOmpMcpAuthorizationInPaseoBrowser(input, browserAuthorizationRegistry),
|
|
71
|
+
);
|
|
72
|
+
const removeIdentityHook = server.before("agent.session_open", ({ request }) => {
|
|
73
|
+
if (request.provider !== "omp-plugin" && !request.provider.startsWith("omp-plugin-")) return;
|
|
74
|
+
return withOmpWorkspaceIdentity(request);
|
|
75
|
+
});
|
|
76
|
+
server.registerProvider(createOmpProvider({ browserAuthorizationRegistry }));
|
|
77
|
+
return () => {
|
|
78
|
+
browserAuthorizationRegistry.clear();
|
|
79
|
+
removeIdentityHook();
|
|
80
|
+
};
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omercnet/paseo-omp",
|
|
3
|
+
"version": "0.2.1-next.72.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/omercnet/paseo-plugins.git",
|
|
10
|
+
"directory": "paseo-omp"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/omercnet/paseo-plugins/tree/main/paseo-omp#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/omercnet/paseo-plugins/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"packageManager": "npm@10.9.4",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22.18.0"
|
|
22
|
+
},
|
|
23
|
+
"author": "Omer Cohen",
|
|
24
|
+
"keywords": [
|
|
25
|
+
"paseo",
|
|
26
|
+
"paseo-plugin",
|
|
27
|
+
"paseo-omp",
|
|
28
|
+
"omp",
|
|
29
|
+
"coding-agents"
|
|
30
|
+
],
|
|
31
|
+
"files": [
|
|
32
|
+
"package-lock.json",
|
|
33
|
+
"CHANGELOG.md",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md",
|
|
36
|
+
"SUPPORT.md",
|
|
37
|
+
"TESTING.md",
|
|
38
|
+
"docs",
|
|
39
|
+
"scripts/prepare-dependencies.mjs",
|
|
40
|
+
"index.client.tsx",
|
|
41
|
+
"index.server.ts",
|
|
42
|
+
"client",
|
|
43
|
+
"server",
|
|
44
|
+
"shared",
|
|
45
|
+
"paseo-plugin.json",
|
|
46
|
+
"tsconfig.json"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"check": "biome check .",
|
|
50
|
+
"check:write": "biome check --write .",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:coverage": "PASEO_OMP_COVERAGE=1 vitest run --coverage",
|
|
53
|
+
"test:integration:docker": "node --import tsx scripts/test-docker-host-tools.ts",
|
|
54
|
+
"test:integration:wsl": "node --import tsx scripts/test-wsl-host-tools.ts",
|
|
55
|
+
"test:integration:canary": "node --import tsx scripts/test-canary.ts",
|
|
56
|
+
"test:integration:install": "node --import tsx scripts/test-package-install.ts",
|
|
57
|
+
"typecheck": "tsc --noEmit"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
61
|
+
"yaml": "^2.9.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@biomejs/biome": "^2.5.10",
|
|
65
|
+
"@getpaseo/cli": "0.8.0",
|
|
66
|
+
"@getpaseo/client": "0.8.0",
|
|
67
|
+
"@getpaseo/plugin": "0.8.0",
|
|
68
|
+
"@getpaseo/protocol": "0.8.0",
|
|
69
|
+
"@tanstack/react-query": "^5.102.3",
|
|
70
|
+
"@types/node": "^24.10.1",
|
|
71
|
+
"@types/react": "^19.2.18",
|
|
72
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
73
|
+
"esbuild": "^0.28.0",
|
|
74
|
+
"react": "19.1.0",
|
|
75
|
+
"react-native": "0.81.5",
|
|
76
|
+
"tsx": "^4.20.6",
|
|
77
|
+
"typescript": "^7.0.2",
|
|
78
|
+
"vitest": "^5.0.0",
|
|
79
|
+
"zod": "^4.4.3"
|
|
80
|
+
},
|
|
81
|
+
"overrides": {
|
|
82
|
+
"qs": "6.16.0"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
|
|
8
|
+
export function prepareDependencies(root = projectRoot, execute = execFileSync) {
|
|
9
|
+
if (!existsSync(join(root, "package-lock.json"))) return false;
|
|
10
|
+
|
|
11
|
+
execute(process.platform === "win32" ? "npm.cmd" : "npm", ["ci", "--ignore-scripts"], {
|
|
12
|
+
cwd: root,
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
});
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
19
|
+
prepareDependencies();
|
|
20
|
+
}
|