@omercnet/paseo-omp 0.2.1 → 0.3.0-next.92.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 +26 -0
  2. package/README.md +25 -13
  3. package/SUPPORT.md +6 -2
  4. package/TESTING.md +21 -18
  5. package/client/composer-pill-settings.tsx +157 -0
  6. package/client/external-url.ts +15 -0
  7. package/client/mcp-authorization.tsx +169 -0
  8. package/client/mcp-popover.tsx +155 -0
  9. package/client/memory-panel.tsx +8 -3
  10. package/client/memory-popover.tsx +8 -4
  11. package/client/omp-config-surface.tsx +189 -29
  12. package/client/omp-plugin-manager.tsx +302 -131
  13. package/client/omp-store-picker.tsx +89 -0
  14. package/client/omp-store-state.ts +45 -0
  15. package/client/paseo-types.ts +9 -0
  16. package/client/provider-diagnostics-state.ts +18 -7
  17. package/client/quota-popover.tsx +8 -3
  18. package/client/quota-state.ts +16 -7
  19. package/client/sessions-popover.tsx +8 -3
  20. package/docs/alpha-release-checklist.md +6 -8
  21. package/docs/configuration.md +8 -4
  22. package/docs/core-provider-issue-audit.md +3 -2
  23. package/docs/images/mcp-authorization-compact.png +0 -0
  24. package/docs/images/mcp-controls-wide.png +0 -0
  25. package/docs/images/plugin-manager.png +0 -0
  26. package/docs/images/workspace-settings.png +0 -0
  27. package/docs/installation.md +35 -19
  28. package/index.client.tsx +339 -123
  29. package/index.server.ts +44 -14
  30. package/package.json +7 -8
  31. package/paseo-plugin.json +2 -2
  32. package/scripts/prepare-dependencies.mjs +24 -0
  33. package/server/mcp-browser.ts +95 -0
  34. package/server/memory.ts +2 -2
  35. package/server/omp-config.ts +16 -7
  36. package/server/omp-plugins.ts +70 -21
  37. package/server/omp-settings.ts +232 -24
  38. package/server/paths.ts +128 -11
  39. package/server/provider/catalog.ts +3 -4
  40. package/server/provider/connection.ts +213 -9
  41. package/server/provider/host-tools.ts +71 -0
  42. package/server/provider/omp-rpc.ts +82 -15
  43. package/server/provider/profile-providers.ts +249 -0
  44. package/server/provider/registration.ts +11 -0
  45. package/server/provider/session-descriptors.ts +306 -1
  46. package/server/provider/session.ts +704 -249
  47. package/server/provider/subsessions.ts +4 -1
  48. package/server/provider/timeline-projector.ts +70 -33
  49. package/server/provider-diagnostics.ts +122 -36
  50. package/server/quota.ts +3 -2
  51. package/server/sessions.ts +2 -2
  52. package/shared/composer-pill-settings.ts +28 -0
  53. package/shared/external-url.ts +21 -0
  54. package/shared/hub.ts +3 -3
  55. package/shared/mcp.ts +47 -0
  56. package/shared/memory.ts +2 -1
  57. package/shared/omp-config.ts +5 -1
  58. package/shared/omp-plugins.ts +74 -33
  59. package/shared/omp-settings.ts +8 -1
  60. package/shared/omp-store.ts +58 -0
  61. package/shared/provider-diagnostics.ts +12 -3
  62. package/shared/quota.ts +2 -1
  63. package/shared/sessions.ts +2 -1
package/index.client.tsx CHANGED
@@ -1,11 +1,24 @@
1
- import type { PaseoAgentListResult, PaseoApi } from "@getpaseo/client";
2
- import type { PluginButtonRegistration, PluginClientContext } from "@getpaseo/plugin/client";
1
+ import { settingsRpc } from "@getpaseo/plugin";
2
+ import type {
3
+ PluginButtonRegistration,
4
+ PluginClientContext,
5
+ PluginSurfaceProps,
6
+ } from "@getpaseo/plugin/client";
3
7
  import { OmpIcon } from "./client/hub-icon";
4
8
  import { HubPopover } from "./client/hub-popover";
5
9
  import { summarizeHubProcesses } from "./client/hub-status";
