@omercnet/paseo-omp 0.2.1 → 0.3.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +25 -13
  3. package/SUPPORT.md +6 -2
  4. package/TESTING.md +21 -18
  5. package/client/composer-pill-settings.tsx +157 -0
  6. package/client/external-url.ts +15 -0
  7. package/client/mcp-authorization.tsx +169 -0
  8. package/client/mcp-popover.tsx +155 -0
  9. package/client/memory-panel.tsx +8 -3
  10. package/client/memory-popover.tsx +8 -4
  11. package/client/omp-config-surface.tsx +189 -29
  12. package/client/omp-plugin-manager.tsx +302 -131
  13. package/client/omp-store-picker.tsx +89 -0
  14. package/client/omp-store-state.ts +45 -0
  15. package/client/paseo-types.ts +9 -0
  16. package/client/provider-diagnostics-state.ts +18 -7
  17. package/client/quota-popover.tsx +8 -3
  18. package/client/quota-state.ts +16 -7
  19. package/client/sessions-popover.tsx +8 -3
  20. package/docs/alpha-release-checklist.md +6 -8
  21. package/docs/configuration.md +8 -4
  22. package/docs/core-provider-issue-audit.md +3 -2
  23. package/docs/images/mcp-authorization-compact.png +0 -0
  24. package/docs/images/mcp-controls-wide.png +0 -0
  25. package/docs/images/plugin-manager.png +0 -0
  26. package/docs/images/workspace-settings.png +0 -0
  27. package/docs/installation.md +35 -19
  28. package/index.client.tsx +339 -123
  29. package/index.server.ts +44 -14
  30. package/package.json +7 -8
  31. package/paseo-plugin.json +2 -2
  32. package/scripts/prepare-dependencies.mjs +24 -0
  33. package/server/mcp-browser.ts +95 -0
  34. package/server/memory.ts +2 -2
  35. package/server/omp-config.ts +16 -7
  36. package/server/omp-plugins.ts +70 -21
  37. package/server/omp-settings.ts +232 -24
  38. package/server/paths.ts +128 -11
  39. package/server/provider/catalog.ts +3 -4
  40. package/server/provider/connection.ts +213 -9
  41. package/server/provider/host-tools.ts +71 -0
  42. package/server/provider/omp-rpc.ts +82 -15
  43. package/server/provider/profile-providers.ts +249 -0
  44. package/server/provider/registration.ts +11 -0
  45. package/server/provider/session-descriptors.ts +306 -1
  46. package/server/provider/session.ts +704 -249
  47. package/server/provider/subsessions.ts +4 -1
  48. package/server/provider/timeline-projector.ts +70 -33
  49. package/server/provider-diagnostics.ts +122 -36
  50. package/server/quota.ts +3 -2
  51. package/server/sessions.ts +2 -2
  52. package/shared/composer-pill-settings.ts +28 -0
  53. package/shared/external-url.ts +21 -0
  54. package/shared/hub.ts +3 -3
  55. package/shared/mcp.ts +47 -0
  56. package/shared/memory.ts +2 -1
  57. package/shared/omp-config.ts +5 -1
  58. package/shared/omp-plugins.ts +74 -33
  59. package/shared/omp-settings.ts +8 -1
  60. package/shared/omp-store.ts +58 -0
  61. package/shared/provider-diagnostics.ts +12 -3
  62. package/shared/quota.ts +2 -1
  63. package/shared/sessions.ts +2 -1
