@omercnet/paseo-omp 0.3.0-next.108.1 → 0.3.0-next.111.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/README.md +19 -5
- package/client/omp-config-surface.tsx +163 -14
- package/client/omp-config-views.ts +24 -0
- package/client/support-diagnostics-state.ts +45 -0
- package/index.server.ts +24 -2
- package/package.json +2 -8
- package/paseo-plugin.json +1 -1
- package/server/operational-failure-diagnostics.ts +76 -0
- package/server/package-version.ts +2 -0
- package/server/protocol-violation-diagnostics.ts +169 -0
- package/server/provider/connection.ts +25 -0
- package/server/provider/host-tools.ts +63 -10
- package/server/provider/omp-rpc.ts +469 -44
- package/server/provider/profile-providers.ts +7 -2
- package/server/provider/registration.ts +12 -2
- package/server/provider/session.ts +70 -3
- package/server/support-diagnostics.ts +284 -0
- package/shared/support-diagnostics.ts +32 -0
- package/CHANGELOG.md +0 -113
- package/SUPPORT.md +0 -44
- package/TESTING.md +0 -150
- package/docs/alpha-release-checklist.md +0 -68
- package/docs/configuration.md +0 -126
- package/docs/core-provider-issue-audit.md +0 -109
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/model-role-picker-compact.webp +0 -0
- package/docs/images/model-role-picker-desktop.webp +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +0 -89
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -4,16 +4,30 @@ Community OMP integration for Paseo. The plugin registers the distinct `omp-plug
|
|
|
4
4
|
|
|
5
5
|
> **Alpha preview:** persistence and protocol contracts are tested, but upgrades may still require re-importing sessions created by an earlier preview.
|
|
6
6
|
|
|
7
|
+
## Screenshots
|
|
8
|
+
|
|
9
|
+
Both PNGs come from the plugin's isolated Paseo development daemon at 2× pixel density. Browser
|
|
10
|
+
DevTools rewrites local filesystem paths and verifies that no private organization names remain
|
|
11
|
+
before capture.
|
|
12
|
+
|
|
13
|
+
### Native plugin manager
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
### Typed OMP configuration
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
7
21
|
## Quick start
|
|
8
22
|
|
|
9
|
-
Requirements: Paseo `>=0.
|
|
23
|
+
Requirements: Paseo `>=0.9.0-beta.1 <0.10.0`, OMP `18.1.15` or newer, and OMP RPC protocol v2. Paseo 0.9 is required for the Help report's supported copy and external-link APIs, nested provider-subagent ancestry, spawning-tool links, npm-managed installation, and the complete client integration.
|
|
10
24
|
|
|
11
25
|
```bash
|
|
12
26
|
paseo plugin install npm:@omercnet/paseo-omp@<version>
|
|
13
27
|
paseo plugin ls paseo-omp
|
|
14
28
|
```
|
|
15
29
|
|
|
16
|
-
Open the **OMP** sidebar to review the provider-profile contract, choose which composer pills appear, browse and edit native scalar settings, manage OMP-native plugins, verify runtime health, and
|
|
30
|
+
Open the **OMP** sidebar to review the provider-profile contract, choose which composer pills appear, browse and edit native scalar settings, manage OMP-native plugins, verify runtime health, and generate a bounded support report under **Help**. The same tabs are available in the workspace OMP panel, except for the global Composer preference tab. Then create an agent and select **OMP Plugin**. Its optional **MCP** composer control runs OMP-native management commands in the current session; setup questions and authorization stay in that chat timeline, where OAuth can open in a workspace-scoped Paseo Browser or on the current device. Composer pill preferences are shared by clients connected to the same host; normal use requires no other plugin-specific settings.
|
|
17
31
|
|
|
18
32
|
- [Install, update, rollback, and local development](docs/installation.md)
|
|
19
33
|
- [Configuration and every provider option](docs/configuration.md)
|
|
@@ -22,13 +36,13 @@ Open the **OMP** sidebar to review the provider-profile contract, choose which c
|
|
|
22
36
|
- [Core-provider issue and parity audit](docs/core-provider-issue-audit.md)
|
|
23
37
|
- [Alpha release checklist](docs/alpha-release-checklist.md)
|
|
24
38
|
|
|
25
|
-
The plugin uses public Paseo 0.
|
|
39
|
+
The plugin uses public Paseo 0.9 beta provider and client contracts and registers distinct `omp-plugin` and `omp-plugin-<profile>` identities; it does not modify the bundled `omp` provider.
|
|
26
40
|
|
|
27
41
|
## Named OMP profiles
|
|
28
42
|
|
|
29
43
|
At startup the plugin discovers directory names under `~/.omp/profiles/` (or `PI_CONFIG_DIR`) and registers **OMP · <profile>** for each valid lowercase name. Select that provider when creating a profile-backed agent. Discovery, launch, recovery and persisted session listing share its fixed profile and session root. Matching command wrappers, including Doppler, remain supported; conflicting profile or session-directory overrides fail before launch. Plain `env NAME=value omp` wrappers are supported, but environment-control flags such as `env -i` and `env -u` are rejected because they can discard the selected store. Profile-specific XDG roots cannot be overridden by per-launch environment values. Reload the plugin after adding a profile.
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
Separate provider identities let the model picker request the correct profile catalog before an agent exists. Existing `omp-plugin` and bundled `omp` agents retain their provider; they are not migrated automatically. Profile names use OMP's lowercase grammar, including dotted names such as `team.prod`.
|
|
32
46
|
|
|
33
47
|
The OMP sidebar and memory panel expose an explicit store selector. Settings, plugins, quota, history, memory and diagnostics use that selection, and profile-agent popovers derive it from the provider ID. OMP-compatible XDG data and state roots are honored when a migrated profile uses them. Default-provider views are labelled **Daemon default store**; changing `providerOptions.command` on the default provider does not make those views profile-aware. Workspace configuration remains project-scoped, with the selected profile supplying inherited settings. Hub records are daemon-wide. The RPCs also accept an explicit absolute `store.agentDir` for custom stores; it is mutually exclusive with `store.profile`.
|
|
34
48
|
|
|
@@ -99,7 +113,7 @@ Existing agents whose provider is `omp` remain owned by the bundled provider. Ne
|
|
|
99
113
|
|
|
100
114
|
OMP `18.1.15` is the oldest version tested end to end. The direct provider's hard compatibility gate is `rpc-ui` protocol v2: metadata-free legacy ready frames and v1-only runtimes are rejected before a provider session opens because they cannot support the advertised persistence and conversation-rewind capabilities. Typed tool approvals remain capability-gated and fall back as described above. For terminal completion, a matching `agent_end.requestId` is authoritative and mismatches are ignored before state changes. Released OMP 18.2.x builds that omit that field use an ordered fallback requiring a fresh correlated native user entry followed by current-turn assistant activity, an idle non-compacting runtime, and no conflicting permission, tool, steer, or child-session work. Ambiguous events while OMP is active are ignored; unresolved ambiguity after confirmed idle fails only the Paseo turn. This trades a bounded residual same-agent stale-event risk for compatibility with released OMP instead of terminating and lazily restarting the runtime after every later prompt. Paseo clients, including mobile clients inside reconnect grace, do not own or terminate the daemon-managed OMP session.
|
|
101
115
|
|
|
102
|
-
Paseo 0.9.0-beta.1 is the verified canary target and
|
|
116
|
+
Paseo 0.9.0-beta.1 is the verified canary target and minimum supported release. It preserves nested provider-subagent ancestry and spawning-tool links, and provides npm-managed plugin sources, platform-owned external URL opening, and whole-item timeline transforms before Overview grouping. The required full Docker canary passes there with OMP 18.1.15 and 18.2.0.
|
|
103
117
|
|
|
104
118
|
Install, update, disable, or remove `paseo-omp` independently of the bundled provider. Verify the provider snapshot contains `omp-plugin` after installation; a bundled `omp` entry may remain present and is not modified by this plugin.
|
|
105
119
|
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
useRpc,
|
|
6
6
|
useWorkspace,
|
|
7
7
|
} from "@getpaseo/plugin/client";
|
|
8
|
-
import { Icon, TextInput } from "@getpaseo/plugin/client/react-native";
|
|
8
|
+
import { copyText, Icon, TextInput } from "@getpaseo/plugin/client/react-native";
|
|
9
|
+
import { ExternalLink } from "@getpaseo/plugin/client/ui";
|
|
9
10
|
import { useIsMutating, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
10
11
|
import type { ReactNode } from "react";
|
|
11
12
|
import { useCallback, useMemo, useState } from "react";
|
|
@@ -27,8 +28,10 @@ import {
|
|
|
27
28
|
} from "../shared/omp-settings";
|
|
28
29
|
import { type OmpStore, storeLabel } from "../shared/omp-store";
|
|
29
30
|
import { getOmpProviderHealth, type OmpProviderHealth } from "../shared/provider-diagnostics";
|
|
31
|
+
import { getOmpSupportReport, OMP_SUPPORT_ISSUE_URL } from "../shared/support-diagnostics";
|
|
30
32
|
import { ComposerPillSettingsSection } from "./composer-pill-settings";
|
|
31
33
|
import { openOmpExternalUrl } from "./external-url";
|
|
34
|
+
import { type OmpConfigSurfaceView as SurfaceView, surfaceViewsForScope } from "./omp-config-views";
|
|
32
35
|
import {
|
|
33
36
|
documentationForSettingCategory,
|
|
34
37
|
documentationForSettingPath,
|
|
@@ -60,6 +63,11 @@ import {
|
|
|
60
63
|
summarizeProviderStatus,
|
|
61
64
|
summarizeRpcUiSupport,
|
|
62
65
|
} from "./provider-diagnostics-state";
|
|
66
|
+
import {
|
|
67
|
+
refreshSupportReport,
|
|
68
|
+
type SupportReportCopyState,
|
|
69
|
+
supportDiagnosticsViewState,
|
|
70
|
+
} from "./support-diagnostics-state";
|
|
63
71
|
|
|
64
72
|
const SETTINGS_QUERY_KEY = ["paseo-omp", "settings"] as const;
|
|
65
73
|
const CONFIG_POLL_MS = 30_000;
|
|
@@ -95,6 +103,10 @@ export interface OmpConfigStyles {
|
|
|
95
103
|
docLinkPressed: ViewStyle;
|
|
96
104
|
docLinkText: TextStyle;
|
|
97
105
|
refreshLabel: TextStyle;
|
|
106
|
+
helpActions: ViewStyle;
|
|
107
|
+
helpReport: ViewStyle;
|
|
108
|
+
helpReportText: TextStyle;
|
|
109
|
+
helpLinkText: TextStyle;
|
|
98
110
|
card: ViewStyle;
|
|
99
111
|
cardHeader: ViewStyle;
|
|
100
112
|
cardTitle: TextStyle;
|
|
@@ -219,6 +231,26 @@ function useConfigStyles(theme: PluginSurfaceProps["theme"], compact: boolean):
|
|
|
219
231
|
backgroundColor: theme.colors.surface1,
|
|
220
232
|
},
|
|
221
233
|
refreshLabel: { color: theme.colors.foreground, fontSize: 13 },
|
|
234
|
+
helpActions: {
|
|
235
|
+
flexDirection: "row",
|
|
236
|
+
alignItems: "center",
|
|
237
|
+
flexWrap: "wrap",
|
|
238
|
+
gap: 8,
|
|
239
|
+
},
|
|
240
|
+
helpReport: {
|
|
241
|
+
maxWidth: "100%",
|
|
242
|
+
padding: compact ? 10 : 12,
|
|
243
|
+
borderWidth: 1,
|
|
244
|
+
borderColor: theme.colors.border,
|
|
245
|
+
borderRadius: 10,
|
|
246
|
+
backgroundColor: theme.colors.surface1,
|
|
247
|
+
},
|
|
248
|
+
helpReportText: {
|
|
249
|
+
color: theme.colors.foreground,
|
|
250
|
+
fontSize: compact ? 11 : 12,
|
|
251
|
+
lineHeight: compact ? 16 : 18,
|
|
252
|
+
},
|
|
253
|
+
helpLinkText: { color: theme.colors.accent, fontSize: 13, fontWeight: "600" },
|
|
222
254
|
cardHeader: {
|
|
223
255
|
flexDirection: "row",
|
|
224
256
|
alignItems: "center",
|
|
@@ -695,6 +727,131 @@ function ProviderHealthSection({
|
|
|
695
727
|
);
|
|
696
728
|
}
|
|
697
729
|
|
|
730
|
+
function SupportDiagnosticsSection({
|
|
731
|
+
theme,
|
|
732
|
+
styles,
|
|
733
|
+
cwd,
|
|
734
|
+
store,
|
|
735
|
+
}: {
|
|
736
|
+
theme: PluginSurfaceProps["theme"];
|
|
737
|
+
styles: OmpConfigStyles;
|
|
738
|
+
cwd?: string;
|
|
739
|
+
store?: OmpStore;
|
|
740
|
+
}) {
|
|
741
|
+
const loadReport = useRpc(getOmpSupportReport);
|
|
742
|
+
const queryClient = useQueryClient();
|
|
743
|
+
const context = { store, ...(cwd ? { cwd } : {}) };
|
|
744
|
+
const queryKey = ["paseo-omp", "support-report", ompStoreKey(store), cwd ?? "global"];
|
|
745
|
+
const report = useQuery({
|
|
746
|
+
queryKey,
|
|
747
|
+
queryFn: () => loadReport(context),
|
|
748
|
+
staleTime: Number.POSITIVE_INFINITY,
|
|
749
|
+
retry: false,
|
|
750
|
+
});
|
|
751
|
+
const refreshReport = useMutation({
|
|
752
|
+
mutationFn: () => refreshSupportReport(loadReport, context),
|
|
753
|
+
onSuccess: (value) => queryClient.setQueryData(queryKey, value),
|
|
754
|
+
});
|
|
755
|
+
const [copyState, setCopyState] = useState<SupportReportCopyState>("idle");
|
|
756
|
+
const [linkError, setLinkError] = useState(false);
|
|
757
|
+
const viewState = supportDiagnosticsViewState({
|
|
758
|
+
loading: report.isLoading,
|
|
759
|
+
refreshing: refreshReport.isPending || (report.isFetching && !report.isLoading),
|
|
760
|
+
hasReport: Boolean(report.data?.report),
|
|
761
|
+
reportFailed: Boolean(report.error || refreshReport.error),
|
|
762
|
+
copyState,
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
const refresh = () => {
|
|
766
|
+
setCopyState("idle");
|
|
767
|
+
refreshReport.mutate();
|
|
768
|
+
};
|
|
769
|
+
const copyReport = async () => {
|
|
770
|
+
if (!report.data?.report) return;
|
|
771
|
+
setCopyState("copying");
|
|
772
|
+
try {
|
|
773
|
+
await copyText(report.data.report);
|
|
774
|
+
setCopyState("copied");
|
|
775
|
+
} catch {
|
|
776
|
+
setCopyState("error");
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
return (
|
|
781
|
+
<>
|
|
782
|
+
<View style={styles.sectionHeader}>
|
|
783
|
+
<Text style={styles.sectionTitle}>Support diagnostics</Text>
|
|
784
|
+
<Text style={styles.muted}>
|
|
785
|
+
Safe OMP runtime, compatibility, storage-state, and protocol counters for maintainers.
|
|
786
|
+
</Text>
|
|
787
|
+
</View>
|
|
788
|
+
<View style={styles.helpActions}>
|
|
789
|
+
<Pressable
|
|
790
|
+
accessibilityRole="button"
|
|
791
|
+
accessibilityLabel="Refresh OMP support report"
|
|
792
|
+
disabled={report.isFetching || refreshReport.isPending}
|
|
793
|
+
onPress={refresh}
|
|
794
|
+
style={styles.refresh}
|
|
795
|
+
>
|
|
796
|
+
<Icon name="RefreshCw" size={14} color={theme.colors.foreground} />
|
|
797
|
+
<Text style={styles.refreshLabel}>{viewState.refreshLabel}</Text>
|
|
798
|
+
</Pressable>
|
|
799
|
+
<Pressable
|
|
800
|
+
accessibilityRole="button"
|
|
801
|
+
accessibilityLabel="Copy OMP support report"
|
|
802
|
+
disabled={viewState.copyDisabled}
|
|
803
|
+
onPress={() => void copyReport()}
|
|
804
|
+
style={[styles.refresh, viewState.copyDisabled ? { opacity: 0.55 } : null]}
|
|
805
|
+
>
|
|
806
|
+
<Icon name="Copy" size={14} color={theme.colors.foreground} />
|
|
807
|
+
<Text style={styles.refreshLabel}>{viewState.copyLabel}</Text>
|
|
808
|
+
</Pressable>
|
|
809
|
+
<ExternalLink
|
|
810
|
+
href={OMP_SUPPORT_ISSUE_URL}
|
|
811
|
+
accessibilityLabel="Create paseo-omp GitHub issue"
|
|
812
|
+
onError={() => setLinkError(true)}
|
|
813
|
+
>
|
|
814
|
+
<Text style={styles.helpLinkText}>Create GitHub issue</Text>
|
|
815
|
+
</ExternalLink>
|
|
816
|
+
</View>
|
|
817
|
+
<Text style={styles.muted}>
|
|
818
|
+
Review before sharing. The report excludes prompts, transcripts, credentials, commands,
|
|
819
|
+
URLs, repository names, and private paths.
|
|
820
|
+
</Text>
|
|
821
|
+
{viewState.loadingMessage ? (
|
|
822
|
+
<Text accessibilityLiveRegion="polite" style={styles.muted}>
|
|
823
|
+
{viewState.loadingMessage}
|
|
824
|
+
</Text>
|
|
825
|
+
) : null}
|
|
826
|
+
{viewState.reportError ? (
|
|
827
|
+
<Text accessibilityRole="alert" style={styles.error}>
|
|
828
|
+
{viewState.reportError}
|
|
829
|
+
</Text>
|
|
830
|
+
) : null}
|
|
831
|
+
{viewState.copyFeedback ? (
|
|
832
|
+
<Text
|
|
833
|
+
accessibilityLiveRegion="polite"
|
|
834
|
+
style={copyState === "error" ? styles.error : styles.muted}
|
|
835
|
+
>
|
|
836
|
+
{viewState.copyFeedback}
|
|
837
|
+
</Text>
|
|
838
|
+
) : null}
|
|
839
|
+
{linkError ? (
|
|
840
|
+
<Text accessibilityRole="alert" style={styles.error}>
|
|
841
|
+
Could not open GitHub. Copy the report and open the issue tracker manually.
|
|
842
|
+
</Text>
|
|
843
|
+
) : null}
|
|
844
|
+
{report.data?.report ? (
|
|
845
|
+
<View style={styles.helpReport}>
|
|
846
|
+
<Text selectable style={styles.helpReportText}>
|
|
847
|
+
{report.data.report}
|
|
848
|
+
</Text>
|
|
849
|
+
</View>
|
|
850
|
+
) : null}
|
|
851
|
+
</>
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
698
855
|
const CATEGORY_LABELS: Record<OmpSettingCategory, string> = {
|
|
699
856
|
appearance: "Appearance",
|
|
700
857
|
model: "Model",
|
|
@@ -740,16 +897,6 @@ function fallbackSettingsFromConfig(config: OmpConfig | null | undefined): OmpSe
|
|
|
740
897
|
return settings;
|
|
741
898
|
}
|
|
742
899
|
|
|
743
|
-
type SurfaceView = "overview" | "plugin" | "plugins" | "composer" | "configuration" | "diagnostics";
|
|
744
|
-
const SURFACE_VIEWS: readonly { id: SurfaceView; label: string }[] = [
|
|
745
|
-
{ id: "overview", label: "Overview" },
|
|
746
|
-
{ id: "plugin", label: "Plugin" },
|
|
747
|
-
{ id: "plugins", label: "OMP plugins" },
|
|
748
|
-
{ id: "composer", label: "Composer" },
|
|
749
|
-
{ id: "configuration", label: "Configuration" },
|
|
750
|
-
{ id: "diagnostics", label: "Diagnostics" },
|
|
751
|
-
];
|
|
752
|
-
|
|
753
900
|
function formatScalarValue(value: unknown): string {
|
|
754
901
|
if (value === undefined || value === null) return "Not set";
|
|
755
902
|
if (typeof value === "boolean") return value ? "Enabled" : "Disabled";
|
|
@@ -1079,9 +1226,7 @@ function OmpConfigContent({
|
|
|
1079
1226
|
const [documentationError, setDocumentationError] = useState<string | null>(null);
|
|
1080
1227
|
const styles = useConfigStyles(theme, layout.compact);
|
|
1081
1228
|
const normalizedSearch = search.trim().toLocaleLowerCase();
|
|
1082
|
-
const surfaceViews = cwd
|
|
1083
|
-
? SURFACE_VIEWS.filter((candidate) => candidate.id !== "composer")
|
|
1084
|
-
: SURFACE_VIEWS;
|
|
1229
|
+
const surfaceViews = surfaceViewsForScope(cwd !== undefined);
|
|
1085
1230
|
const catalog = useMemo(() => {
|
|
1086
1231
|
const sourceSettings = settingsQuery.data?.available
|
|
1087
1232
|
? settingsQuery.data.settings
|
|
@@ -1271,6 +1416,10 @@ function OmpConfigContent({
|
|
|
1271
1416
|
<ProviderHealthSection theme={theme} styles={styles} cwd={cwd} store={store} />
|
|
1272
1417
|
) : null}
|
|
1273
1418
|
|
|
1419
|
+
{view === "help" ? (
|
|
1420
|
+
<SupportDiagnosticsSection theme={theme} styles={styles} cwd={cwd} store={store} />
|
|
1421
|
+
) : null}
|
|
1422
|
+
|
|
1274
1423
|
{view === "configuration" ? (
|
|
1275
1424
|
<>
|
|
1276
1425
|
<View style={styles.sectionHeader}>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type OmpConfigSurfaceView =
|
|
2
|
+
| "overview"
|
|
3
|
+
| "plugin"
|
|
4
|
+
| "plugins"
|
|
5
|
+
| "composer"
|
|
6
|
+
| "configuration"
|
|
7
|
+
| "diagnostics"
|
|
8
|
+
| "help";
|
|
9
|
+
|
|
10
|
+
const SURFACE_VIEWS: readonly { id: OmpConfigSurfaceView; label: string }[] = [
|
|
11
|
+
{ id: "overview", label: "Overview" },
|
|
12
|
+
{ id: "plugin", label: "Plugin" },
|
|
13
|
+
{ id: "plugins", label: "OMP plugins" },
|
|
14
|
+
{ id: "composer", label: "Composer" },
|
|
15
|
+
{ id: "configuration", label: "Configuration" },
|
|
16
|
+
{ id: "diagnostics", label: "Diagnostics" },
|
|
17
|
+
{ id: "help", label: "Help" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export function surfaceViewsForScope(workspaceScoped: boolean) {
|
|
21
|
+
return workspaceScoped
|
|
22
|
+
? SURFACE_VIEWS.filter((candidate) => candidate.id !== "composer")
|
|
23
|
+
: SURFACE_VIEWS;
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { OmpStore } from "../shared/omp-store";
|
|
2
|
+
|
|
3
|
+
type SupportReportInput = { store?: OmpStore; cwd?: string; force?: boolean };
|
|
4
|
+
type SupportReportOutput = { report: string };
|
|
5
|
+
|
|
6
|
+
export async function refreshSupportReport(
|
|
7
|
+
loadReport: (input: SupportReportInput) => Promise<SupportReportOutput>,
|
|
8
|
+
input: Omit<SupportReportInput, "force">,
|
|
9
|
+
): Promise<SupportReportOutput> {
|
|
10
|
+
return await loadReport({ ...input, force: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type SupportReportCopyState = "idle" | "copying" | "copied" | "error";
|
|
14
|
+
|
|
15
|
+
export interface SupportDiagnosticsViewState {
|
|
16
|
+
refreshLabel: string;
|
|
17
|
+
copyLabel: string;
|
|
18
|
+
copyDisabled: boolean;
|
|
19
|
+
loadingMessage: string | null;
|
|
20
|
+
reportError: string | null;
|
|
21
|
+
copyFeedback: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Maps request and copy state to the exact accessible feedback rendered by the Help tab. */
|
|
25
|
+
export function supportDiagnosticsViewState(input: {
|
|
26
|
+
loading: boolean;
|
|
27
|
+
refreshing: boolean;
|
|
28
|
+
hasReport: boolean;
|
|
29
|
+
reportFailed: boolean;
|
|
30
|
+
copyState: SupportReportCopyState;
|
|
31
|
+
}): SupportDiagnosticsViewState {
|
|
32
|
+
return {
|
|
33
|
+
refreshLabel: input.refreshing ? "Refreshing…" : "Refresh",
|
|
34
|
+
copyLabel: input.copyState === "copying" ? "Copying…" : "Copy report",
|
|
35
|
+
copyDisabled: !input.hasReport || input.copyState === "copying",
|
|
36
|
+
loadingMessage: input.loading ? "Collecting OMP diagnostics…" : null,
|
|
37
|
+
reportError: input.reportFailed ? "Could not collect OMP diagnostics." : null,
|
|
38
|
+
copyFeedback:
|
|
39
|
+
input.copyState === "copied"
|
|
40
|
+
? "Report copied."
|
|
41
|
+
: input.copyState === "error"
|
|
42
|
+
? "Could not copy. Select the report text and copy it manually."
|
|
43
|
+
: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
package/index.server.ts
CHANGED
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
resolveMutateOmpPluginConfig,
|
|
15
15
|
} from "./server/omp-plugins";
|
|
16
16
|
import { resolveListOmpSettings, resolveUpdateOmpSettings } from "./server/omp-settings";
|
|
17
|
+
import { OmpOperationalFailureCollector } from "./server/operational-failure-diagnostics";
|
|
17
18
|
import { withOmpStore } from "./server/paths";
|
|
19
|
+
import { OmpProtocolViolationCollector } from "./server/protocol-violation-diagnostics";
|
|
18
20
|
import { withOmpWorkspaceIdentity } from "./server/provider/host-tools";
|
|
19
21
|
import {
|
|
20
22
|
createProfileOmpProvider,
|
|
@@ -25,6 +27,7 @@ import { createOmpProvider } from "./server/provider/registration";
|
|
|
25
27
|
import { resolveGetOmpProviderHealth } from "./server/provider-diagnostics";
|
|
26
28
|
import { resolveListOmpQuotas } from "./server/quota";
|
|
27
29
|
import { resolveListOmpSessions } from "./server/sessions";
|
|
30
|
+
import { resolveGetOmpSupportReport } from "./server/support-diagnostics";
|
|
28
31
|
import { composerPillSettings } from "./shared/composer-pill-settings";
|
|
29
32
|
import { listHubProcesses, tailHubLog } from "./shared/hub";
|
|
30
33
|
import { openOmpMcpAuthorizationInPaseoBrowser } from "./shared/mcp";
|
|
@@ -42,6 +45,7 @@ import { listOmpStores, type OmpStore } from "./shared/omp-store";
|
|
|
42
45
|
import { getOmpProviderHealth } from "./shared/provider-diagnostics";
|
|
43
46
|
import { listOmpQuotas } from "./shared/quota";
|
|
44
47
|
import { listOmpSessions } from "./shared/sessions";
|
|
48
|
+
import { getOmpSupportReport } from "./shared/support-diagnostics";
|
|
45
49
|
|
|
46
50
|
function scoped<T extends { store?: OmpStore }, R>(handler: (input: T) => R) {
|
|
47
51
|
return (input: T): R => withOmpStore(input.store, () => handler(input));
|
|
@@ -50,10 +54,18 @@ function scoped<T extends { store?: OmpStore }, R>(handler: (input: T) => R) {
|
|
|
50
54
|
export default function contribute(server: PluginServerContext) {
|
|
51
55
|
server.registerSettings(composerPillSettings);
|
|
52
56
|
const browserAuthorizationRegistry = new OmpBrowserAuthorizationRegistry();
|
|
57
|
+
const protocolViolations = new OmpProtocolViolationCollector();
|
|
58
|
+
const operationalFailures = new OmpOperationalFailureCollector();
|
|
53
59
|
const profiles = discoverOmpProfilesSync();
|
|
54
60
|
server.handle(listOmpStores, async () => ({ profiles: await discoverOmpProfiles() }));
|
|
55
61
|
for (const profile of profiles) {
|
|
56
|
-
server.registerProvider(
|
|
62
|
+
server.registerProvider(
|
|
63
|
+
createProfileOmpProvider(profile, {
|
|
64
|
+
browserAuthorizationRegistry,
|
|
65
|
+
reportProtocolViolation: protocolViolations.report,
|
|
66
|
+
reportOperationalFailure: operationalFailures.report,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
57
69
|
}
|
|
58
70
|
server.handle(listHubProcesses, resolveListHubProcesses);
|
|
59
71
|
server.handle(tailHubLog, resolveTailHubLog);
|
|
@@ -69,6 +81,10 @@ export default function contribute(server: PluginServerContext) {
|
|
|
69
81
|
server.handle(listOmpSettings, scoped(resolveListOmpSettings));
|
|
70
82
|
server.handle(updateOmpSettings, scoped(resolveUpdateOmpSettings));
|
|
71
83
|
server.handle(getOmpProviderHealth, scoped(resolveGetOmpProviderHealth));
|
|
84
|
+
server.handle(
|
|
85
|
+
getOmpSupportReport,
|
|
86
|
+
scoped((input) => resolveGetOmpSupportReport(input, protocolViolations, operationalFailures)),
|
|
87
|
+
);
|
|
72
88
|
server.handle(openOmpMcpAuthorizationInPaseoBrowser, (input) =>
|
|
73
89
|
resolveOpenOmpMcpAuthorizationInPaseoBrowser(input, browserAuthorizationRegistry),
|
|
74
90
|
);
|
|
@@ -76,7 +92,13 @@ export default function contribute(server: PluginServerContext) {
|
|
|
76
92
|
if (request.provider !== "omp-plugin" && !request.provider.startsWith("omp-plugin-")) return;
|
|
77
93
|
return withOmpWorkspaceIdentity(request);
|
|
78
94
|
});
|
|
79
|
-
server.registerProvider(
|
|
95
|
+
server.registerProvider(
|
|
96
|
+
createOmpProvider({
|
|
97
|
+
browserAuthorizationRegistry,
|
|
98
|
+
reportProtocolViolation: protocolViolations.report,
|
|
99
|
+
reportOperationalFailure: operationalFailures.report,
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
80
102
|
return () => {
|
|
81
103
|
browserAuthorizationRegistry.clear();
|
|
82
104
|
removeIdentityHook();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.111.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,21 +29,15 @@
|
|
|
29
29
|
"coding-agents"
|
|
30
30
|
],
|
|
31
31
|
"files": [
|
|
32
|
-
"package-lock.json",
|
|
33
|
-
"CHANGELOG.md",
|
|
34
32
|
"LICENSE",
|
|
35
33
|
"README.md",
|
|
36
|
-
"SUPPORT.md",
|
|
37
|
-
"TESTING.md",
|
|
38
|
-
"docs",
|
|
39
34
|
"scripts/prepare-dependencies.mjs",
|
|
40
35
|
"index.client.tsx",
|
|
41
36
|
"index.server.ts",
|
|
42
37
|
"client",
|
|
43
38
|
"server",
|
|
44
39
|
"shared",
|
|
45
|
-
"paseo-plugin.json"
|
|
46
|
-
"tsconfig.json"
|
|
40
|
+
"paseo-plugin.json"
|
|
47
41
|
],
|
|
48
42
|
"scripts": {
|
|
49
43
|
"check": "biome check .",
|
package/paseo-plugin.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const OMP_OPERATIONAL_FAILURES = [
|
|
2
|
+
{ category: "session-open", stage: "startup" },
|
|
3
|
+
{ category: "session-open", stage: "catalog" },
|
|
4
|
+
{ category: "replay-recovery", stage: "persisted-replay" },
|
|
5
|
+
{ category: "replay-recovery", stage: "runtime-recovery" },
|
|
6
|
+
{ category: "replay-recovery", stage: "rewind" },
|
|
7
|
+
{ category: "tool-projector", stage: "host-tool-unknown" },
|
|
8
|
+
{ category: "tool-projector", stage: "host-tool-capacity" },
|
|
9
|
+
{ category: "tool-projector", stage: "host-tool-normalization" },
|
|
10
|
+
{ category: "tool-projector", stage: "host-tool-call" },
|
|
11
|
+
{ category: "tool-projector", stage: "host-tool-timeout" },
|
|
12
|
+
{ category: "tool-projector", stage: "host-tool-delivery" },
|
|
13
|
+
{ category: "tool-projector", stage: "timeline-projector" },
|
|
14
|
+
{ category: "tool-projector", stage: "subsession-projector" },
|
|
15
|
+
{ category: "terminal-outcome", stage: "failed" },
|
|
16
|
+
{ category: "terminal-outcome", stage: "unresolved" },
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type OmpOperationalFailure = (typeof OMP_OPERATIONAL_FAILURES)[number];
|
|
20
|
+
export type OmpOperationalFailureCategory = OmpOperationalFailure["category"];
|
|
21
|
+
export type OmpOperationalFailureStage = OmpOperationalFailure["stage"];
|
|
22
|
+
export type OmpOperationalFailureReporter = (failure: OmpOperationalFailure) => void;
|
|
23
|
+
|
|
24
|
+
export type OmpOperationalFailureSummary = OmpOperationalFailure & {
|
|
25
|
+
occurrenceCount: number;
|
|
26
|
+
firstAt: string | null;
|
|
27
|
+
lastAt: string | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const FAILURE_KEYS: Readonly<Record<string, true>> = Object.fromEntries(
|
|
31
|
+
OMP_OPERATIONAL_FAILURES.map(({ category, stage }) => [`${category}:${stage}`, true]),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
function timestamp(now: () => Date): string | null {
|
|
35
|
+
try {
|
|
36
|
+
const value = now();
|
|
37
|
+
return Number.isFinite(value.getTime()) ? value.toISOString() : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Fixed category/stage cells make aggregation constant-space for one plugin subprocess lifetime. */
|
|
44
|
+
export class OmpOperationalFailureCollector {
|
|
45
|
+
private readonly summaries = Object.fromEntries(
|
|
46
|
+
OMP_OPERATIONAL_FAILURES.map(({ category, stage }) => [
|
|
47
|
+
`${category}:${stage}`,
|
|
48
|
+
{ category, stage, occurrenceCount: 0, firstAt: null, lastAt: null },
|
|
49
|
+
]),
|
|
50
|
+
) as Record<string, OmpOperationalFailureSummary>;
|
|
51
|
+
|
|
52
|
+
constructor(private readonly now: () => Date = () => new Date()) {}
|
|
53
|
+
|
|
54
|
+
report: OmpOperationalFailureReporter = (failure) => {
|
|
55
|
+
try {
|
|
56
|
+
const key = `${failure.category}:${failure.stage}`;
|
|
57
|
+
if (!FAILURE_KEYS[key]) return;
|
|
58
|
+
const summary = this.summaries[key];
|
|
59
|
+
if (!summary) return;
|
|
60
|
+
summary.occurrenceCount = Math.min(Number.MAX_SAFE_INTEGER, summary.occurrenceCount + 1);
|
|
61
|
+
const recordedAt = timestamp(this.now);
|
|
62
|
+
if (recordedAt) {
|
|
63
|
+
summary.firstAt ??= recordedAt;
|
|
64
|
+
summary.lastAt = recordedAt;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Diagnostics must never affect provider or session flow.
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
snapshot(): OmpOperationalFailureSummary[] {
|
|
72
|
+
return OMP_OPERATIONAL_FAILURES.map(({ category, stage }) => ({
|
|
73
|
+
...this.summaries[`${category}:${stage}`],
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|