@omercnet/paseo-omp 0.2.1 → 0.3.0-next.101.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 +26 -0
- package/README.md +25 -13
- package/SUPPORT.md +7 -3
- package/TESTING.md +21 -18
- package/client/composer-pill-settings.tsx +157 -0
- package/client/external-url.ts +15 -0
- package/client/mcp-authorization.tsx +169 -0
- package/client/mcp-popover.tsx +155 -0
- package/client/memory-panel.tsx +8 -3
- package/client/memory-popover.tsx +8 -4
- package/client/omp-config-surface.tsx +189 -29
- package/client/omp-plugin-manager.tsx +302 -131
- package/client/omp-store-picker.tsx +89 -0
- package/client/omp-store-state.ts +45 -0
- package/client/paseo-types.ts +9 -0
- package/client/provider-diagnostics-state.ts +18 -7
- package/client/quota-popover.tsx +8 -3
- package/client/quota-state.ts +16 -7
- package/client/sessions-popover.tsx +8 -3
- package/docs/alpha-release-checklist.md +6 -8
- package/docs/configuration.md +8 -4
- package/docs/core-provider-issue-audit.md +3 -2
- 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 +35 -19
- package/index.client.tsx +339 -123
- package/index.server.ts +44 -14
- package/package.json +7 -8
- package/paseo-plugin.json +2 -2
- package/scripts/prepare-dependencies.mjs +24 -0
- package/server/mcp-browser.ts +95 -0
- package/server/memory.ts +2 -2
- package/server/omp-config.ts +16 -7
- package/server/omp-plugins.ts +70 -21
- package/server/omp-settings.ts +232 -24
- package/server/paths.ts +128 -11
- package/server/provider/catalog.ts +3 -4
- package/server/provider/connection.ts +248 -17
- package/server/provider/host-tools.ts +294 -26
- package/server/provider/omp-rpc.ts +499 -72
- package/server/provider/profile-providers.ts +249 -0
- package/server/provider/registration.ts +11 -0
- package/server/provider/security.ts +8 -10
- package/server/provider/session-descriptors.ts +340 -1
- package/server/provider/session.ts +716 -249
- package/server/provider/subsessions.ts +25 -2
- package/server/provider/timeline-projector.ts +104 -44
- package/server/provider-diagnostics.ts +122 -36
- package/server/quota.ts +3 -2
- package/server/sessions.ts +2 -2
- package/shared/composer-pill-settings.ts +28 -0
- package/shared/external-url.ts +21 -0
- package/shared/hub.ts +3 -3
- package/shared/mcp.ts +47 -0
- package/shared/memory.ts +2 -1
- package/shared/omp-config.ts +5 -1
- package/shared/omp-plugins.ts +74 -33
- package/shared/omp-settings.ts +8 -1
- package/shared/omp-store.ts +58 -0
- package/shared/provider-diagnostics.ts +12 -3
- package/shared/quota.ts +2 -1
- package/shared/sessions.ts +2 -1
package/index.client.tsx
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
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
|
|
37
|
-
memory
|
|
38
|
-
sessions
|
|
39
|
-
quota
|
|
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
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
|
305
|
+
const store = storeForProvider(agent.provider);
|
|
306
|
+
let entry = pills.get(agent.id);
|
|
119
307
|
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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,
|
|
328
|
+
for (const [agentId, entry] of pills) {
|
|
184
329
|
if (activeIds.has(agentId)) continue;
|
|
185
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
//
|
|
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()
|
|
457
|
+
void refreshHubStatus();
|
|
245
458
|
}, STATUS_POLL_MS);
|
|
246
459
|
const quotaPoll = setInterval(() => {
|
|
247
|
-
void refreshQuotaStatus()
|
|
460
|
+
void refreshQuotaStatus();
|
|
248
461
|
}, QUOTA_POLL_MS);
|
|
249
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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 () =>
|
|
76
|
+
server.registerProvider(createOmpProvider({ browserAuthorizationRegistry }));
|
|
77
|
+
return () => {
|
|
78
|
+
browserAuthorizationRegistry.clear();
|
|
79
|
+
removeIdentityHook();
|
|
80
|
+
};
|
|
51
81
|
}
|