@omercnet/paseo-omp 1.1.0 → 1.2.0
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/README.md +11 -4
- package/client/hub-popover.tsx +30 -6
- package/client/hub-sidebar.tsx +135 -0
- package/client/memory-panel.tsx +1 -1
- package/client/omp-config-styles.ts +1 -1
- package/client/omp-config-surface.tsx +16 -3
- package/client/sidebar-compat.ts +160 -0
- package/index.client.tsx +82 -11
- package/package.json +6 -6
- package/paseo-plugin.json +1 -1
- package/server/package-version.ts +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ before capture.
|
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
27
|
-
Requirements: Paseo `^0.9.2 || ^0.10.0`, OMP `18.1.15` or newer, and OMP RPC protocol v2. Paseo 0.9.2 is required for reliable plugin-provider request failure containment, reload cleanup, and daemon shutdown ordering; 0.10.1 is verified against the same public plugin contracts.
|
|
27
|
+
Requirements: Paseo `^0.9.2 || ^0.10.0 || ^0.11.0`, OMP `18.1.15` or newer, and OMP RPC protocol v2. Paseo 0.9.2 is required for reliable plugin-provider request failure containment, reload cleanup, and daemon shutdown ordering; 0.10.1 is verified against the same public plugin contracts.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
paseo plugin install npm:@omercnet/paseo-omp@<version>
|
|
@@ -54,9 +54,13 @@ Profile selection is request-local: concurrent clients cannot change each other'
|
|
|
54
54
|
|
|
55
55
|
## Paseo provider SDK coverage
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
The capability inventory is pinned to `@getpaseo/plugin` 0.11.0-beta.3 (PR #251), with nightly
|
|
58
|
+
compatibility checks against supported 0.9 and 0.10 hosts. Provider capability scoring still covers
|
|
59
|
+
all 17 entries in the SDK's `PROVIDER_CAPABILITIES`; the separate provider/client SDK table below
|
|
60
|
+
also tracks the 0.11 registration, screen, and sidebar APIs. This measures strict SDK surface
|
|
61
|
+
coverage, not general product quality.
|
|
58
62
|
|
|
59
|
-
**
|
|
63
|
+
**Provider capability completeness: 61.8%.** The provider advertises 11 of 17 capabilities (64.7%); ten are complete and `session.configure` is partial.
|
|
60
64
|
|
|
61
65
|
Scoring is deliberately mechanical so releases remain comparable:
|
|
62
66
|
|
|
@@ -91,7 +95,7 @@ Provider functionality outside the capability flags is tracked separately:
|
|
|
91
95
|
| --- | ---: | --- |
|
|
92
96
|
| Registration metadata and sanitized SVG icon | **100%** | Stable `omp-plugin` identity, label, description, and bundled icon. |
|
|
93
97
|
| Strict `providerOptionsSchema` | **100%** | Command, literal environment, names-only profile inherited environment, output-redaction mode, session directory, RPC timeout, and role-model options are validated and normalized. Host-wide inherited names are separately persisted and merged at launch. |
|
|
94
|
-
| Availability diagnostics | **
|
|
98
|
+
| Availability diagnostics | **50%** | The plugin implements a `checkAvailability` hook with bounded checks that distinguish missing, unrunnable, incompatible, and available OMP runtimes, but no released daemon calls it (no upstream reference at v0.10.2 or v0.11.0-beta.3). Hosts treat the provider as available when a connection opens, and launch failures surface at session start. |
|
|
95
99
|
| Catalog cache identity | **100%** | Hash includes effective options, merged host/profile inherited-environment names, settings, scope, cwd, and default command, but never resolves or fingerprints inherited values. |
|
|
96
100
|
| Models, modes, and thinking catalog | **100%** | Native catalog is mapped to opaque public model IDs with committed defaults and permission-gated modes. |
|
|
97
101
|
| Connection `send` / `onEvent` / `close` lifecycle | **100%** | Request correlation, multi-session ownership, process recovery, teardown, and provider reload/removal are covered. |
|
|
@@ -101,6 +105,9 @@ Provider functionality outside the capability flags is tracked separately:
|
|
|
101
105
|
| Commands and committed session state events | **100%** | Publishes `session.commands`, `session.opened`, `session.config`, `session.ready`, and request completion in protocol order. |
|
|
102
106
|
| Built-in timeline snapshots | **100%** | Assistant, reasoning, tools, todos, notifications, errors, compaction, and friendly MCP labels use stable IDs and complete snapshots. OMP emits client-safe PNG/JPEG when possible; validated legacy WebP images render on capable clients with an explicit per-image fallback elsewhere. |
|
|
103
107
|
| Usage reporting | **100%** | Periodic, post-compaction, fallback, terminal, timeout, and recovered-runtime samples publish `session.usage`. |
|
|
108
|
+
| 0.11 provider `status()` | **0%** | Not registered. Its request carries only an optional daemon-resolved `launch`; the daemon supplies one only when a registration declares `command`, which would move launch ownership to the daemon. Without that, status cannot see per-agent `providerOptions.command`, so probing a guessed default could wrongly mark a valid custom-command provider unavailable. |
|
|
109
|
+
| 0.11 screens and sidebar items | **100%** | The global config surface migrates to `addScreen({ id: "config" })`, preserving legacy links and encoding profile selection in screen params. The Hub status row uses `addSidebarHeaderItem` + `SidebarRow`, lists processes and tails logs in a reusable popover. Hosts without the 0.11 APIs retain the static config row/surface; the Hub row is 0.11-only. |
|
|
110
|
+
| 0.11 usage source | **0%** | Not registered. `server/quota.ts` reads OMP's recorded `usage_history`, not live quota, so there is no live fetcher to register. Upstream Claude and Codex sources fetch live quota for the Anthropic and Codex OAuth credentials in the daemon's default or `OMP_PROFILE` store; other providers and named profiles remain covered only by this plugin's historical quota pill. |
|
|
104
111
|
|
|
105
112
|
Tracking rules:
|
|
106
113
|
|
package/client/hub-popover.tsx
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type PluginButtonContentProps,
|
|
3
|
+
type PluginHostProps,
|
|
4
|
+
useAgent,
|
|
5
|
+
useRpc,
|
|
6
|
+
} from "@getpaseo/plugin/client";
|
|
2
7
|
import { useQuery } from "@tanstack/react-query";
|
|
3
8
|
import { useMemo, useState } from "react";
|
|
4
9
|
import { Pressable, Text, View } from "react-native";
|
|
@@ -34,16 +39,31 @@ function toneColor(
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
export function HubPopover(props: PluginButtonContentProps) {
|
|
37
|
-
const { theme, layout } = props;
|
|
38
42
|
const agentId = props.context === "agent" ? props.agentId : "";
|
|
39
43
|
const cwd = useAgent(agentId, (agent) => agent.cwd) ?? "";
|
|
44
|
+
return <HubProcessList theme={props.theme} layout={props.layout} cwd={cwd} />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Process cards with a per-process log tail, for one workspace directory. Pass `processes` when
|
|
49
|
+
* the caller already polls them; the list then skips its own process query.
|
|
50
|
+
*/
|
|
51
|
+
export function HubProcessList({
|
|
52
|
+
theme,
|
|
53
|
+
layout,
|
|
54
|
+
cwd,
|
|
55
|
+
processes: provided,
|
|
56
|
+
}: Pick<PluginHostProps, "theme" | "layout"> & {
|
|
57
|
+
cwd: string;
|
|
58
|
+
processes?: readonly HubProcess[];
|
|
59
|
+
}) {
|
|
40
60
|
const loadProcesses = useRpc(listHubProcesses);
|
|
41
61
|
const loadLog = useRpc(tailHubLog);
|
|
42
62
|
const [selectedName, setSelectedName] = useState<string | null>(null);
|
|
43
63
|
const processes = useQuery({
|
|
44
64
|
queryKey: ["paseo-omp", "processes", cwd],
|
|
45
65
|
queryFn: () => loadProcesses({ cwd }),
|
|
46
|
-
enabled: cwd.length > 0,
|
|
66
|
+
enabled: cwd.length > 0 && provided === undefined,
|
|
47
67
|
refetchInterval: PROCESS_POLL_MS,
|
|
48
68
|
});
|
|
49
69
|
const log = useQuery({
|
|
@@ -80,9 +100,13 @@ export function HubPopover(props: PluginButtonContentProps) {
|
|
|
80
100
|
[layout.compact, theme],
|
|
81
101
|
);
|
|
82
102
|
|
|
83
|
-
if (processes.isLoading)
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
if (!provided && processes.isLoading) {
|
|
104
|
+
return <Text style={styles.muted}>Loading hub processes…</Text>;
|
|
105
|
+
}
|
|
106
|
+
if (!provided && processes.error) {
|
|
107
|
+
return <Text style={styles.error}>Could not read omp hub state.</Text>;
|
|
108
|
+
}
|
|
109
|
+
const items = provided ?? processes.data?.processes ?? [];
|
|
86
110
|
if (items.length === 0) return <Text style={styles.muted}>No hub-supervised processes.</Text>;
|
|
87
111
|
|
|
88
112
|
return (
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { PluginHostProps } from "@getpaseo/plugin/client";
|
|
2
|
+
// Namespace import: on 0.9/0.10 hosts this module lacks SidebarRow, which is only rendered from
|
|
3
|
+
// 0.11-only sidebar items. Typed locally so older SDK typechecks still compile.
|
|
4
|
+
import * as pluginUi from "@getpaseo/plugin/client/ui";
|
|
5
|
+
import { useQuery } from "@tanstack/react-query";
|
|
6
|
+
import type { ComponentType, ReactNode } from "react";
|
|
7
|
+
import { Text, View } from "react-native";
|
|
8
|
+
import { HubProcessList } from "./hub-popover";
|
|
9
|
+
import { CONFIG_SCREEN_ID, type HubSnapshot, hubTrailing } from "./sidebar-compat";
|
|
10
|
+
|
|
11
|
+
// The row is always mounted; a slower poll bounds per-client RPC fan-out across workspaces.
|
|
12
|
+
const HUB_POLL_MS = 15_000;
|
|
13
|
+
const HUB_SIDEBAR_QUERY_KEY = ["paseo-omp", "hub-sidebar"] as const;
|
|
14
|
+
|
|
15
|
+
interface OpenScreenInput {
|
|
16
|
+
screenId: string;
|
|
17
|
+
params?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
type HostProps = Pick<PluginHostProps, "theme" | "layout">;
|
|
20
|
+
interface PopoverProps extends HostProps {
|
|
21
|
+
close(): void;
|
|
22
|
+
openScreen(input: OpenScreenInput): void;
|
|
23
|
+
}
|
|
24
|
+
interface SidebarItemProps extends HostProps {
|
|
25
|
+
currentScreen: { screenId: string; params: Record<string, string> } | null;
|
|
26
|
+
openScreen(input: OpenScreenInput): void;
|
|
27
|
+
openPopover(Content: ComponentType<PopoverProps>): void;
|
|
28
|
+
}
|
|
29
|
+
type SidebarRowComponent = ComponentType<{
|
|
30
|
+
id?: string;
|
|
31
|
+
icon?: string | ComponentType<{ size: number; color: string }>;
|
|
32
|
+
label?: string;
|
|
33
|
+
onPress(): void;
|
|
34
|
+
active?: boolean;
|
|
35
|
+
trailing?: ReactNode;
|
|
36
|
+
}>;
|
|
37
|
+
|
|
38
|
+
const uiModule: object = pluginUi;
|
|
39
|
+
// Present on every 0.11 host, the only hosts that render these items. The guard and cast keep
|
|
40
|
+
// older-SDK typechecks (which lack SidebarRow) compiling; presence check only, because host
|
|
41
|
+
// components may be memo/forwardRef objects rather than plain functions.
|
|
42
|
+
const SidebarRow: SidebarRowComponent | undefined =
|
|
43
|
+
"SidebarRow" in uiModule && uiModule.SidebarRow
|
|
44
|
+
? (uiModule.SidebarRow as SidebarRowComponent)
|
|
45
|
+
: undefined;
|
|
46
|
+
|
|
47
|
+
export function ConfigSidebarItem({ currentScreen, openScreen }: SidebarItemProps) {
|
|
48
|
+
if (!SidebarRow) return null;
|
|
49
|
+
return (
|
|
50
|
+
<SidebarRow
|
|
51
|
+
icon="Settings"
|
|
52
|
+
label="OMP"
|
|
53
|
+
active={currentScreen?.screenId === CONFIG_SCREEN_ID}
|
|
54
|
+
onPress={() => openScreen({ screenId: CONFIG_SCREEN_ID })}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createHubSidebar(loadSnapshot: () => Promise<HubSnapshot>) {
|
|
60
|
+
const useHubSnapshot = () =>
|
|
61
|
+
useQuery({
|
|
62
|
+
queryKey: HUB_SIDEBAR_QUERY_KEY,
|
|
63
|
+
queryFn: loadSnapshot,
|
|
64
|
+
refetchInterval: HUB_POLL_MS,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function HubSidebarPopover({ theme, layout }: PopoverProps) {
|
|
68
|
+
const snapshot = useHubSnapshot();
|
|
69
|
+
const muted = { color: theme.colors.foregroundMuted, fontSize: 13 };
|
|
70
|
+
if (snapshot.isLoading) return <Text style={muted}>Loading hub processes…</Text>;
|
|
71
|
+
if (!snapshot.data) {
|
|
72
|
+
return (
|
|
73
|
+
<Text style={{ color: theme.colors.statusDanger, fontSize: 13 }}>
|
|
74
|
+
Could not read omp hub state.
|
|
75
|
+
</Text>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const active = snapshot.data.workspaces.filter(({ processes }) => processes.length > 0);
|
|
79
|
+
const notes = [
|
|
80
|
+
snapshot.data.unreadable > 0
|
|
81
|
+
? `${snapshot.data.unreadable} workspace(s) could not be read.`
|
|
82
|
+
: undefined,
|
|
83
|
+
snapshot.data.truncated ? "Showing the first 50 workspaces." : undefined,
|
|
84
|
+
].filter(Boolean);
|
|
85
|
+
return (
|
|
86
|
+
<View style={{ gap: layout.compact ? 12 : 14 }}>
|
|
87
|
+
{notes.map((note) => (
|
|
88
|
+
<Text key={note} style={{ ...muted, fontSize: 12 }}>
|
|
89
|
+
{note}
|
|
90
|
+
</Text>
|
|
91
|
+
))}
|
|
92
|
+
{active.length === 0 ? <Text style={muted}>No hub-supervised processes.</Text> : null}
|
|
93
|
+
{active.map(({ cwd, processes }) => (
|
|
94
|
+
<View key={cwd} style={{ gap: 6 }}>
|
|
95
|
+
<Text numberOfLines={1} style={{ ...muted, fontSize: 12 }}>
|
|
96
|
+
{cwd}
|
|
97
|
+
</Text>
|
|
98
|
+
<HubProcessList theme={theme} layout={layout} cwd={cwd} processes={processes} />
|
|
99
|
+
</View>
|
|
100
|
+
))}
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function HubSidebarItem({ theme, openPopover }: SidebarItemProps) {
|
|
106
|
+
const snapshot = useHubSnapshot();
|
|
107
|
+
if (!SidebarRow) return null;
|
|
108
|
+
const state = hubTrailing(snapshot.data, snapshot.error !== null);
|
|
109
|
+
const trailing = state ? (
|
|
110
|
+
<View
|
|
111
|
+
accessibilityLabel={state.accessibilityLabel}
|
|
112
|
+
style={{ flexDirection: "row", alignItems: "center", gap: 6 }}
|
|
113
|
+
>
|
|
114
|
+
{state.running ? (
|
|
115
|
+
<Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>{state.running}</Text>
|
|
116
|
+
) : null}
|
|
117
|
+
{state.failed || state.unreadable ? (
|
|
118
|
+
<Text style={{ color: theme.colors.statusDanger, fontSize: 12, fontWeight: "600" }}>
|
|
119
|
+
{[state.failed, state.unreadable ? "!" : undefined].filter(Boolean).join(" ")}
|
|
120
|
+
</Text>
|
|
121
|
+
) : null}
|
|
122
|
+
</View>
|
|
123
|
+
) : undefined;
|
|
124
|
+
return (
|
|
125
|
+
<SidebarRow
|
|
126
|
+
icon="Activity"
|
|
127
|
+
label="OMP Hub"
|
|
128
|
+
trailing={trailing}
|
|
129
|
+
onPress={() => openPopover(HubSidebarPopover)}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { HubSidebarItem, HubSidebarPopover };
|
|
135
|
+
}
|
package/client/memory-panel.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export function OmpMemoryPanel({ theme, layout, workspaceId }: PluginWorkspacePa
|
|
|
23
23
|
const styles = useMemo(
|
|
24
24
|
() => ({
|
|
25
25
|
root: {
|
|
26
|
-
|
|
26
|
+
flexGrow: 1,
|
|
27
27
|
gap: layout.compact ? 10 : 14,
|
|
28
28
|
padding: layout.compact ? 16 : 24,
|
|
29
29
|
backgroundColor: theme.colors.surface0,
|
|
@@ -914,11 +914,14 @@ function OmpConfigContent({
|
|
|
914
914
|
cwd,
|
|
915
915
|
store,
|
|
916
916
|
onStoreChange,
|
|
917
|
+
storeInHistory = false,
|
|
917
918
|
onComposerPillSettingsChange,
|
|
918
919
|
}: PluginSurfaceProps & {
|
|
919
920
|
cwd?: string;
|
|
920
921
|
store?: OmpStore;
|
|
921
922
|
onStoreChange(store: OmpStore | undefined): void;
|
|
923
|
+
/** True when each store selection is a separate history entry (0.11 screen params). */
|
|
924
|
+
storeInHistory?: boolean;
|
|
922
925
|
onComposerPillSettingsChange?: (settings: ComposerPillSettings) => void;
|
|
923
926
|
}) {
|
|
924
927
|
const loadConfig = useRpc(listOmpConfig);
|
|
@@ -1074,7 +1077,9 @@ function OmpConfigContent({
|
|
|
1074
1077
|
disabled={pendingMutations > 0}
|
|
1075
1078
|
/>
|
|
1076
1079
|
<Text style={styles.muted}>
|
|
1077
|
-
|
|
1080
|
+
{storeInHistory
|
|
1081
|
+
? "Each store opens as its own page; unapplied edits stay with the previous store, and Back returns to them."
|
|
1082
|
+
: "Switching stores clears unapplied edits and pending confirmations."}
|
|
1078
1083
|
</Text>
|
|
1079
1084
|
</>
|
|
1080
1085
|
) : null}
|
|
@@ -1339,9 +1344,14 @@ function OmpStoreContent(
|
|
|
1339
1344
|
props: PluginSurfaceProps & {
|
|
1340
1345
|
cwd?: string;
|
|
1341
1346
|
onComposerPillSettingsChange?: (settings: ComposerPillSettings) => void;
|
|
1347
|
+
/** Controlled selection (0.11 screen params); omitted, the surface keeps local state. */
|
|
1348
|
+
store?: OmpStore;
|
|
1349
|
+
onStoreChange?: (store: OmpStore | undefined) => void;
|
|
1342
1350
|
},
|
|
1343
1351
|
) {
|
|
1344
|
-
const [
|
|
1352
|
+
const [localStore, setLocalStore] = useState<OmpStore>();
|
|
1353
|
+
const controlled = props.onStoreChange !== undefined;
|
|
1354
|
+
const store = controlled ? props.store : localStore;
|
|
1345
1355
|
// Remount every editor when its target changes: drafts, confirmations, and mutation notices
|
|
1346
1356
|
// belong to one store/workspace and must never be applied to the next selection.
|
|
1347
1357
|
return (
|
|
@@ -1349,7 +1359,8 @@ function OmpStoreContent(
|
|
|
1349
1359
|
key={`${ompStoreKey(store)}:${props.cwd ?? "global"}`}
|
|
1350
1360
|
{...props}
|
|
1351
1361
|
store={store}
|
|
1352
|
-
onStoreChange={
|
|
1362
|
+
onStoreChange={props.onStoreChange ?? setLocalStore}
|
|
1363
|
+
storeInHistory={controlled}
|
|
1353
1364
|
/>
|
|
1354
1365
|
);
|
|
1355
1366
|
}
|
|
@@ -1357,6 +1368,8 @@ function OmpStoreContent(
|
|
|
1357
1368
|
export function OmpConfigSurface(
|
|
1358
1369
|
props: PluginSurfaceProps & {
|
|
1359
1370
|
onComposerPillSettingsChange: (settings: ComposerPillSettings) => void;
|
|
1371
|
+
store?: OmpStore;
|
|
1372
|
+
onStoreChange?: (store: OmpStore | undefined) => void;
|
|
1360
1373
|
},
|
|
1361
1374
|
) {
|
|
1362
1375
|
return <OmpStoreContent {...props} />;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { HubProcess } from "../shared/hub";
|
|
2
|
+
import { isOmpProfileName, type OmpStore } from "../shared/omp-store";
|
|
3
|
+
import { hubProcessTone } from "./hub-status";
|
|
4
|
+
|
|
5
|
+
/** The sidebar id the 0.10 `addSidebarItem` used; the 0.11 screen keeps it so saved links work. */
|
|
6
|
+
export const CONFIG_SCREEN_ID = "config";
|
|
7
|
+
export const HUB_SIDEBAR_ITEM_ID = "hub";
|
|
8
|
+
|
|
9
|
+
type ScreenParams = Readonly<Record<string, string>>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The 0.11 screen/sidebar-item API, typed structurally so 0.9/0.10 SDK typechecks still compile.
|
|
13
|
+
* On 0.10 hosts these members are absent at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export interface ScreenCapableClient {
|
|
16
|
+
addScreen(contribution: {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string | ((params: ScreenParams) => string);
|
|
19
|
+
Component: (props: never) => unknown;
|
|
20
|
+
}): () => void;
|
|
21
|
+
addSidebarHeaderItem(contribution: {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
Component: (props: never) => unknown;
|
|
25
|
+
}): () => void;
|
|
26
|
+
openScreen(input: { screenId: string; params?: Record<string, string> }): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** True only on Paseo 0.11+ hosts, which provide screens, header items, and `openScreen`. */
|
|
30
|
+
export function supportsScreens<Client extends object>(
|
|
31
|
+
client: Client,
|
|
32
|
+
): client is Client & ScreenCapableClient {
|
|
33
|
+
const candidate = client as Partial<Record<keyof ScreenCapableClient, unknown>>;
|
|
34
|
+
return (
|
|
35
|
+
typeof candidate.addScreen === "function" &&
|
|
36
|
+
typeof candidate.addSidebarHeaderItem === "function" &&
|
|
37
|
+
typeof candidate.openScreen === "function"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A `profile` screen param selects that store; anything invalid falls back to the default. */
|
|
42
|
+
export function configStoreFromParams(params: ScreenParams | undefined): OmpStore | undefined {
|
|
43
|
+
const profile = params?.profile?.trim();
|
|
44
|
+
return profile && isOmpProfileName(profile) ? { profile } : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function configParamsFromStore(store: OmpStore | undefined): Record<string, string> {
|
|
48
|
+
return store?.profile ? { profile: store.profile } : {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function configScreenTitle(params: ScreenParams): string {
|
|
52
|
+
const store = configStoreFromParams(params);
|
|
53
|
+
return store?.profile ? `OMP · ${store.profile}` : "OMP";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface HubWorkspaceProcesses {
|
|
57
|
+
cwd: string;
|
|
58
|
+
processes: readonly HubProcess[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface HubSidebarSummary {
|
|
62
|
+
running: number;
|
|
63
|
+
failed: number;
|
|
64
|
+
total: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function summarizeHubWorkspaces(
|
|
68
|
+
workspaces: readonly HubWorkspaceProcesses[],
|
|
69
|
+
): HubSidebarSummary {
|
|
70
|
+
const summary: HubSidebarSummary = { running: 0, failed: 0, total: 0 };
|
|
71
|
+
for (const { processes } of workspaces) {
|
|
72
|
+
for (const process of processes) {
|
|
73
|
+
summary.total += 1;
|
|
74
|
+
const tone = hubProcessTone(process);
|
|
75
|
+
if (tone === "success") summary.running += 1;
|
|
76
|
+
else if (tone === "danger") summary.failed += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return summary;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Distinct, non-empty workspace directories in first-seen order, bounded for polling cost. */
|
|
83
|
+
export function workspaceDirectories(
|
|
84
|
+
workspaces: ReadonlyArray<{ workspaceDirectory?: string; projectRootPath: string }>,
|
|
85
|
+
limit = 50,
|
|
86
|
+
): { directories: string[]; truncated: boolean } {
|
|
87
|
+
const directories = new Set<string>();
|
|
88
|
+
for (const workspace of workspaces) {
|
|
89
|
+
const directory = workspace.workspaceDirectory || workspace.projectRootPath;
|
|
90
|
+
if (!directory || directories.has(directory)) continue;
|
|
91
|
+
if (directories.size >= limit) return { directories: [...directories], truncated: true };
|
|
92
|
+
directories.add(directory);
|
|
93
|
+
}
|
|
94
|
+
return { directories: [...directories], truncated: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface HubSnapshot {
|
|
98
|
+
workspaces: HubWorkspaceProcesses[];
|
|
99
|
+
/** More workspaces exist than were polled; counts are a lower bound. */
|
|
100
|
+
truncated: boolean;
|
|
101
|
+
/** Workspaces whose hub state could not be read this poll. */
|
|
102
|
+
unreadable: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* One poll of hub state. A failing workspace never hides the others; the poll fails only when
|
|
107
|
+
* the workspace list itself fails or every workspace is unreadable.
|
|
108
|
+
*/
|
|
109
|
+
export async function loadHubSnapshot(
|
|
110
|
+
listWorkspaces: () => Promise<{
|
|
111
|
+
entries: ReadonlyArray<{ workspaceDirectory?: string; projectRootPath: string }>;
|
|
112
|
+
pageInfo?: { hasMore?: boolean };
|
|
113
|
+
}>,
|
|
114
|
+
listProcesses: (cwd: string) => Promise<readonly HubProcess[]>,
|
|
115
|
+
): Promise<HubSnapshot> {
|
|
116
|
+
const page = await listWorkspaces();
|
|
117
|
+
const { directories, truncated } = workspaceDirectories(page.entries);
|
|
118
|
+
const results = await Promise.allSettled(
|
|
119
|
+
directories.map(async (cwd) => ({ cwd, processes: await listProcesses(cwd) })),
|
|
120
|
+
);
|
|
121
|
+
const workspaces: HubWorkspaceProcesses[] = [];
|
|
122
|
+
for (const result of results) if (result.status === "fulfilled") workspaces.push(result.value);
|
|
123
|
+
if (directories.length > 0 && workspaces.length === 0) {
|
|
124
|
+
throw new Error("OMP hub state is unreadable");
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
workspaces,
|
|
128
|
+
truncated: truncated || page.pageInfo?.hasMore === true,
|
|
129
|
+
unreadable: directories.length - workspaces.length,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface HubTrailing {
|
|
134
|
+
running?: string;
|
|
135
|
+
failed?: string;
|
|
136
|
+
/** Some or all hub state could not be read. */
|
|
137
|
+
unreadable: boolean;
|
|
138
|
+
accessibilityLabel: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** What the Hub row's trailing slot shows; null when there is nothing to report. */
|
|
142
|
+
export function hubTrailing(
|
|
143
|
+
snapshot: HubSnapshot | undefined,
|
|
144
|
+
loadFailed: boolean,
|
|
145
|
+
): HubTrailing | null {
|
|
146
|
+
const summary = summarizeHubWorkspaces(snapshot?.workspaces ?? []);
|
|
147
|
+
const atLeast = snapshot?.truncated ? "+" : "";
|
|
148
|
+
const unreadable = loadFailed || (snapshot?.unreadable ?? 0) > 0;
|
|
149
|
+
if (summary.total === 0 && !unreadable) return null;
|
|
150
|
+
const running = summary.total > 0 ? `${summary.running}${atLeast} running` : undefined;
|
|
151
|
+
const failed = summary.failed > 0 ? `${summary.failed}${atLeast} failed` : undefined;
|
|
152
|
+
return {
|
|
153
|
+
...(running ? { running } : {}),
|
|
154
|
+
...(failed ? { failed } : {}),
|
|
155
|
+
unreadable,
|
|
156
|
+
accessibilityLabel: [running, failed, unreadable ? "some hub state unreadable" : undefined]
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join(", "),
|
|
159
|
+
};
|
|
160
|
+
}
|
package/index.client.tsx
CHANGED
|
@@ -4,8 +4,10 @@ import type {
|
|
|
4
4
|
PluginClientContext,
|
|
5
5
|
PluginSurfaceProps,
|
|
6
6
|
} from "@getpaseo/plugin/client";
|
|
7
|
+
import type { ComponentType } from "react";
|
|
7
8
|
import { OmpIcon } from "./client/hub-icon";
|
|
8
9
|
import { HubPopover } from "./client/hub-popover";
|
|
10
|
+
import { ConfigSidebarItem, createHubSidebar } from "./client/hub-sidebar";
|
|
9
11
|
import { summarizeHubProcesses } from "./client/hub-status";
|
|
10
12
|
import { OmpMcpAuthorizationCard } from "./client/mcp-authorization";
|
|
11
13
|
import { McpPopover } from "./client/mcp-popover";
|
|
@@ -29,6 +31,15 @@ import {
|
|
|
29
31
|
quotaSummaryForProvider,
|
|
30
32
|
} from "./client/quota-state";
|
|
31
33
|
import { SessionsPopover } from "./client/sessions-popover";
|
|
34
|
+
import {
|
|
35
|
+
CONFIG_SCREEN_ID,
|
|
36
|
+
configParamsFromStore,
|
|
37
|
+
configScreenTitle,
|
|
38
|
+
configStoreFromParams,
|
|
39
|
+
HUB_SIDEBAR_ITEM_ID,
|
|
40
|
+
loadHubSnapshot,
|
|
41
|
+
supportsScreens,
|
|
42
|
+
} from "./client/sidebar-compat";
|
|
32
43
|
import {
|
|
33
44
|
type ComposerPillSettings,
|
|
34
45
|
composerPillSettings,
|
|
@@ -100,6 +111,69 @@ async function loadAgents(paseo: PaseoApi): Promise<AgentEntry[]> {
|
|
|
100
111
|
return entries;
|
|
101
112
|
}
|
|
102
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Paseo 0.11+: a param-addressable `config` screen (same id as the old sidebar item, so saved
|
|
116
|
+
* links resolve to it), a header row that opens it, and a Hub status row. 0.9/0.10: the original
|
|
117
|
+
* surface and static sidebar row, unchanged.
|
|
118
|
+
*/
|
|
119
|
+
export function registerConfigAndHub(
|
|
120
|
+
client: Pick<PluginClientContext, "addSurface" | "addSidebarItem" | "rpc" | "paseo">,
|
|
121
|
+
ConfigSurface: ComponentType<PluginSurfaceProps>,
|
|
122
|
+
onComposerPillSettingsChange: (settings: ComposerPillSettings) => void,
|
|
123
|
+
): () => void {
|
|
124
|
+
if (!supportsScreens(client)) {
|
|
125
|
+
const removeSurface = client.addSurface(CONFIG_SCREEN_ID, ConfigSurface);
|
|
126
|
+
const removeItem = client.addSidebarItem({
|
|
127
|
+
id: CONFIG_SCREEN_ID,
|
|
128
|
+
title: "OMP",
|
|
129
|
+
icon: "Settings",
|
|
130
|
+
surface: CONFIG_SCREEN_ID,
|
|
131
|
+
});
|
|
132
|
+
return () => {
|
|
133
|
+
removeItem();
|
|
134
|
+
removeSurface();
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const screens = client;
|
|
138
|
+
function ConfigScreen(props: PluginSurfaceProps & { params: Record<string, string> }) {
|
|
139
|
+
const current = configStoreFromParams(props.params);
|
|
140
|
+
return (
|
|
141
|
+
<OmpConfigSurface
|
|
142
|
+
{...props}
|
|
143
|
+
onComposerPillSettingsChange={onComposerPillSettingsChange}
|
|
144
|
+
store={current}
|
|
145
|
+
onStoreChange={(store) => {
|
|
146
|
+
// Re-selecting the current store must not push a duplicate screen onto history.
|
|
147
|
+
if (ompStoreKey(store) === ompStoreKey(current)) return;
|
|
148
|
+
screens.openScreen({ screenId: CONFIG_SCREEN_ID, params: configParamsFromStore(store) });
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const { HubSidebarItem } = createHubSidebar(() =>
|
|
154
|
+
loadHubSnapshot(
|
|
155
|
+
() => client.paseo.workspaces.list({ page: { limit: 200 } }),
|
|
156
|
+
async (cwd) => (await client.rpc(listHubProcesses, { cwd })).processes,
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
const removers = [
|
|
160
|
+
screens.addScreen({ id: CONFIG_SCREEN_ID, title: configScreenTitle, Component: ConfigScreen }),
|
|
161
|
+
screens.addSidebarHeaderItem({
|
|
162
|
+
id: CONFIG_SCREEN_ID,
|
|
163
|
+
title: "OMP",
|
|
164
|
+
Component: ConfigSidebarItem,
|
|
165
|
+
}),
|
|
166
|
+
screens.addSidebarHeaderItem({
|
|
167
|
+
id: HUB_SIDEBAR_ITEM_ID,
|
|
168
|
+
title: "OMP Hub",
|
|
169
|
+
Component: HubSidebarItem,
|
|
170
|
+
}),
|
|
171
|
+
];
|
|
172
|
+
return () => {
|
|
173
|
+
for (const remove of removers.reverse()) remove();
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
103
177
|
export default function contribute(client: PluginClientContext) {
|
|
104
178
|
const pills = new Map<string, PillEntry>();
|
|
105
179
|
let preferences: ComposerPillSettings | undefined;
|
|
@@ -159,21 +233,19 @@ export default function contribute(client: PluginClientContext) {
|
|
|
159
233
|
openPanel("workspace", { location: "workspace" });
|
|
160
234
|
},
|
|
161
235
|
});
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
id: "config",
|
|
165
|
-
title: "OMP",
|
|
166
|
-
icon: "Settings",
|
|
167
|
-
surface: "config",
|
|
168
|
-
});
|
|
236
|
+
const usesScreens = supportsScreens(client);
|
|
237
|
+
const removeConfigEntry = registerConfigAndHub(client, ConfigSurface, applyComposerPillSettings);
|
|
169
238
|
const removeOpenConfig = client.addCommandCenterItem({
|
|
170
239
|
id: "open-config",
|
|
171
240
|
title: "Open OMP",
|
|
172
241
|
icon: "Settings",
|
|
173
242
|
keywords: ["omp", "config", "settings", "models", "providers", "composer", "pills"],
|
|
174
243
|
context: "global",
|
|
175
|
-
onSelect(
|
|
176
|
-
|
|
244
|
+
onSelect(capabilities) {
|
|
245
|
+
// Branch on the same gate as registration: the screen exists only on the 0.11 path.
|
|
246
|
+
if (usesScreens && "openScreen" in capabilities) {
|
|
247
|
+
capabilities.openScreen({ screenId: CONFIG_SCREEN_ID });
|
|
248
|
+
} else capabilities.openSurface(CONFIG_SCREEN_ID);
|
|
177
249
|
},
|
|
178
250
|
});
|
|
179
251
|
const removeImageRenderer = client.addTimelineRenderer({
|
|
@@ -497,8 +569,7 @@ export default function contribute(client: PluginClientContext) {
|
|
|
497
569
|
removeMcpAuthorizationRenderer();
|
|
498
570
|
removeImageTransformer();
|
|
499
571
|
removeOpenConfig();
|
|
500
|
-
|
|
501
|
-
removeConfigSurface();
|
|
572
|
+
removeConfigEntry();
|
|
502
573
|
removeOpenWorkspace();
|
|
503
574
|
removeWorkspacePanel();
|
|
504
575
|
removeOpenMemory();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,16 +51,16 @@
|
|
|
51
51
|
"typecheck": "tsc --noEmit -p tsconfig.client.json && tsc --noEmit -p tsconfig.server.json && tsc --noEmit -p tsconfig.tests.json"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@getpaseo/protocol": "0.
|
|
54
|
+
"@getpaseo/protocol": "0.11.0-beta.3",
|
|
55
55
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
56
56
|
"yaml": "^2.9.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@biomejs/biome": "^2.5.10",
|
|
60
|
-
"@getpaseo/cli": "0.
|
|
61
|
-
"@getpaseo/client": "0.
|
|
62
|
-
"@getpaseo/plugin": "0.
|
|
63
|
-
"@getpaseo/server": "0.
|
|
60
|
+
"@getpaseo/cli": "0.11.0-beta.3",
|
|
61
|
+
"@getpaseo/client": "0.11.0-beta.3",
|
|
62
|
+
"@getpaseo/plugin": "0.11.0-beta.3",
|
|
63
|
+
"@getpaseo/server": "0.11.0-beta.3",
|
|
64
64
|
"@oh-my-pi/pi-agent-core": "18.2.0",
|
|
65
65
|
"@oh-my-pi/pi-coding-agent": "18.2.0",
|
|
66
66
|
"@tanstack/react-query": "^5.102.3",
|
package/paseo-plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "paseo-omp",
|
|
3
3
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
4
|
-
"requirements": { "paseo": "^0.9.2 || ^0.10.0" },
|
|
4
|
+
"requirements": { "paseo": "^0.9.2 || ^0.10.0 || ^0.11.0" },
|
|
5
5
|
"build": [["node", "scripts/prepare-dependencies.mjs"]]
|
|
6
6
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "1.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "1.2.0"; // x-release-please-version
|