@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 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
+ ![OMP native plugin manager](https://raw.githubusercontent.com/omercnet/paseo-plugins/main/paseo-omp/docs/images/plugin-manager.png)
16
+
17
+ ### Typed OMP configuration
18
+
19
+ ![OMP typed configuration editor](https://raw.githubusercontent.com/omercnet/paseo-plugins/main/paseo-omp/docs/images/workspace-settings.png)
20
+
7
21
  ## Quick start
8
22
 
9
- Requirements: Paseo `>=0.8.0 <0.10.0`, OMP `18.1.15` or newer, and OMP RPC protocol v2. Paseo `0.9.0-beta.1` or newer is required for nested provider-subagent ancestry and spawning-tool links, and is recommended for npm-managed installation and the complete client integration.
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 inspect storage and process diagnostics. 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.
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.8 and 0.9 beta provider contracts and registers distinct `omp-plugin` and `omp-plugin-<profile>` identities; it does not modify the bundled `omp` provider.
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
- Paseo 0.8 does not pass agent launch options to its model picker. Separate provider identities let the 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`.
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 the first release that preserves nested provider-subagent ancestry and spawning-tool links. The required full Docker canary passes there with OMP 18.1.15 and 18.2.0. Paseo 0.9 also provides npm-managed plugin sources, platform-owned external URL opening, and whole-item timeline transforms before Overview grouping. Paseo 0.8.x remains supported for other provider operations and Git or directory installation; its clients do not provide the external URL opener, retain their older timeline presentation semantics, and flatten nested provider subagents.
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(createProfileOmpProvider(profile, { browserAuthorizationRegistry }));
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(createOmpProvider({ browserAuthorizationRegistry }));
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.108.1",
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
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "id": "paseo-omp",
3
- "requirements": { "paseo": ">=0.8.0 <0.10.0" },
3
+ "requirements": { "paseo": ">=0.9.0-beta.1 <0.10.0" },
4
4
  "build": [["node", "scripts/prepare-dependencies.mjs"]]
5
5
  }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ /** Release Please keeps this bundle-safe constant aligned with package.json. */
2
+ export const PASEO_OMP_PACKAGE_VERSION = "0.3.0-next.111.1"; // x-release-please-version