@omercnet/paseo-context-mode 3.1.3 → 3.1.4-next.434.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.
@@ -1,16 +1,16 @@
1
1
  import type { PluginButtonRegistration, PluginClientContext } from "@getpaseo/plugin/client";
2
2
 
3
- type PillAgent = {
3
+ interface PillAgent {
4
4
  id: string;
5
5
  workspaceId?: string | null;
6
6
  status: string;
7
7
  archivedAt?: string | null;
8
- };
8
+ }
9
9
 
10
- type PillEntry = {
10
+ interface PillEntry {
11
11
  workspaceId: string;
12
12
  registration: PluginButtonRegistration;
13
- };
13
+ }
14
14
 
15
15
  function shouldShowPill(agent: PillAgent): agent is PillAgent & { workspaceId: string } {
16
16
  return Boolean(agent.workspaceId) && !agent.archivedAt && agent.status !== "closed";
@@ -44,7 +44,7 @@ export function contributeContextModeComposerPills(client: PluginClientContext):
44
44
  workspaceId: agent.workspaceId,
45
45
  agentId: agent.id,
46
46
  button: {
47
- title: "View Context Mode status",
47
+ title: "Open Context Mode knowledge",
48
48
  icon: "Gauge",
49
49
  label: "Context Mode",
50
50
  behavior: {
@@ -1,13 +1,15 @@
1
- import { type PluginSurfaceProps, usePaseo } from "@getpaseo/plugin/client";
1
+ import { usePaseo } from "@getpaseo/plugin/client";
2
2
  import { useQuery } from "@tanstack/react-query";
3
- import { Text, View } from "react-native";
4
- import { KnowledgeSection } from "./knowledge-section";
3
+ import { View } from "react-native";
4
+ import { ErrorText, Pending } from "./controls";
5
+ import { KnowledgeSection, type KnowledgeSectionProps } from "./knowledge-section";
5
6
  import { resolveKnowledgeScope, type ScreenParams } from "./screen-scope";
6
7
 
7
- export function ScopedKnowledgeSection({
8
- params,
9
- ...props
10
- }: PluginSurfaceProps & { params: ScreenParams; providers: readonly string[] }) {
8
+ interface ScopedKnowledgeSectionProps extends Omit<KnowledgeSectionProps, "initialScope"> {
9
+ params: ScreenParams;
10
+ }
11
+
12
+ export function ScopedKnowledgeSection({ params, ...props }: ScopedKnowledgeSectionProps) {
11
13
  const paseo = usePaseo();
12
14
  const scope = useQuery({
13
15
  queryKey: [
@@ -20,16 +22,11 @@ export function ScopedKnowledgeSection({
20
22
  queryFn: () => resolveKnowledgeScope(paseo, params),
21
23
  retry: false,
22
24
  });
23
- if (scope.isPending)
24
- return (
25
- <Text style={{ color: props.theme.colors.foregroundMuted }}>Resolving Knowledge scope…</Text>
26
- );
25
+ if (scope.isPending) return <Pending theme={props.theme} label="Resolving knowledge scope..." />;
27
26
  if (scope.error)
28
27
  return (
29
- <View>
30
- <Text accessibilityRole="alert" style={{ color: props.theme.colors.statusDanger }}>
31
- {scope.error.message}
32
- </Text>
28
+ <View style={{ gap: 12 }}>
29
+ <ErrorText theme={props.theme} error={scope.error} />
33
30
  <KnowledgeSection
34
31
  key="scope-error"
35
32
  {...props}
@@ -37,14 +34,11 @@ export function ScopedKnowledgeSection({
37
34
  />
38
35
  </View>
39
36
  );
37
+ const scopedProviderKnown = props.providers.includes(scope.data.provider ?? "");
40
38
  // Remount only when the resolved destination changes so stale form state never survives it.
41
39
  return (
42
40
  <KnowledgeSection
43
- key={JSON.stringify([
44
- scope.data.provider,
45
- scope.data.projectPath,
46
- props.providers.includes(scope.data.provider ?? ""),
47
- ])}
41
+ key={JSON.stringify([scope.data.provider, scope.data.projectPath, scopedProviderKnown])}
48
42
  {...props}
49
43
  initialScope={scope.data}
50
44
  />
@@ -2,14 +2,20 @@ import type { PluginClientContext } from "@getpaseo/plugin/client";
2
2
 
3
3
  type PaseoApi = PluginClientContext["paseo"];
4
4
 
5
+ /** `setup` is the Diagnostics tab; the id stays so links shared before the rename still open it. */
5
6
  export type SurfaceTab = "savings" | "knowledge" | "setup";
6
- export type KnowledgeScope = {
7
+ export interface KnowledgeScope {
7
8
  provider: string | null;
8
9
  projectPath: string;
9
10
  note?: string;
10
- };
11
+ }
12
+
13
+ export interface ScreenParams {
14
+ section: SurfaceTab;
15
+ agentId?: string;
16
+ workspaceId?: string;
17
+ }
11
18
 
12
- export type ScreenParams = { section: SurfaceTab; agentId?: string; workspaceId?: string };
13
19
  export function parseScreenParams(params: Record<string, string>): ScreenParams {
14
20
  const section: SurfaceTab =
15
21
  params.section === "knowledge" || params.section === "setup" ? params.section : "savings";
@@ -25,7 +31,7 @@ function notOnThisHost(kind: "agent" | "workspace"): KnowledgeScope {
25
31
  return {
26
32
  provider: null,
27
33
  projectPath: "",
28
- note: `The ${kind} this screen was opened from is not on this host. Select a provider and project.`,
34
+ note: `The ${kind} this screen was opened from is not on this host. Select an agent and project.`,
29
35
  };
30
36
  }
31
37
 
@@ -38,7 +44,7 @@ export async function resolveKnowledgeScope(
38
44
  if (!result) return notOnThisHost("agent");
39
45
  if (params.workspaceId && result.agent.workspaceId !== params.workspaceId) {
40
46
  throw new Error(
41
- "Agent no longer belongs to this workspace. Select a provider and project explicitly.",
47
+ "Agent no longer belongs to this workspace. Select an agent and project explicitly.",
42
48
  );
43
49
  }
44
50
  return { provider: result.agent.provider, projectPath: result.agent.cwd };
@@ -50,6 +56,6 @@ export async function resolveKnowledgeScope(
50
56
  return {
51
57
  provider: null,
52
58
  projectPath: workspace.workspaceDirectory,
53
- note: "Select a provider store for this workspace.",
59
+ note: "Select which agent to record knowledge work as for this workspace.",
54
60
  };
55
61
  }
@@ -1,5 +1,7 @@
1
+ import type { PluginTheme } from "@getpaseo/plugin";
1
2
  import { type PluginSurfaceProps, type SettingsState, useSettings } from "@getpaseo/plugin/client";
2
3
  import {
4
+ Loading,
3
5
  SettingsAction,
4
6
  SettingsCard,
5
7
  SettingsInput,
@@ -8,95 +10,109 @@ import {
8
10
  SettingsSelect,
9
11
  SettingsSwitch,
10
12
  } from "@getpaseo/plugin/client/ui";
11
- import { useCallback, useMemo, useState } from "react";
13
+ import { useState } from "react";
12
14
  import { Text, View } from "react-native";
13
15
  import {
14
16
  type ContextModeSettings,
15
17
  ContextModeSettingsSchema,
16
18
  contextModeSettings,
17
- } from "../shared";
19
+ } from "../shared/settings";
20
+ import { type ChoiceOption, ErrorText } from "./controls";
18
21
 
19
22
  type ReadySettings = Extract<SettingsState<typeof contextModeSettings.schema>, { status: "ready" }>;
23
+ type RefreshInterval = ContextModeSettings["refreshIntervalMs"];
24
+ type RefreshValue = `${RefreshInterval}`;
25
+ type RuntimeChoice = "bundled" | "own";
20
26
 
21
- const binaryModeOptions = [
22
- { label: "Configured path, PATH, then bundled", value: "path" },
23
- { label: "PATH, then bundled", value: "automatic" },
27
+ const RUNTIME_OPTIONS: readonly ChoiceOption<RuntimeChoice>[] = [
28
+ { label: "Bundled", value: "bundled" },
29
+ { label: "Your own binary", value: "own" },
24
30
  ];
25
- const refreshOptions = [
31
+
32
+ const REFRESH_INTERVALS: Record<RefreshValue, RefreshInterval> = {
33
+ "5000": 5_000,
34
+ "15000": 15_000,
35
+ "30000": 30_000,
36
+ "60000": 60_000,
37
+ };
38
+
39
+ const REFRESH_OPTIONS: readonly ChoiceOption<RefreshValue>[] = [
26
40
  { label: "5 seconds", value: "5000" },
27
41
  { label: "15 seconds", value: "15000" },
28
42
  { label: "30 seconds", value: "30000" },
29
43
  { label: "60 seconds", value: "60000" },
30
44
  ];
31
45
 
32
- function ReadyContextModeSettings({
33
- settings,
34
- theme,
35
- }: {
46
+ function refreshValue(interval: RefreshInterval): RefreshValue {
47
+ return `${interval}`;
48
+ }
49
+
50
+ interface ReadyContextModeSettingsProps {
36
51
  settings: ReadySettings;
37
- theme: PluginSurfaceProps["theme"];
38
- }) {
39
- const [binaryPath, setBinaryPath] = useState(settings.values.binaryPath);
40
- const styles = useMemo(
41
- () => ({
42
- root: { gap: 16 },
43
- value: { color: theme.colors.foreground },
44
- muted: { color: theme.colors.foregroundMuted },
45
- error: { color: theme.colors.statusDanger },
46
- }),
47
- [theme],
48
- );
49
- const save = useCallback(
50
- (patch: Partial<ContextModeSettings>) =>
51
- settings.save({ ...settings.values, ...patch }, settings.revision),
52
- [settings],
53
- );
52
+ theme: PluginTheme;
53
+ }
54
+
55
+ function ReadyContextModeSettings({ settings, theme }: ReadyContextModeSettingsProps) {
56
+ const savedPath = settings.values.binaryPath;
57
+ const [runtime, setRuntime] = useState<RuntimeChoice>(savedPath === null ? "bundled" : "own");
58
+ const [binaryPath, setBinaryPath] = useState(savedPath ?? "");
59
+ const textStyle = { color: theme.colors.foreground };
60
+ const mutedStyle = { color: theme.colors.foregroundMuted };
61
+
62
+ function save(patch: Partial<ContextModeSettings>) {
63
+ void settings.save({ ...settings.values, ...patch }, settings.revision);
64
+ }
65
+
66
+ function chooseRuntime(next: RuntimeChoice) {
67
+ setRuntime(next);
68
+ if (next === "bundled" && savedPath !== null) save({ binaryPath: null });
69
+ }
70
+
54
71
  const normalizedPath = binaryPath.trim();
72
+ const pathChanged = normalizedPath !== savedPath;
55
73
  const pathResult = ContextModeSettingsSchema.safeParse({
56
74
  ...settings.values,
57
- binaryMode: "path",
58
75
  binaryPath: normalizedPath,
59
76
  });
60
- const pathError = pathResult.success
61
- ? null
62
- : (pathResult.error.issues[0]?.message ?? "Enter an absolute executable path.");
77
+ const pathError =
78
+ pathResult.success || normalizedPath === ""
79
+ ? null
80
+ : pathResult.error.issues.map((issue) => issue.message).join(" ");
81
+ const canSavePath = !settings.saving && pathChanged && pathResult.success;
63
82
 
64
83
  return (
65
- <View style={styles.root}>
84
+ <View style={{ gap: 16 }}>
66
85
  <SettingsSection
67
- title="Executable"
68
- info="The daemon checks a configured absolute path first, then PATH, then the bundled runtime."
86
+ title="Runtime"
87
+ info="Context Mode runs from the runtime bundled with this plugin unless you point it at your own executable."
69
88
  >
70
89
  <SettingsCard>
71
90
  <SettingsSelect
72
- label="Binary lookup"
73
- value={settings.values.binaryMode}
74
- options={binaryModeOptions}
91
+ label="Runtime"
92
+ value={runtime}
93
+ options={RUNTIME_OPTIONS}
75
94
  disabled={settings.saving}
76
- onValueChange={(binaryMode) =>
77
- void save({ binaryMode: binaryMode as ContextModeSettings["binaryMode"] })
78
- }
79
- />
80
- <SettingsInput
81
- label="Absolute binary path"
82
- hint="Optional external override. Leave empty to use PATH or the bundled runtime."
83
- error={normalizedPath === settings.values.binaryPath ? null : pathError}
84
- initialValue={binaryPath}
85
- onChangeText={setBinaryPath}
86
- placeholder="/usr/local/bin/context-mode"
87
- disabled={settings.saving || settings.values.binaryMode === "automatic"}
88
- />
89
- <SettingsAction
90
- label="Save binary path"
91
- actionLabel="Save"
92
- disabled={
93
- settings.saving ||
94
- settings.values.binaryMode === "automatic" ||
95
- normalizedPath === settings.values.binaryPath ||
96
- !pathResult.success
97
- }
98
- onPress={() => void save({ binaryPath: normalizedPath })}
95
+ onValueChange={chooseRuntime}
99
96
  />
97
+ {runtime === "own" ? (
98
+ <>
99
+ <SettingsInput
100
+ label="Absolute binary path"
101
+ hint="The Context Mode executable the daemon should run"
102
+ error={pathChanged ? pathError : null}
103
+ initialValue={binaryPath}
104
+ onChangeText={setBinaryPath}
105
+ placeholder="/usr/local/bin/context-mode"
106
+ disabled={settings.saving}
107
+ />
108
+ <SettingsAction
109
+ label="Use this binary"
110
+ actionLabel="Save"
111
+ disabled={!canSavePath}
112
+ onPress={() => save({ binaryPath: normalizedPath })}
113
+ />
114
+ </>
115
+ ) : null}
100
116
  </SettingsCard>
101
117
  </SettingsSection>
102
118
 
@@ -107,17 +123,17 @@ function ReadyContextModeSettings({
107
123
  <SettingsCard>
108
124
  <SettingsSwitch
109
125
  label="Automatically activate Context Mode"
110
- hint="Inject the bundled MCP server into new agents and provider-specific storage variables whenever sessions open."
126
+ hint="Inject the Context Mode MCP server into new Claude, Codex, Copilot, and OpenCode agents"
111
127
  value={settings.values.autoInject}
112
128
  disabled={settings.saving}
113
- onValueChange={(autoInject) => void save({ autoInject })}
129
+ onValueChange={(autoInject) => save({ autoInject })}
114
130
  />
115
131
  <SettingsSwitch
116
132
  label="Prefer native integrations"
117
- hint="Keep OMP, Pi, and OpenCode native plugins when detected instead of registering duplicate MCP tools."
133
+ hint="Skip injection when Context Mode is already installed natively for a provider, so its tools are not registered twice"
118
134
  value={settings.values.preferNativeIntegrations}
119
135
  disabled={settings.saving || !settings.values.autoInject}
120
- onValueChange={(preferNativeIntegrations) => void save({ preferNativeIntegrations })}
136
+ onValueChange={(preferNativeIntegrations) => save({ preferNativeIntegrations })}
121
137
  />
122
138
  </SettingsCard>
123
139
  </SettingsSection>
@@ -125,32 +141,27 @@ function ReadyContextModeSettings({
125
141
  <SettingsSection title="Display">
126
142
  <SettingsCard>
127
143
  <SettingsSelect
128
- label="Refresh interval"
129
- value={String(settings.values.refreshIntervalMs)}
130
- options={refreshOptions}
144
+ label="Status refresh interval"
145
+ value={refreshValue(settings.values.refreshIntervalMs)}
146
+ options={REFRESH_OPTIONS}
131
147
  disabled={settings.saving}
132
- onValueChange={(value) =>
133
- void save({
134
- refreshIntervalMs: Number(value) as ContextModeSettings["refreshIntervalMs"],
135
- })
136
- }
148
+ onValueChange={(value) => save({ refreshIntervalMs: REFRESH_INTERVALS[value] })}
137
149
  />
138
150
  </SettingsCard>
139
151
  </SettingsSection>
140
152
 
141
- <SettingsSection title="Safety">
153
+ <SettingsSection title="Storage">
142
154
  <SettingsCard>
143
155
  <SettingsRow
144
- label="Storage reuse"
145
- hint="Existing provider-specific Context Mode roots are reused. Session databases are never merged across providers."
156
+ label="Data location"
157
+ hint="Context Mode keeps one store per project, shared by every provider"
146
158
  >
147
- <Text style={styles.value}>Provider scoped</Text>
159
+ <Text style={textStyle}>Per project</Text>
148
160
  </SettingsRow>
149
161
  <SettingsRow label="Context Mode runtime">
150
- <Text style={styles.muted}>Bundled with this Paseo plugin</Text>
151
- </SettingsRow>
152
- <SettingsRow label="Upgrades">
153
- <Text style={styles.muted}>Explicit command only</Text>
162
+ <Text selectable style={mutedStyle}>
163
+ {savedPath ?? "Bundled with this plugin"}
164
+ </Text>
154
165
  </SettingsRow>
155
166
  </SettingsCard>
156
167
  </SettingsSection>
@@ -158,7 +169,7 @@ function ReadyContextModeSettings({
158
169
  <SettingsSection title="Persistence">
159
170
  <SettingsCard>
160
171
  <SettingsRow label="Settings" hint="Shared by clients connected to this host">
161
- <Text style={styles.value}>{settings.saving ? "Saving…" : "Saved on host"}</Text>
172
+ <Text style={textStyle}>{settings.saving ? "Saving..." : "Saved on host"}</Text>
162
173
  </SettingsRow>
163
174
  <SettingsAction
164
175
  label="Restore defaults"
@@ -176,11 +187,6 @@ function ReadyContextModeSettings({
176
187
  />
177
188
  ) : null}
178
189
  </SettingsCard>
179
- {settings.saveError ? (
180
- <Text accessibilityRole="alert" style={styles.error}>
181
- {settings.saveError}
182
- </Text>
183
- ) : null}
184
190
  </SettingsSection>
185
191
  </View>
186
192
  );
@@ -188,21 +194,12 @@ function ReadyContextModeSettings({
188
194
 
189
195
  export function ContextModeSettingsScreen({ theme }: PluginSurfaceProps) {
190
196
  const settings = useSettings(contextModeSettings);
191
- const styles = useMemo(
192
- () => ({
193
- text: { color: theme.colors.foreground },
194
- error: { color: theme.colors.statusDanger },
195
- }),
196
- [theme],
197
- );
198
197
 
199
- if (settings.status === "loading") return <Text style={styles.text}>Loading settings…</Text>;
198
+ if (settings.status === "loading") return <Loading />;
200
199
  if (settings.status !== "ready") {
201
200
  return (
202
201
  <SettingsSection title="Context Mode settings">
203
- <Text accessibilityRole="alert" style={styles.error}>
204
- {settings.error}
205
- </Text>
202
+ <ErrorText theme={theme} error={settings.error} />
206
203
  <SettingsCard>
207
204
  <SettingsAction
208
205
  label="Read settings again"