10
+ import { OmpMcpAuthorizationCard } from "./client/mcp-authorization";
11
+ import { McpPopover } from "./client/mcp-popover";
6
12
  import { OmpMemoryPanel } from "./client/memory-panel";
7
13
  import { MemoryPopover } from "./client/memory-popover";
8
- import { OmpConfigSurface } from "./client/omp-config-surface";
14
+ import { OmpConfigSurface, OmpWorkspacePanel } from "./client/omp-config-surface";
15
+ import {
16
+ createStoreQuotaLoader,
17
+ isOmpPluginProvider,
18
+ isOmpProvider,
19
+ ompStoreKey,
20
+ } from "./client/omp-store-state";
21
+ import type { PaseoAgentListResult, PaseoApi } from "./client/paseo-types";
9
22
  import { quotaProviderIcon } from "./client/provider-icon";
10
23
  import { OmpImageTimeline } from "./client/provider-image";
11
24
  import { QuotaPopover } from "./client/quota-popover";
@@ -16,7 +29,14 @@ import {
16
29
  quotaSummaryForProvider,
17
30
  } from "./client/quota-state";
18
31
  import { SessionsPopover } from "./client/sessions-popover";
32
+ import {
33
+ type ComposerPillSettings,
34
+ composerPillSettings,
35
+ composerPillSettingsSchema,
36
+ } from "./shared/composer-pill-settings";
19
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";
20
40
  import { ompImageTimelineSchema, transformOmpImageToolItem } from "./shared/provider-image";
21
41
  import { listOmpQuotas } from "./shared/quota";
22
42
 
@@ -25,20 +45,46 @@ const MAX_PAGES = 10;
25
45
  const STATUS_POLL_MS = 4_000;
26
46
  const QUOTA_POLL_MS = 30_000;
27
47
  const RECONCILE_DEBOUNCE_MS = 250;
48
+ const SETTINGS_POLL_MS = 15_000;
49
+ const composerPillSettingsRpc = settingsRpc(composerPillSettings.id);
28
50
 
29
51
  type AgentEntry = PaseoAgentListResult["entries"][number];
30
52
 
31
53
  type PillEntry = {
54
+ store?: OmpStore;
32
55
  cwd: string;
56
+ provider: string;
33
57
  workspaceId: string;
34
58
  quotaProvider: string | null;
35
59
  quotaSeverity: QuotaSeverity;
36
- hub: PluginButtonRegistration;
37
- memory: PluginButtonRegistration;
38
- sessions: PluginButtonRegistration;
39
- quota: PluginButtonRegistration;
60
+ hub?: PluginButtonRegistration;
61
+ memory?: PluginButtonRegistration;
62
+ sessions?: PluginButtonRegistration;
63
+ quota?: PluginButtonRegistration;
64
+ mcp?: PluginButtonRegistration;
40
65
  };