@@ -0,0 +1,89 @@
1
+ import { type PluginSurfaceProps, useRpc } from "@getpaseo/plugin/client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { Pressable, Text, View } from "react-native";
4
+ import { listOmpStores, type OmpStore, storeLabel } from "../shared/omp-store";
5
+ import { ompStoreKey } from "./omp-store-state";
6
+
7
+ export function OmpStorePicker({
8
+ theme,
9
+ store,
10
+ onChange,
11
+ disabled = false,
12
+ }: {
13
+ theme: PluginSurfaceProps["theme"];
14
+ store?: OmpStore;
15
+ onChange(store: OmpStore | undefined): void;
16
+ disabled?: boolean;
17
+ }) {
18
+ const loadStores = useRpc(listOmpStores);
19
+ const stores = useQuery({
20
+ queryKey: ["paseo-omp", "stores"],
21
+ queryFn: () => loadStores({}),
22
+ staleTime: 30_000,
23
+ });
24
+ const profiles = [
25
+ ...new Set([...(stores.data?.profiles ?? []), ...(store?.profile ? [store.profile] : [])]),
26
+ ];
27
+ const choices: Array<OmpStore | undefined> = [
28
+ undefined,
29
+ ...profiles.map((profile) => ({ profile })),
30
+ ];
31
+ if (store?.agentDir) choices.push(store);
32
+ return (
33
+ <View style={{ gap: 8 }}>
34
+ <Text style={{ color: theme.colors.foreground, fontWeight: "600" }}>OMP store</Text>
35
+ <View
36
+ accessibilityRole="radiogroup"
37
+ style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}
38
+ >
39
+ {choices.map((choice) => {
40
+ const selected = ompStoreKey(choice) === ompStoreKey(store);
41
+ return (
42
+ <Pressable
43
+ key={ompStoreKey(choice)}
44
+ accessibilityRole="radio"
45
+ accessibilityLabel={storeLabel(choice)}
46
+ accessibilityState={{ checked: selected, disabled }}
47
+ aria-checked={selected}
48
+ aria-disabled={disabled}
49
+ disabled={disabled}
50
+ onPress={() => onChange(choice)}
51
+ style={{
52
+ borderWidth: 1,
53
+ borderRadius: 8,
54
+ paddingHorizontal: 10,
55
+ paddingVertical: 8,
56
+ borderColor: selected ? theme.colors.accent : theme.colors.border,
57
+ backgroundColor: theme.colors.surface1,
58
+ opacity: disabled ? 0.5 : 1,
59
+ }}
60
+ >
61
+ <Text
62
+ style={{ color: theme.colors.foreground, fontWeight: selected ? "600" : "400" }}
63
+ >
64
+ {selected ? "✓ " : ""}
65
+ {storeLabel(choice)}
66
+ </Text>
67
+ </Pressable>
68
+ );
69
+ })}
70
+ </View>
71
+ {stores.isLoading ? (
72
+ <Text style={{ color: theme.colors.foregroundMuted }}>Loading profiles…</Text>
73
+ ) : null}
74
+ {stores.error ? (
75
+ <Text style={{ color: theme.colors.statusDanger }}>
76
+ Could not list OMP profiles.{" "}
77
+ <Text
78
+ onPress={() => {
79
+ void stores.refetch();
80
+ }}
81
+ accessibilityRole="button"
82
+ >
83
+ Retry
84
+ </Text>
85
+ </Text>
86
+ ) : null}
87
+ </View>
88
+ );
89
+ }
@@ -0,0 +1,45 @@
1
+ import { type OmpStore, storeForProvider } from "../shared/omp-store";
2
+ import type { OmpQuota } from "../shared/quota";
3
+
4
+ export function ompStoreKey(store?: OmpStore): string {
5
+ return store?.profile
6
+ ? `profile:${store.profile}`
7
+ : store?.agentDir
8
+ ? `directory:${store.agentDir}`
9
+ : "default";
10
+ }
11
+
12
+ export function isOmpPluginProvider(provider?: string): boolean {
13
+ return provider === "omp-plugin" || storeForProvider(provider) !== undefined;
14
+ }
15
+
16
+ export function isOmpProvider(provider?: string): boolean {
17
+ return provider === "omp" || isOmpPluginProvider(provider);
18
+ }
19
+
20
+ /** Each store owns its own result and in-flight read. Failed reads never become empty success. */
21
+ export function createStoreQuotaLoader(
22
+ load: (input: { store?: OmpStore }) => Promise<{ quotas: OmpQuota[] }>,
23
+ maxAgeMs: number,
24
+ now = Date.now,
25
+ ) {
26
+ const cache = new Map<string, { value: { quotas: OmpQuota[] }; at: number }>();
27
+ const pending = new Map<string, Promise<{ quotas: OmpQuota[] }>>();
28
+ return (store?: OmpStore): Promise<{ quotas: OmpQuota[] }> => {
29
+ const key = ompStoreKey(store);
30
+ const cached = cache.get(key);
31
+ if (cached && now() - cached.at < maxAgeMs) return Promise.resolve(cached.value);
32
+ const current = pending.get(key);
33
+ if (current) return current;
34
+ const request = load({ store })
35
+ .then((value) => {
36
+ cache.set(key, { value, at: now() });
37
+ return value;
38
+ })
39
+ .finally(() => {
40
+ pending.delete(key);
41
+ });
42
+ pending.set(key, request);
43
+ return request;
44
+ };
45
+ }
@@ -0,0 +1,9 @@
1
+ import type { PluginClientContext } from "@getpaseo/plugin/client";
2
+
3
+ type AsyncResult<Callable> = Callable extends (...args: never[]) => Promise<infer Result>
4
+ ? Result
5
+ : never;
6
+
7
+ export type PaseoApi = PluginClientContext["paseo"];
8
+ export type PaseoAgentListResult = AsyncResult<PaseoApi["agents"]["list"]>;
9
+ export type PaseoProviderSnapshotResult = AsyncResult<PaseoApi["providers"]["snapshot"]>;
@@ -1,4 +1,3 @@
1
- import type { PaseoApi, PaseoProviderSnapshotResult } from "@getpaseo/client";
2
1
  import type {
3
2
  OmpLspDiagnostics,
4
3
  OmpMcpDiagnostics,
@@ -8,10 +7,11 @@ import type {
8
7
  OmpVersionStatus,
9
8
  PathState,
10
9
  } from "../shared/provider-diagnostics";
10
+ import { isOmpProvider } from "./omp-store-state";
11
+ import type { PaseoApi, PaseoProviderSnapshotResult } from "./paseo-types";
11
12
 
12
13
  export type ProviderHealthTone = "ok" | "warning" | "danger" | "muted";
13
14
  export const OMP_PROVIDER_IDS = ["omp", "omp-plugin"] as const;
14
- const OMP_PROVIDER_ID_SET: ReadonlySet<string> = new Set(OMP_PROVIDER_IDS);
15
15
 
16
16
  export function isUnsupportedHostError(error: unknown): boolean {
17
17
  if (typeof error !== "object" || error === null) return false;
@@ -40,6 +40,7 @@ export async function loadReadyProviderSnapshot(
40
40
 
41
41
  export interface RefreshDiagnosticsOptions {
42
42
  providers: ProviderActions;
43
+ providerIds?: readonly string[];
43
44
  loadForcedHealth(): Promise<OmpProviderHealth>;
44
45
  cacheHealth(health: OmpProviderHealth): void;
45
46
  cacheProviders(snapshot: PaseoProviderSnapshotResult): void;
@@ -51,7 +52,11 @@ export async function refreshProviderDiagnostics(
51
52
  options: RefreshDiagnosticsOptions,
52
53
  ): Promise<{ failed: boolean }> {
53
54
  const [providerRefresh, forcedHealth] = await Promise.allSettled([
54
- options.providers.refresh({ providers: [...OMP_PROVIDER_IDS] }),
55
+ options.providers.refresh({
56
+ providers: [
57
+ ...new Set([...OMP_PROVIDER_IDS, ...(options.providerIds ?? []).filter(isOmpProvider)]),
58
+ ],
59
+ }),
55
60
  options.loadForcedHealth(),
56
61
  ]);
57
62
  if (forcedHealth.status === "fulfilled") options.cacheHealth(forcedHealth.value);
@@ -152,15 +157,21 @@ export function summarizeProcessDiagnostics(diagnostics: OmpProcessDiagnostics):
152
157
  if (diagnostics.status === "unavailable") return "No hub run directory found";
153
158
  if (diagnostics.status === "unknown") return "Unknown (could not read the hub run directory)";
154
159
  const count = diagnostics.trackedCount ?? 0;
155
- if (diagnostics.status === "partial") return `${count} tracked (partial: some inaccessible)`;
156
- return `${count} tracked`;
160
+ const statesKnown =
161
+ diagnostics.activeCount != null &&
162
+ diagnostics.historicalCount != null &&
163
+ diagnostics.unknownCount != null;
164
+ const detail = statesKnown
165
+ ? `${diagnostics.activeCount} active-state, ${diagnostics.historicalCount} historical, ${diagnostics.unknownCount} unknown`
166
+ : "states not reported";
167
+ return `${count} metadata records (${detail}${diagnostics.status === "partial" ? "; partial access" : ""}); live processes not verified`;
157
168
  }
158
169
 
159
170
  export function processTone(diagnostics: OmpProcessDiagnostics): ProviderHealthTone {
160
171
  if (diagnostics.status === "unknown") return "warning";
161
172
  if (diagnostics.status === "partial") return "warning";
162
173
  if (diagnostics.status === "unavailable") return "muted";
163
- return diagnostics.trackedCount && diagnostics.trackedCount > 0 ? "ok" : "muted";
174
+ return "muted";
164
175
  }
165
176
 
166
177
  const PATH_STATE_LABELS: Record<PathState, string> = {
@@ -236,7 +247,7 @@ export function selectKnownOmpProviders(
236
247
  entries: readonly PaseoProviderSnapshotResult["entries"][number][],
237
248
  ): KnownOmpProviderSummary[] {
238
249
  return entries.flatMap((entry) =>
239
- OMP_PROVIDER_ID_SET.has(entry.provider)
250
+ isOmpProvider(entry.provider)
240
251
  ? [
241
252
  {
242
253
  id: entry.provider,
@@ -3,7 +3,9 @@ import { Icon } from "@getpaseo/plugin/client/react-native";
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import { useMemo } from "react";
5
5
  import { Text, View } from "react-native";
6
+ import { storeForProvider, storeLabel } from "../shared/omp-store";
6
7
  import { listOmpQuotas } from "../shared/quota";
8
+ import { ompStoreKey } from "./omp-store-state";
7
9
  import {
8
10
  type QuotaProviderGroup,
9
11
  quotaProviderFromSession,
@@ -21,10 +23,12 @@ export function QuotaPopover(props: PluginButtonContentProps) {
21
23
  const agentId = props.context === "agent" ? props.agentId : "";
22
24
  const session = useAgent(agentId, (agent) => ({ model: agent.model, provider: agent.provider }));
23
25
  const currentProvider = quotaProviderFromSession(session?.provider ?? "", session?.model ?? null);
26
+ const store = storeForProvider(session?.provider);
24
27
  const loadQuotas = useRpc(listOmpQuotas);
25
28
  const quotas = useQuery({
26
- queryKey: ["paseo-omp", "quotas"],
27
- queryFn: () => loadQuotas({}),
29
+ queryKey: ["paseo-omp", "quotas", ompStoreKey(store)],
30
+ queryFn: () => loadQuotas({ store }),
31
+ enabled: session !== undefined && session !== null,
28
32
  refetchInterval: QUOTA_POLL_MS,
29
33
  });
30
34
 
@@ -84,7 +88,7 @@ export function QuotaPopover(props: PluginButtonContentProps) {
84
88
  [layout.compact, theme],
85
89
  );
86
90
 
87
- if (quotas.isLoading) return <Text style={styles.muted}>Loading omp quotas…</Text>;
91
+ if (!session || quotas.isLoading) return <Text style={styles.muted}>Loading omp quotas…</Text>;
88
92
  if (quotas.error) return <Text style={styles.error}>Could not read omp quota state.</Text>;
89
93
 
90
94
  const groups = quotaProviderGroups(quotas.data?.quotas ?? [], currentProvider);
@@ -96,6 +100,7 @@ export function QuotaPopover(props: PluginButtonContentProps) {
96
100
 
97
101
  return (
98
102
  <View style={styles.root}>
103
+ <Text style={styles.muted}>{storeLabel(store)}</Text>
99
104
  {currentProvider && !hasCurrent ? (
100
105
  <Text style={styles.muted}>
101
106
  {`No recorded quota yet for ${quotaProviderLabel(currentProvider)} (this session's provider).`}
@@ -1,4 +1,5 @@
1
1
  import type { OmpQuota } from "../shared/quota";
2
+ import { isOmpProvider } from "./omp-store-state";
2
3
 
3
4
  const PROVIDER_LABELS: Record<string, string> = {
4
5
  anthropic: "Anthropic",
@@ -37,9 +38,9 @@ export function quotaProviderFromSession(
37
38
  model: string | null = null,
38
39
  ): string | null {
39
40
  const [runtime, modelProvider] = provider.split("/");
40
- if (runtime !== "omp") return null;
41
+ if (!isOmpProvider(runtime)) return null;
41
42
  if (modelProvider) return modelProvider;
42
- return model?.split("/")[0] ?? null;
43
+ return model?.includes("/") ? model.split("/")[0] : null;
43
44
  }
44
45
 
45
46
  export function quotaProviderLabel(provider: string | null): string {
@@ -51,32 +52,40 @@ export function quotaProviderLabel(provider: string | null): string {
51
52
  export function quotasForProvider(
52
53
  quotas: readonly OmpQuota[],
53
54
  provider: string | null,
55
+ includeAll = false,
54
56
  ): OmpQuota[] {
55
- return provider ? quotas.filter((quota) => quota.provider === provider) : [];
57
+ return provider
58
+ ? quotas.filter((quota) => quota.provider === provider)
59
+ : includeAll
60
+ ? [...quotas]
61
+ : [];
56
62
  }
57
63
 
58
64
  export function quotaSummaryForProvider(
59
65
  quotas: readonly OmpQuota[],
60
66
  provider: string | null,
67
+ includeAll = false,
61
68
  ): { visible: boolean; label: string } {
62
- const matching = quotasForProvider(quotas, provider);
69
+ const matching = quotasForProvider(quotas, provider, includeAll);
70
+ const label = provider ? quotaProviderLabel(provider) : "Quotas";
63
71
  const used = matching.flatMap((quota) =>
64
72
  quota.usedFraction === null ? [] : [quota.usedFraction],
65
73
  );
66
74
  if (used.length === 0) {
67
75
  return provider
68
76
  ? { visible: true, label: `${quotaProviderLabel(provider)} · —` }
69
- : { visible: false, label: "Quota" };
77
+ : { visible: includeAll, label: "Quotas · —" };
70
78
  }
71
79
  const peak = Math.round(Math.max(...used) * 100);
72
- return { visible: true, label: `${quotaProviderLabel(provider)} · ${peak}%` };
80
+ return { visible: true, label: `${label} · ${peak}%` };
73
81
  }
74
82
 
75
83
  export function quotaSeverityForProvider(
76
84
  quotas: readonly OmpQuota[],
77
85
  provider: string | null,
86
+ includeAll = false,
78
87
  ): QuotaSeverity {
79
- const used = quotasForProvider(quotas, provider).flatMap((quota) =>
88
+ const used = quotasForProvider(quotas, provider, includeAll).flatMap((quota) =>
80
89
  quota.usedFraction === null ? [] : [quota.usedFraction],
81
90
  );
82
91
  return used.length === 0 ? "unknown" : quotaSeverityFromFraction(Math.max(...used));
@@ -2,7 +2,9 @@ import { type PluginButtonContentProps, useAgent, useRpc } from "@getpaseo/plugi
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { useMemo } from "react";
4
4
  import { ScrollView, Text, View } from "react-native";
5
+ import { storeForProvider, storeLabel } from "../shared/omp-store";
5
6
  import { listOmpSessions } from "../shared/sessions";
7
+ import { ompStoreKey } from "./omp-store-state";
6
8
 
7
9
  const SESSIONS_POLL_MS = 20_000;
8
10
  const PREVIEW_LIMIT = 20;
@@ -20,11 +22,13 @@ function age(epochSeconds: number): string {
20
22
  export function SessionsPopover(props: PluginButtonContentProps) {
21
23
  const { theme, layout } = props;
22
24
  const agentId = props.context === "agent" ? props.agentId : "";
23
- const cwd = useAgent(agentId, (agent) => agent.cwd) ?? "";
25
+ const agent = useAgent(agentId, ({ cwd, provider }) => ({ cwd, provider }));
26
+ const cwd = agent?.cwd ?? "";
27
+ const store = storeForProvider(agent?.provider);
24
28
  const loadSessions = useRpc(listOmpSessions);
25
29
  const sessions = useQuery({
26
- queryKey: ["paseo-omp", "sessions", cwd],
27
- queryFn: () => loadSessions({ cwd }),
30
+ queryKey: ["paseo-omp", "sessions", ompStoreKey(store), cwd],
31
+ queryFn: () => loadSessions({ cwd, store }),
28
32
  enabled: cwd.length > 0,
29
33
  refetchInterval: SESSIONS_POLL_MS,
30
34
  });
@@ -56,6 +60,7 @@ export function SessionsPopover(props: PluginButtonContentProps) {
56
60
 
57
61
  return (
58
62
  <ScrollView contentContainerStyle={styles.root}>
63
+ <Text style={styles.muted}>{storeLabel(store)}</Text>
59
64
  {items.slice(0, PREVIEW_LIMIT).map((entry) => (
60
65
  <View key={entry.id} style={styles.row}>
61
66
  {entry.title ? <Text style={styles.title}>{entry.title}</Text> : null}
@@ -1,13 +1,13 @@
1
1
  # Alpha release checklist
2
2
 
3
- This checklist prepares `paseo-omp-v0.1.0-alpha.1`. It does not authorize publication. A maintainer must explicitly approve the tested artifact before any push, tag, GitHub release, or package publication.
3
+ This checklist prepares `paseo-omp-v0.1.0-alpha.1`. It does not authorize publication. A maintainer must explicitly approve the tested package before any push, tag, GitHub release, or npm publication.
4
4
 
5
5
  ## Release identity
6
6
 
7
7
  - [ ] Release Please proposes `0.1.0-alpha.1` from manifest version `0.0.0`.
8
- - [ ] Package, tag, and archive names are `@omercnet/paseo-omp`, `paseo-omp-v0.1.0-alpha.1`, and `paseo-omp-v0.1.0-alpha.1.zip`.
8
+ - [ ] Package and tag names are `@omercnet/paseo-omp` and `paseo-omp-v0.1.0-alpha.1`.
9
9
  - [ ] Provider identity remains `omp-plugin`; bundled `omp` remains independent and enabled or disabled by the user.
10
- - [ ] Paseo requirement remains an official released range, currently `^0.8.0`.
10
+ - [ ] Paseo requirement remains the reviewed dual-version range, currently `>=0.8.0 <0.10.0`.
11
11
  - [ ] Minimum tested OMP version, checksum, CI job, README, SUPPORT, and TESTING agree.
12
12
 
13
13
  ## Required gates
@@ -16,24 +16,22 @@ This checklist prepares `paseo-omp-v0.1.0-alpha.1`. It does not authorize public
16
16
  - [ ] `npm run typecheck`
17
17
  - [ ] `npm run test:coverage`; aggregate loaded-source coverage meets the configured threshold.
18
18
  - [ ] Real installed OMP regression against the documented minimum version.
19
- - [ ] `npm run package:release`
20
19
  - [ ] `npm run test:integration:install`
21
20
  - [ ] `npm run test:integration:docker`
22
21
  - [ ] Windows/WSL host ownership job passes in CI.
23
22
  - [ ] Docker canary matrix passes on the exact release candidate.
24
23
  - [ ] GitHub Actions syntax and release-configuration schemas pass.
25
- - [ ] Release ZIP contents contain documentation, production dependencies, and both plugin entries without development-only files.
26
24
 
27
25
  ## Required manual acceptance
28
26
 
29
- - [ ] Maintainer installs the exact candidate archive into the controlled official-Paseo Docker canary.
27
+ - [ ] Maintainer installs the exact `npm pack` candidate into the controlled official-Paseo Docker canary.
30
28
  - [ ] Maintainer verifies catalog, prompt, tools, configured MCP, permissions, steer, interrupt, import/resume, subagents, rewind, usage, Hub, and plugin surfaces.
31
29
  - [ ] Maintainer confirms the known limitations are acceptable for alpha.
32
30
  - [ ] Maintainer explicitly authorizes publication after testing. Silence or prior approval for development is not release authorization.
33
31
 
34
32
  ## Alpha blocker
35
33
 
36
- - [x] `omp-audit.1`: incomplete or compacted `agent_end` frames recover success or failure only from complete streamed `message_end` evidence whose count covers the declared terminal messages; partial evidence still fails closed.
34
+ - [x] `omp-audit.1`: incomplete or compacted `agent_end` frames recover success, failure, or native cancellation from complete streamed `message_end` evidence, or from bounded history whose entry IDs correlate with the streamed turn. Idle state is confirmed before and after retrieval, and concurrent interrupts remain authoritative. Missing, unavailable, non-correlatable, or conflicting terminal evidence fails closed with content-free count diagnostics.
37
35
 
38
36
  ## Accepted alpha limitations
39
37
 
@@ -67,4 +65,4 @@ The prerelease notes must include:
67
65
 
68
66
  ## Publication boundary
69
67
 
70
- Release Please may prepare metadata. The publisher must resolve the immutable tag, require successful CI for the exact tagged commit, build from tracked allowlisted files, attest the archive and checksum, and remain idempotent for recovery. Never retag an alpha commit as stable.
68
+ Release Please may prepare metadata. Publication must use npm trusted publishing from the immutable release commit. Never retag an alpha commit as stable.
@@ -1,6 +1,6 @@
1
1
  # Configuration
2
2
 
3
- Open the **OMP** sidebar to browse the complete installed OMP settings catalog. Boolean, number, string, and enum settings support revision-checked Apply, Discard, and Reset actions; arrays, records, and credentials remain read-only. Configuration writes use OMP's native `config set` and `config reset` commands rather than rewriting YAML.
3
+ Open the global **OMP** sidebar to browse and edit machine-wide state, or open the workspace **OMP** panel from the workspace tab or Explorer to manage project-scoped state. Scalar edits in the global surface use OMP's native `config set` and `config reset` commands. Workspace edits create validated overrides in `<workspace>/.omp/config.yml`; removing an override restores the effective global or default value. Arrays, records, and credentials remain read-only in both surfaces.
4
4
 
5
5
  The **Plugin** tab documents the supported `omp-plugin` launch options, including names-only inherited environment configuration. Paseo's public plugin API does not expose the effective provider options for active launches, so the tab does not claim profile values are active. Choose **OMP Plugin** when creating an agent. Model, mode, thinking level, system prompt, persistence, MCP servers, workspace, and agent environment use Paseo's standard provider controls.
6
6
 
@@ -49,17 +49,21 @@ These options cover every plugin-specific launch value. Values that belong to an
49
49
 
50
50
  ## OMP-native plugins
51
51
 
52
- Open **OMP → OMP plugins** to inspect plugins installed through OMP. The manager uses OMP's documented singular `omp plugin` CLI and supports user-scoped install, enable, disable, upgrade, and uninstall operations. Every state-changing action requires an explicit confirmation; already-running OMP sessions are unchanged.
52
+ Open **OMP → OMP plugins** globally for user-scoped management, or use the workspace **OMP** panel to include project-scoped installations and effective project overrides. The manager uses OMP's documented singular `omp plugin` CLI and supports install, enable, disable, upgrade, and uninstall operations. Every state-changing action requires explicit confirmation; already-running OMP sessions are unchanged.
53
53
 
54
- Project-scoped installations remain visible but read-only because their lifecycle commands must run from that project's working directory. Plugin configuration exposes schema metadata without returning current or default values. Non-secret scalar plugin settings can be set or deleted through write-only controls. Secret settings are presence-only and delete-only because OMP's CLI would otherwise expose a new secret through process arguments.
54
+ Project-scoped lifecycle commands run from the selected workspace and use `--scope project`. Duplicate path installations that share one npm package identity remain read-only because OMP's lifecycle CLI addresses npm plugins by package name rather than installation path. Plugin configuration exposes schema metadata without returning current or default values. Non-secret scalar plugin settings can be set or deleted through write-only controls. Secret settings are presence-only and delete-only because OMP's CLI would otherwise expose a new secret through process arguments.
55
55
 
56
56
  The Configuration view links to the official OMP settings reference, value parsing and precedence guides, relevant category sections, and a small curated set of setting-specific anchors.
57
57
 
58
58
 
59
- ## MCP tools and policy boundary
59
+ ## MCP tools, management, and policy boundary
60
60
 
61
61
  Configured MCP servers and Paseo's caller-scoped MCP tools are supported. The plugin discovers their schemas, assigns collision-safe OMP names, binds them before `session.ready`, forwards progress and terminal results, propagates cancellation, and renders calls with friendly labels.
62
62
 
63
+ Use the **MCP** control beside the composer on an **OMP Plugin** agent to run OMP's native list, add, reload, test, authorize, enable, disable, resource, and prompt commands. The control is agent-scoped because OMP MCP discovery depends on both the active profile and the workspace directory. The global OMP sidebar remains a host-level health and settings surface; a separate workspace manager would duplicate OMP's own discovery and precedence rules.
64
+
65
+ Command output, setup questions, and OAuth prompts appear in the agent timeline. OAuth URLs render as an interactive card and always retain the full provider authorization URL, never substituting OMP's daemon-local `/launch` shortcut. **Open in Paseo Browser** calls the current agent's caller-scoped `browser_new_tab` tool, so the authorization page becomes a browser tab in the same workspace; it requires Paseo tools to be injected into the agent, browser tools to be enabled, and a connected Paseo desktop browser host. **Open on this device** remains available when no browser host is connected. For a loopback callback to complete automatically, the chosen browser host must run on the daemon machine. Otherwise, finish authorization in either browser, copy the final redirect URL or authorization code, and submit it in the OMP authorization prompt. Tokens and refresh material are stored by OMP on the daemon (or its configured auth broker), never in the Paseo client or plugin timeline.
66
+
63
67
  Paseo's exact session `toolPolicy` preapproval grants are not equivalent to OMP's `set_host_tools` contract. The plugin cannot preserve that policy exactly, so any non-empty `toolPolicy` rejects session startup. It never converts exact grants into broader access. `disallowedTools` is separate: it controls only recognized native OMP built-ins and rejects unknown names.
64
68
 
65
69
  ## Credentials and environment
@@ -1,6 +1,6 @@
1
1
  # Core OMP provider issue audit
2
2
 
3
- This audit compares reports in `getpaseo/paseo` with the community `omp-plugin` provider. It was refreshed on 2026-09-12 from GitHub issue titles and bodies containing `OMP`, `oh-my-pi`, or `rpc-ui`, OMP-related pull requests, and materially equivalent Pi/RPC reports. GitHub Discussions were also inspected; the repository had no OMP-related discussion.
3
+ This audit compares reports in `getpaseo/paseo` with the community `omp-plugin` provider. It was refreshed on 2026-09-18 from GitHub issue titles and bodies containing `OMP`, `oh-my-pi`, or `rpc-ui`, OMP-related pull requests, and materially equivalent Pi/RPC reports. GitHub Discussions were also inspected; the repository had no OMP-related discussion.
4
4
 
5
5
  Issue and pull-request pairs are consolidated by root cause. A closed upstream issue does not prove the plugin implements the behavior, and an open upstream issue does not imply the plugin is affected.
6
6
 
@@ -17,9 +17,10 @@ Status meanings:
17
17
  | --- | --- | --- |
18
18
  | [#3838](https://github.com/getpaseo/paseo/issues/3838), [PR #3839](https://github.com/getpaseo/paseo/pull/3839) | **Verified** | A dead OMP subprocess invalidates its generation. A later write lazily resumes the same native session. Process-tree cleanup, recovery, and registry replacement are covered. |
19
19
  | [#3252](https://github.com/getpaseo/paseo/issues/3252), equivalent Pi [#3496](https://github.com/getpaseo/paseo/issues/3496), [PR #3258](https://github.com/getpaseo/paseo/pull/3258) | **Verified** | Hidden and custom notices do not terminalize a turn before the native user echo and terminal evidence. |
20
- | [#2260](https://github.com/getpaseo/paseo/issues/2260), [PR #2261](https://github.com/getpaseo/paseo/pull/2261) | **Verified** | Incomplete or compacted `agent_end` frames use complete streamed `message_end` evidence only when it covers the declared message count. Streamed success and failure outcomes are preserved; missing or partial evidence still fails closed. |
20
+ | [#2260](https://github.com/getpaseo/paseo/issues/2260), [PR #2261](https://github.com/getpaseo/paseo/pull/2261) | **Verified** | Incomplete or compacted `agent_end` frames use complete streamed `message_end` evidence or bounded history correlated by entry ID, with idle state confirmed before and after retrieval. Success, failure, native cancellation, and concurrent interrupts are preserved; missing, unavailable, non-correlatable, or conflicting terminal evidence fails closed with content-free count diagnostics. |
21
21
  | [#2281](https://github.com/getpaseo/paseo/issues/2281), [PR #2282](https://github.com/getpaseo/paseo/pull/2282) | **Verified** | Local-only prompt results and structured commands have explicit terminal ownership. |
22
22
  | [#3654](https://github.com/getpaseo/paseo/issues/3654), [#3998](https://github.com/getpaseo/paseo/issues/3998), [PR #3667](https://github.com/getpaseo/paseo/pull/3667) | **Verified** | Post-`agent_end` state reconciliation is bounded. Stale or unavailable `get_state` cannot leave a turn running forever. |
23
+ | Upstream OMP [PR #12331](https://github.com/can1357/oh-my-pi/pull/12331) and released OMP 18.2.x unkeyed `agent_end` frames | **Mitigated** | Request-keyed terminals use exact matching. For released binaries that omit the key, the plugin accepts only ordered current-prompt evidence: a fresh branch-correlated user entry followed by assistant activity, confirmed native idle/non-compacting state, and no active permission, tool, steer, or child work. Active or pre-evidence stale candidates are ignored. Confirmed-idle ambiguity fails only the Paseo turn, not the OMP runtime. The remaining same-agent stale-event misattribution risk is explicit and bounded; waiting for the upstream protocol fix would make every later turn unusable on published releases. Client reconnects, including mobile reconnect grace, are host transport behavior and do not own the daemon-managed provider session. |
23
24
  | [#3999](https://github.com/getpaseo/paseo/issues/3999), [#4000](https://github.com/getpaseo/paseo/issues/4000), [#4039](https://github.com/getpaseo/paseo/issues/4039), [PR #3772](https://github.com/getpaseo/paseo/pull/3772), [PR #4217](https://github.com/getpaseo/paseo/pull/4217) | **Verified** | `prompt.steer` is negotiated and implemented with expected-turn checks, acknowledgement ordering, duplicate correlation, and interrupt/terminal race coverage. |
24
25
  | Shared RPC cancellation [#3540](https://github.com/getpaseo/paseo/issues/3540), Pi [#3749](https://github.com/getpaseo/paseo/issues/3749) | **Verified in provider** | Native abort, exactly-one terminal event, permission cleanup, descendant cleanup, and uncertain-cleanup quarantine are tested. UI keyboard delivery remains host-owned. |
25
26
  | [#3218](https://github.com/getpaseo/paseo/issues/3218) | **Verified at provider boundary** | Session close owns and awaits OMP process cleanup. Whether every archive UI route invokes provider close is a host concern. |
Binary file
@@ -4,43 +4,61 @@ Paseo plugins are trusted, unsandboxed code. Review this plugin and its producti
4
4
 
5
5
  ## Requirements
6
6
 
7
- - Paseo daemon and apps: `^0.8.0`
7
+ - Paseo daemon and apps: `>=0.8.0 <0.10.0`; `0.9.0-beta.1` is recommended
8
8
  - OMP: `18.1.15` or newer is the supported floor
9
9
  - OMP RPC: protocol v2 must negotiate successfully
10
10
 
11
11
  The first public build is an alpha. Alpha releases are compatibility previews and may require deleting and re-importing agents created by an earlier preview.
12
12
 
13
- ## Install a release
13
+ ## Install or update from npm on Paseo 0.9
14
14
 
15
- Prefer a reviewed release tag over a moving branch:
15
+ Paseo 0.9.0-beta.1 can acquire the published package and its production dependencies directly from npm on the daemon host:
16
+
17
+ ```bash
18
+ paseo plugin install npm:@omercnet/paseo-omp@<version>
19
+ paseo plugin ls paseo-omp
20
+ ```
21
+
22
+ Check for the registry's current `latest` version and approve the proposed update, or select an exact version explicitly:
23
+
24
+ ```bash
25
+ paseo plugin update paseo-omp
26
+ paseo plugin update paseo-omp --version <new-version>
27
+ ```
28
+
29
+ The daemon uses its own npm registry and authentication configuration. npm acquisition installs the artifact's production dependencies before preparation; because npm artifacts omit `package-lock.json`, the preparation helper leaves that tree unchanged. A Git checkout with the committed lockfile runs frozen `npm ci --omit=dev --ignore-scripts`. A failed download, preparation, compatibility check, or activation keeps the installed revision active. Paseo 0.8 does not support npm plugin sources; use a Git tag or local directory instead.
30
+
31
+ ## Install a Git release on Paseo 0.8 or 0.9
32
+
33
+ Install the matching reviewed Git tag:
16
34
 
17
35
  ```bash
18
36
  paseo plugin add omercnet/paseo-plugins:paseo-omp --ref paseo-omp-v<version>
19
37
  paseo plugin ls paseo-omp
20
38
  ```
21
39
 
22
- A tag-pinned installation does not advance through `paseo plugin update`. To upgrade, record the current installation, then replace it with the new tag in one maintenance window:
40
+ ### Update a Git installation on Paseo 0.9
41
+
42
+ An ordinary update reviews the remote repository's current default HEAD. To move directly to another reviewed tag or commit, select it explicitly:
23
43
 
24
44
  ```bash
25
- paseo plugin ls paseo-omp --json > paseo-omp-before-update.json
26
- paseo plugin remove paseo-omp
27
- paseo plugin add omercnet/paseo-plugins:paseo-omp --ref paseo-omp-v<new-version>
45
+ paseo plugin update paseo-omp
46
+ paseo plugin update paseo-omp --ref paseo-omp-v<new-version>
28
47
  ```
29
48
 
30
- Removal deletes plugin-scoped settings and briefly makes `omp-plugin` unavailable. It does not modify Paseo's bundled `omp` provider or native OMP transcripts. Roll back by repeating the remove/add sequence with the recorded tag or commit.
49
+ The install-time `--ref` does not constrain later updates. Paseo 0.9 stages and validates the selected revision before replacing the active installation, so plugin settings remain intact and a failed update leaves the previous revision running.
31
50
 
32
- ## Install a release archive
51
+ ### Update a tag-pinned Git installation on Paseo 0.8
33
52
 
34
- Release ZIPs contain the production dependency tree and install offline. Authenticate provenance before installation:
53
+ Paseo 0.8 does not provide the 0.9 non-destructive explicit-ref update flow. Record the current installation, then replace it with the new tag in one maintenance window:
35
54
 
36
55
  ```bash
37
- gh attestation verify paseo-omp-v<version>.zip --repo omercnet/paseo-plugins
38
- sha256sum --check paseo-omp-v<version>.zip.sha256
39
- unzip paseo-omp-v<version>.zip
40
- paseo plugin install "$PWD/paseo-omp"
56
+ paseo plugin ls paseo-omp --json > paseo-omp-before-update.json
57
+ paseo plugin remove paseo-omp
58
+ paseo plugin add omercnet/paseo-plugins:paseo-omp --ref paseo-omp-v<new-version>
41
59
  ```
42
60
 
43
- The checksum detects accidental corruption; the GitHub attestation authenticates the artifact.
61
+ Removal deletes plugin-scoped settings and briefly makes `omp-plugin` unavailable. It does not modify Paseo's bundled `omp` provider or native OMP transcripts. Roll back by repeating the remove/add sequence with the recorded tag or commit.
44
62
 
45
63
  ## Install a local checkout
46
64
 
@@ -62,12 +80,10 @@ paseo plugin ls paseo-omp
62
80
 
63
81
  ## Track a branch
64
82
 
65
- Tracking `main` executes future dependency and plugin updates with the daemon user's privileges. Record the installed commit before each update:
83
+ Tracking `main` executes future dependency and plugin updates with the daemon user's privileges:
66
84
 
67
85
  ```bash
68
86
  paseo plugin add omercnet/paseo-plugins:paseo-omp --ref main
69
- paseo plugin ls paseo-omp --json > paseo-omp-before-update.json
70
- paseo plugin update paseo-omp
71
87
  ```
72
88
 
73
- A failed Git build or incompatible update leaves the previous revision active. Remove and re-add the recorded commit to roll back.
89
+ On Paseo 0.9, preview and approve the remote default HEAD with `paseo plugin update paseo-omp`, or select `main` explicitly with `paseo plugin update paseo-omp --ref main`. On Paseo 0.8, record the installed commit before its existing branch-update workflow. A failed build or compatibility check leaves the previous revision active.