41
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
+
42
88
  async function loadAgents(paseo: PaseoApi): Promise<AgentEntry[]> {
43
89
  const entries: AgentEntry[] = [];
44
90
  let cursor: string | undefined;
@@ -55,6 +101,28 @@ async function loadAgents(paseo: PaseoApi): Promise<AgentEntry[]> {
55
101
  }
56
102
 
57
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
+
58
126
  const removeMemoryPanel = client.addWorkspacePanel({
59
127
  id: "memory",
60
128
  title: "OMP Memory",
@@ -73,7 +141,25 @@ export default function contribute(client: PluginClientContext) {
73
141
  openPanel("memory", { location: "explorer" });
74
142
  },
75
143
  });
76
- const removeConfigSurface = client.addSurface("config", OmpConfigSurface);
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);
77
163
  const removeConfigSidebarItem = client.addSidebarItem({
78
164
  id: "config",
79
165
  title: "OMP",
@@ -84,7 +170,7 @@ export default function contribute(client: PluginClientContext) {
84
170
  id: "open-config",
85
171
  title: "Open OMP",
86
172
  icon: "Settings",
87
- keywords: ["omp", "config", "settings", "models", "providers"],
173
+ keywords: ["omp", "config", "settings", "models", "providers", "composer", "pills"],
88
174
  context: "global",
89
175
  onSelect({ openSurface }) {
90
176
  openSurface("config");
@@ -96,6 +182,12 @@ export default function contribute(client: PluginClientContext) {
96
182
  schema: ompImageTimelineSchema,
97
183
  Component: OmpImageTimeline,
98
184
  });
185
+ const removeMcpAuthorizationRenderer = client.addTimelineRenderer({
186
+ kind: OMP_MCP_AUTH_TIMELINE_KIND,
187
+ version: 1,
188
+ schema: ompMcpAuthorizationTimelineSchema,
189
+ Component: OmpMcpAuthorizationCard,
190
+ });
99
191
  const removeImageTransformer = client.addTimelineTransformer({
100
192
  id: "omp-images",
101
193
  query: { itemType: "tool_call" },
@@ -103,136 +195,257 @@ export default function contribute(client: PluginClientContext) {
103
195
  return transformOmpImageToolItem(item);
104
196
  },
105
197
  });
106
- const pills = new Map<string, PillEntry>();
107
- let disposed = false;
108
- let reconcileTimer: ReturnType<typeof setTimeout> | undefined;
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
+ }
109
223
 
110
- async function reconcile() {
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;
111
298
  const agents = await loadAgents(client.paseo);
112
- if (disposed) return;
299
+ if (disposed || !preferences) return;
113
300
  const activeIds = new Set<string>();
114
301
  for (const { agent } of agents) {
115
302
  if (agent.archivedAt || !agent.workspaceId || !agent.cwd) continue;
116
303
  activeIds.add(agent.id);
117
304
  const quotaProvider = quotaProviderFromSession(agent.provider, agent.model);
118
- const current = pills.get(agent.id);
305
+ const store = storeForProvider(agent.provider);
306
+ let entry = pills.get(agent.id);
119
307
  if (
120
- current?.cwd === agent.cwd &&
121
- current.workspaceId === agent.workspaceId &&
122
- current.quotaProvider === quotaProvider
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)
123
314
  ) {
124
- continue;
125
- }
126
- current?.hub.remove();
127
- current?.memory.remove();
128
- current?.sessions.remove();
129
- current?.quota.remove();
130
- pills.set(agent.id, {
131
- cwd: agent.cwd,
132
- workspaceId: agent.workspaceId,
133
- quotaProvider,
134
- quotaSeverity: "unknown",
135
- hub: client.addComposerPill({
136
- id: "hub",
137
- workspaceId: agent.workspaceId,
138
- agentId: agent.id,
139
- button: {
140
- title: "Hub processes",
141
- icon: OmpIcon,
142
- label: "Hub",
143
- visible: false,
144
- behavior: { kind: "popover", Content: HubPopover },
145
- },
146
- }),
147
- memory: client.addComposerPill({
148
- id: "memory",
149
- workspaceId: agent.workspaceId,
150
- agentId: agent.id,
151
- button: {
152
- title: "OMP workspace memory",
153
- icon: "Brain",
154
- label: "Memory",
155
- behavior: { kind: "popover", Content: MemoryPopover },
156
- },
157
- }),
158
- sessions: client.addComposerPill({
159
- id: "sessions",
315
+ if (entry) removePills(entry);
316
+ entry = {
317
+ store,
318
+ cwd: agent.cwd,
319
+ provider: agent.provider,
160
320
  workspaceId: agent.workspaceId,
161
- agentId: agent.id,
162
- button: {
163
- title: "omp session history",
164
- icon: "History",
165
- label: "Sessions",
166
- behavior: { kind: "popover", Content: SessionsPopover },
167
- },
168
- }),
169
- quota: client.addComposerPill({
170
- id: "quota",
171
- workspaceId: agent.workspaceId,
172
- agentId: agent.id,
173
- button: {
174
- title: "OMP provider quotas",
175
- icon: quotaProviderIcon(quotaProvider, "unknown"),
176
- label: "Quota",
177
- visible: false,
178
- behavior: { kind: "popover", Content: QuotaPopover },
179
- },
180
- }),
181
- });
321
+ quotaProvider,
322
+ quotaSeverity: "unknown",
323
+ };
324
+ pills.set(agent.id, entry);
325
+ }
326
+ syncAgentPills(entry, agent);
182
327
  }
183
- for (const [agentId, pill] of pills) {
328
+ for (const [agentId, entry] of pills) {
184
329
  if (activeIds.has(agentId)) continue;
185
- pill.hub.remove();
186
- pill.memory.remove();
187
- pill.sessions.remove();
188
- pill.quota.remove();
330
+ removePills(entry);
189
331
  pills.delete(agentId);
190
332
  }
191
333
  await Promise.all([refreshHubStatus(), refreshQuotaStatus()]);
192
334
  }
193
335
 
194
- async function refreshHubStatus() {
195
- const pillsByCwd = new Map<string, PillEntry[]>();
196
- for (const pill of pills.values()) {
197
- const group = pillsByCwd.get(pill.cwd);
198
- if (group) group.push(pill);
199
- else pillsByCwd.set(pill.cwd, [pill]);
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;
200
349
  }
201
- await Promise.all(
202
- [...pillsByCwd].map(async ([cwd, cwdPills]) => {
203
- try {
204
- const result = await client.rpc(listHubProcesses, { cwd });
205
- if (disposed) return;
206
- const summary = summarizeHubProcesses(result.processes);
207
- for (const pill of cwdPills) pill.hub.update(summary);
208
- } catch {
209
- // Keep the last known state. A disconnected host or missing omp directory should not
210
- // remove a status the user was already inspecting.
211
- }
212
- }),
213
- );
214
350
  }
215
351
 
216
- async function refreshQuotaStatus() {
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;
217
367
  try {
218
- const result = await client.rpc(listOmpQuotas, {});
219
- if (disposed) return;
220
- for (const pill of pills.values()) {
221
- const severity = quotaSeverityForProvider(result.quotas, pill.quotaProvider);
222
- pill.quota.update({
223
- ...quotaSummaryForProvider(result.quotas, pill.quotaProvider),
224
- ...(severity === pill.quotaSeverity
225
- ? {}
226
- : { icon: quotaProviderIcon(pill.quotaProvider, severity) }),
227
- });
228
- pill.quotaSeverity = severity;
229
- }
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);
230
441
  } catch {
231
- // The quota database is optional and may not exist on a new omp installation.
442
+ // Keep the last known preferences. The sidebar exposes read and validation failures.
443
+ } finally {
444
+ settingsReadRunning = false;
232
445
  }
233
446
  }
234
447
 
235
- function scheduleReconcile() {
448
+ function scheduleReconcile(): void {
236
449
  clearTimeout(reconcileTimer);
237
450
  reconcileTimer = setTimeout(() => {
238
451
  void reconcile().catch(() => {});
@@ -241,31 +454,34 @@ export default function contribute(client: PluginClientContext) {
241
454
 
242
455
  const unsubscribeAgents = client.paseo.agents.subscribe(scheduleReconcile);
243
456
  const hubPoll = setInterval(() => {
244
- void refreshHubStatus().catch(() => {});
457
+ void refreshHubStatus();
245
458
  }, STATUS_POLL_MS);
246
459
  const quotaPoll = setInterval(() => {
247
- void refreshQuotaStatus().catch(() => {});
460
+ void refreshQuotaStatus();
248
461
  }, QUOTA_POLL_MS);
249
- void reconcile().catch(() => {});
462
+ const settingsPoll = setInterval(() => {
463
+ void refreshComposerPillSettings();
464
+ }, SETTINGS_POLL_MS);
465
+ void refreshComposerPillSettings();
250
466
 
251
467
  return () => {
252
468
  disposed = true;
469
+ settingsGeneration += 1;
253
470
  unsubscribeAgents();
254
471
  clearTimeout(reconcileTimer);
255
472
  clearInterval(hubPoll);
256
473
  clearInterval(quotaPoll);
257
- for (const pill of pills.values()) {
258
- pill.hub.remove();
259
- pill.memory.remove();
260
- pill.sessions.remove();
261
- pill.quota.remove();
262
- }
474
+ clearInterval(settingsPoll);
475
+ for (const entry of pills.values()) removePills(entry);
263
476
  pills.clear();
264
477
  removeImageRenderer();
478
+ removeMcpAuthorizationRenderer();
265
479
  removeImageTransformer();
266
480
  removeOpenConfig();
267
481
  removeConfigSidebarItem();
268
482
  removeConfigSurface();
483
+ removeOpenWorkspace();
484
+ removeWorkspacePanel();
269
485
  removeOpenMemory();
270
486
  removeMemoryPanel();
271
487
  };
package/index.server.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { PluginServerContext } from "@getpaseo/plugin/server";
2
2
  import { resolveListHubProcesses, resolveTailHubLog } from "./server/hub";
3
+ import {
4
+ OmpBrowserAuthorizationRegistry,
5
+ resolveOpenOmpMcpAuthorizationInPaseoBrowser,
6
+ } from "./server/mcp-browser";
3
7
  import { resolveListOmpMemory } from "./server/memory";
4
8
  import { resolveListOmpConfig } from "./server/omp-config";
5
9
  import {
@@ -9,12 +13,20 @@ import {
9
13
  resolveMutateOmpPluginConfig,
10
14
  } from "./server/omp-plugins";
11
15
  import { resolveListOmpSettings, resolveUpdateOmpSettings } from "./server/omp-settings";
16
+ import { withOmpStore } from "./server/paths";
12
17
  import { withOmpWorkspaceIdentity } from "./server/provider/host-tools";
18
+ import {
19
+ createProfileOmpProvider,
20
+ discoverOmpProfiles,
21
+ discoverOmpProfilesSync,
22
+ } from "./server/provider/profile-providers";
13
23
  import { createOmpProvider } from "./server/provider/registration";
14
24
  import { resolveGetOmpProviderHealth } from "./server/provider-diagnostics";
15
25
  import { resolveListOmpQuotas } from "./server/quota";
16
26
  import { resolveListOmpSessions } from "./server/sessions";
27
+ import { composerPillSettings } from "./shared/composer-pill-settings";
17
28
  import { listHubProcesses, tailHubLog } from "./shared/hub";
29
+ import { openOmpMcpAuthorizationInPaseoBrowser } from "./shared/mcp";
18
30
  import { listOmpMemory } from "./shared/memory";
19
31
  import { listOmpConfig } from "./shared/omp-config";
20
32
  import {
@@ -24,28 +36,46 @@ import {
24
36
  mutateOmpPluginConfig,
25
37
  } from "./shared/omp-plugins";
26
38
  import { listOmpSettings, updateOmpSettings } from "./shared/omp-settings";
39
+ import { listOmpStores, type OmpStore } from "./shared/omp-store";
27
40
  import { getOmpProviderHealth } from "./shared/provider-diagnostics";
28
41
  import { listOmpQuotas } from "./shared/quota";
29
42
  import { listOmpSessions } from "./shared/sessions";
30
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
+
31
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
+ }
32
56
  server.handle(listHubProcesses, resolveListHubProcesses);
33
57
  server.handle(tailHubLog, resolveTailHubLog);
34
- server.handle(listOmpQuotas, resolveListOmpQuotas);
35
- server.handle(listOmpMemory, resolveListOmpMemory);
36
- server.handle(listOmpSessions, resolveListOmpSessions);
37
- server.handle(listOmpConfig, resolveListOmpConfig);
38
- server.handle(listOmpPlugins, resolveListOmpPlugins);
39
- server.handle(inspectOmpPluginConfig, resolveInspectOmpPluginConfig);
40
- server.handle(mutateOmpPlugin, resolveMutateOmpPlugin);
41
- server.handle(mutateOmpPluginConfig, resolveMutateOmpPluginConfig);
42
- server.handle(listOmpSettings, resolveListOmpSettings);
43
- server.handle(updateOmpSettings, resolveUpdateOmpSettings);
44
- server.handle(getOmpProviderHealth, resolveGetOmpProviderHealth);
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
+ );
45
72
  const removeIdentityHook = server.before("agent.session_open", ({ request }) => {
46
- if (request.provider !== "omp-plugin") return;
73
+ if (request.provider !== "omp-plugin" && !request.provider.startsWith("omp-plugin-")) return;
47
74
  return withOmpWorkspaceIdentity(request);
48
75
  });
49
- server.registerProvider(createOmpProvider());
50
- return () => removeIdentityHook();
76
+ server.registerProvider(createOmpProvider({ browserAuthorizationRegistry }));
77
+ return () => {
78
+ browserAuthorizationRegistry.clear();
79
+ removeIdentityHook();
80
+ };
51
81
  }