@omercnet/paseo-context-mode 0.0.0-next.127.2

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.
@@ -0,0 +1,226 @@
1
+ import { type PluginSurfaceProps, type SettingsState, useSettings } from "@getpaseo/plugin/client";
2
+ import {
3
+ SettingsAction,
4
+ SettingsCard,
5
+ SettingsInput,
6
+ SettingsRow,
7
+ SettingsSection,
8
+ SettingsSelect,
9
+ SettingsSwitch,
10
+ } from "@getpaseo/plugin/client/ui";
11
+ import { useCallback, useMemo, useState } from "react";
12
+ import { Text, View } from "react-native";
13
+ import {
14
+ type ContextModeSettings,
15
+ ContextModeSettingsSchema,
16
+ contextModeSettings,
17
+ } from "../shared";
18
+
19
+ type ReadySettings = Extract<SettingsState<typeof contextModeSettings.schema>, { status: "ready" }>;
20
+
21
+ const binaryModeOptions = [
22
+ { label: "Configured path, PATH, then bundled", value: "path" },
23
+ { label: "PATH, then bundled", value: "automatic" },
24
+ ];
25
+ const refreshOptions = [
26
+ { label: "5 seconds", value: "5000" },
27
+ { label: "15 seconds", value: "15000" },
28
+ { label: "30 seconds", value: "30000" },
29
+ { label: "60 seconds", value: "60000" },
30
+ ];
31
+
32
+ function ReadyContextModeSettings({
33
+ settings,
34
+ theme,
35
+ }: {
36
+ 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
+ );
54
+ const normalizedPath = binaryPath.trim();
55
+ const pathResult = ContextModeSettingsSchema.safeParse({
56
+ ...settings.values,
57
+ binaryMode: "path",
58
+ binaryPath: normalizedPath,
59
+ });
60
+ const pathError = pathResult.success
61
+ ? null
62
+ : (pathResult.error.issues[0]?.message ?? "Enter an absolute executable path.");
63
+
64
+ return (
65
+ <View style={styles.root}>
66
+ <SettingsSection
67
+ title="Executable"
68
+ info="The daemon checks a configured absolute path first, then PATH, then the bundled runtime."
69
+ >
70
+ <SettingsCard>
71
+ <SettingsSelect
72
+ label="Binary lookup"
73
+ value={settings.values.binaryMode}
74
+ options={binaryModeOptions}
75
+ 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 })}
99
+ />
100
+ </SettingsCard>
101
+ </SettingsSection>
102
+
103
+ <SettingsSection
104
+ title="Agent activation"
105
+ info="Context Mode is added to supported Paseo agents when their sessions are created."
106
+ >
107
+ <SettingsCard>
108
+ <SettingsSwitch
109
+ label="Automatically activate Context Mode"
110
+ hint="Inject the bundled MCP server into new agents and provider-specific storage variables whenever sessions open."
111
+ value={settings.values.autoInject}
112
+ disabled={settings.saving}
113
+ onValueChange={(autoInject) => void save({ autoInject })}
114
+ />
115
+ <SettingsSwitch
116
+ label="Prefer native integrations"
117
+ hint="Keep OMP, Pi, and OpenCode native plugins when detected instead of registering duplicate MCP tools."
118
+ value={settings.values.preferNativeIntegrations}
119
+ disabled={settings.saving || !settings.values.autoInject}
120
+ onValueChange={(preferNativeIntegrations) => void save({ preferNativeIntegrations })}
121
+ />
122
+ </SettingsCard>
123
+ </SettingsSection>
124
+
125
+ <SettingsSection title="Display">
126
+ <SettingsCard>
127
+ <SettingsSelect
128
+ label="Refresh interval"
129
+ value={String(settings.values.refreshIntervalMs)}
130
+ options={refreshOptions}
131
+ disabled={settings.saving}
132
+ onValueChange={(value) =>
133
+ void save({
134
+ refreshIntervalMs: Number(value) as ContextModeSettings["refreshIntervalMs"],
135
+ })
136
+ }
137
+ />
138
+ </SettingsCard>
139
+ </SettingsSection>
140
+
141
+ <SettingsSection title="Safety">
142
+ <SettingsCard>
143
+ <SettingsRow
144
+ label="Storage reuse"
145
+ hint="Existing provider-specific Context Mode roots are reused. Session databases are never merged across providers."
146
+ >
147
+ <Text style={styles.value}>Provider scoped</Text>
148
+ </SettingsRow>
149
+ <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>
154
+ </SettingsRow>
155
+ </SettingsCard>
156
+ </SettingsSection>
157
+
158
+ <SettingsSection title="Persistence">
159
+ <SettingsCard>
160
+ <SettingsRow label="Settings" hint="Shared by clients connected to this host">
161
+ <Text style={styles.value}>{settings.saving ? "Saving…" : "Saved on host"}</Text>
162
+ </SettingsRow>
163
+ <SettingsAction
164
+ label="Restore defaults"
165
+ actionLabel="Reset"
166
+ disabled={settings.saving}
167
+ onPress={() => void settings.reset()}
168
+ />
169
+ {settings.saveError ? (
170
+ <SettingsAction
171
+ label="Reload saved values"
172
+ error={settings.saveError}
173
+ actionLabel="Reload"
174
+ disabled={settings.saving}
175
+ onPress={settings.reload}
176
+ />
177
+ ) : null}
178
+ </SettingsCard>
179
+ {settings.saveError ? (
180
+ <Text accessibilityRole="alert" style={styles.error}>
181
+ {settings.saveError}
182
+ </Text>
183
+ ) : null}
184
+ </SettingsSection>
185
+ </View>
186
+ );
187
+ }
188
+
189
+ export function ContextModeSettingsScreen({ theme }: PluginSurfaceProps) {
190
+ 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
+
199
+ if (settings.status === "loading") return <Text style={styles.text}>Loading settings…</Text>;
200
+ if (settings.status !== "ready") {
201
+ return (
202
+ <SettingsSection title="Context Mode settings">
203
+ <Text accessibilityRole="alert" style={styles.error}>
204
+ {settings.error}
205
+ </Text>
206
+ <SettingsCard>
207
+ <SettingsAction
208
+ label="Read settings again"
209
+ actionLabel="Reload"
210
+ onPress={settings.reload}
211
+ />
212
+ {settings.status === "invalid" ? (
213
+ <SettingsAction
214
+ label="Replace invalid data with defaults"
215
+ actionLabel="Reset"
216
+ disabled={settings.saving}
217
+ onPress={() => void settings.reset()}
218
+ />
219
+ ) : null}
220
+ </SettingsCard>
221
+ </SettingsSection>
222
+ );
223
+ }
224
+
225
+ return <ReadyContextModeSettings key={settings.revision} settings={settings} theme={theme} />;
226
+ }
@@ -0,0 +1,43 @@
1
+ import type { PluginClientContext, PluginSurfaceProps } from "@getpaseo/plugin/client";
2
+ import { contributeContextModeComposerPills } from "./client/context-mode-pill";
3
+ import { ContextModeSurface } from "./client/context-mode-surface";
4
+ import { ContextModeSettingsScreen } from "./client/settings-screen";
5
+
6
+ export default function contribute(client: PluginClientContext) {
7
+ function Surface(props: PluginSurfaceProps) {
8
+ return (
9
+ <ContextModeSurface {...props} onOpenSettings={() => client.openSettings("context-mode")} />
10
+ );
11
+ }
12
+
13
+ const cleanups = [
14
+ contributeContextModeComposerPills(client),
15
+ client.addSettingsScreen({
16
+ id: "context-mode",
17
+ title: "Context Mode settings",
18
+ icon: "Settings",
19
+ Component: ContextModeSettingsScreen,
20
+ }),
21
+ client.addSurface("context-mode", Surface),
22
+ client.addSidebarItem({
23
+ id: "context-mode",
24
+ title: "Context Mode",
25
+ icon: "Gauge",
26
+ surface: "context-mode",
27
+ }),
28
+ client.addCommandCenterItem({
29
+ id: "open-context-mode",
30
+ title: "Open Context Mode",
31
+ icon: "Gauge",
32
+ keywords: ["context", "savings", "tokens", "knowledge", "search", "doctor", "health"],
33
+ context: "global",
34
+ onSelect({ openSurface }) {
35
+ openSurface("context-mode");
36
+ },
37
+ }),
38
+ ];
39
+
40
+ return () => {
41
+ for (const cleanup of cleanups.reverse()) cleanup();
42
+ };
43
+ }
@@ -0,0 +1,54 @@
1
+ import type { PluginServerContext } from "@getpaseo/plugin/server";
2
+ import { createContextModeActionHandlers } from "./server/actions";
3
+ import { createContextModeAnalyticsHandler } from "./server/analytics";
4
+ import { resolveContextModeBinary } from "./server/binary";
5
+ import { createContextModeHandlers } from "./server/handlers";
6
+ import { injectContextModeEnvironment, injectContextModeOnCreate } from "./server/integration";
7
+ import { createContextModeKnowledgeHandlers } from "./server/knowledge";
8
+ import {
9
+ contextModeSettings,
10
+ fetchAndIndexContextMode,
11
+ getContextModeAnalyticsDashboard,
12
+ getContextModeDoctor,
13
+ getContextModeDoctorReport,
14
+ getContextModeInstallAction,
15
+ getContextModeIntegrationAudit,
16
+ getContextModeStats,
17
+ getContextModeStatus,
18
+ getContextModeUpgradeAction,
19
+ indexContextModePath,
20
+ purgeContextModeKnowledge,
21
+ searchContextModeKnowledge,
22
+ } from "./shared";
23
+
24
+ export default function contribute(server: PluginServerContext) {
25
+ const settings = server.registerSettings(contextModeSettings);
26
+ const handlers = createContextModeHandlers(settings);
27
+ const knowledgeHandlers = createContextModeKnowledgeHandlers(settings);
28
+ const actionHandlers = createContextModeActionHandlers(settings);
29
+ server.handle(getContextModeStatus, handlers.status);
30
+ server.handle(getContextModeDoctor, handlers.doctor);
31
+ server.handle(getContextModeStats, handlers.stats);
32
+ server.handle(getContextModeIntegrationAudit, handlers.audit);
33
+ server.handle(getContextModeAnalyticsDashboard, createContextModeAnalyticsHandler());
34
+ server.handle(searchContextModeKnowledge, knowledgeHandlers.search);
35
+ server.handle(indexContextModePath, knowledgeHandlers.indexPath);
36
+ server.handle(fetchAndIndexContextMode, knowledgeHandlers.fetchAndIndex);
37
+ server.handle(purgeContextModeKnowledge, knowledgeHandlers.purge);
38
+ server.handle(getContextModeDoctorReport, actionHandlers.doctor);
39
+ server.handle(getContextModeInstallAction, actionHandlers.install);
40
+ server.handle(getContextModeUpgradeAction, actionHandlers.upgrade);
41
+ server.before("agent.create", async ({ request }) => {
42
+ const current = await settings.read();
43
+ if (current.status !== "ready") return request;
44
+ const binary = await resolveContextModeBinary(current.values);
45
+ if (binary.state !== "found") return request;
46
+ return injectContextModeOnCreate(request, current.values, binary.launch);
47
+ });
48
+ server.before("agent.session_open", async ({ request }) => {
49
+ const current = await settings.read();
50
+ if (current.status !== "ready") return request;
51
+ return injectContextModeEnvironment(request, current.values);
52
+ });
53
+ return () => {};
54
+ }
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@omercnet/paseo-context-mode",
3
+ "version": "0.0.0-next.127.2",
4
+ "type": "module",
5
+ "description": "Provider-aware Context Mode health, savings, and MCP activation for Paseo.",
6
+ "license": "MIT",
7
+ "author": "Omer Cohen",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/omercnet/paseo-plugins.git",
11
+ "directory": "context-mode"
12
+ },
13
+ "homepage": "https://github.com/omercnet/paseo-plugins/tree/main/context-mode#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/omercnet/paseo-plugins/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "packageManager": "npm@11.19.1",
21
+ "engines": {
22
+ "node": ">=24"
23
+ },
24
+ "keywords": [
25
+ "paseo",
26
+ "paseo-plugin",
27
+ "context-mode",
28
+ "context-window",
29
+ "developer-tools"
30
+ ],
31
+ "files": [
32
+ "LICENSE",
33
+ "NOTICE",
34
+ "README.md",
35
+ "index.client.tsx",
36
+ "index.server.ts",
37
+ "client",
38
+ "server",
39
+ "shared",
40
+ "paseo-plugin.json"
41
+ ],
42
+ "scripts": {
43
+ "check": "biome check .",
44
+ "check:write": "biome check --write .",
45
+ "lint": "biome check .",
46
+ "format:check": "biome check .",
47
+ "test": "vitest run",
48
+ "test:unit": "vitest run",
49
+ "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=lcov --coverage.thresholds.lines=90 --coverage.thresholds.functions=85",
50
+ "typecheck": "tsc --noEmit -p tsconfig.client.json && tsc --noEmit -p tsconfig.server.json",
51
+ "verify:package": "node scripts/verify-package.mjs"
52
+ },
53
+ "devDependencies": {
54
+ "@biomejs/biome": "^2.5.10",
55
+ "@getpaseo/cli": "0.9.0-beta.1",
56
+ "@getpaseo/client": "0.9.0-beta.1",
57
+ "@getpaseo/plugin": "0.9.0-beta.1",
58
+ "@getpaseo/protocol": "0.9.0-beta.1",
59
+ "@tanstack/react-query": "^5.102.3",
60
+ "@types/node": "^24.5.2",
61
+ "@types/react": "^19.2.18",
62
+ "@vitest/coverage-v8": "^5.0.0",
63
+ "react": "19.1.0",
64
+ "react-native": "0.81.5",
65
+ "typescript": "^7.0.2",
66
+ "vitest": "^5.0.0",
67
+ "zod": "^4.4.3"
68
+ },
69
+ "dependencies": {
70
+ "context-mode": "1.0.169"
71
+ }
72
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "context-mode",
3
+ "description": "Provider-aware Context Mode health, savings, and MCP activation for Paseo.",
4
+ "requirements": { "paseo": ">=0.9.0-beta.1 <0.10.0" }
5
+ }
@@ -0,0 +1,146 @@
1
+ import type { PluginSettings } from "@getpaseo/plugin/server";
2
+ import {
3
+ CONTEXT_MODE_TARGET_VERSION,
4
+ type ContextModeAction,
5
+ type ContextModeActionInput,
6
+ type DoctorReport,
7
+ type DoctorStatusRow,
8
+ } from "../shared/actions";
9
+ import type { ContextModeSettings, contextModeSettings } from "../shared/settings";
10
+ import { type BinaryResolution, type LaunchDescriptor, resolveContextModeBinary } from "./binary";
11
+ import { contextModeEnvironmentFor, contextModePlatformFor } from "./knowledge";
12
+ import {
13
+ callContextModeTool,
14
+ inspectContextMode,
15
+ type McpInspection,
16
+ type McpProcessDependencies,
17
+ type McpToolArguments,
18
+ } from "./mcp-process";
19
+
20
+ type SettingsHandle = PluginSettings<typeof contextModeSettings.schema>;
21
+
22
+ type ActionToolCaller = (
23
+ launch: LaunchDescriptor,
24
+ name: "ctx_doctor",
25
+ arguments_: McpToolArguments,
26
+ dependencies: McpProcessDependencies,
27
+ ) => Promise<string>;
28
+
29
+ export interface ActionHandlerDependencies {
30
+ osPlatform?: NodeJS.Platform;
31
+ resolveBinary?: (settings: ContextModeSettings) => Promise<BinaryResolution>;
32
+ inspect?: (
33
+ launch: LaunchDescriptor,
34
+ dependencies?: McpProcessDependencies,
35
+ ) => Promise<McpInspection>;
36
+ callTool?: ActionToolCaller;
37
+ }
38
+
39
+ export interface ContextModeActionHandlers {
40
+ doctor(input: ContextModeActionInput): Promise<DoctorReport>;
41
+ install(input: ContextModeActionInput): Promise<ContextModeAction>;
42
+ upgrade(input: ContextModeActionInput): Promise<ContextModeAction>;
43
+ }
44
+
45
+ export function parseContextModeDoctorOutput(rawOutput: string): DoctorStatusRow[] {
46
+ const rows: DoctorStatusRow[] = [];
47
+ for (const line of rawOutput.split(/\r?\n/)) {
48
+ const match = /^\[(OK|WARN|FAIL)\]\s+(.+)$/.exec(line.trim());
49
+ if (!match) continue;
50
+ const body = match[2] ?? "";
51
+ const separator = body.indexOf(":");
52
+ const check = (separator < 0 ? body : body.slice(0, separator)).trim().slice(0, 256);
53
+ if (!check) continue;
54
+ const detail = (separator < 0 ? "" : body.slice(separator + 1).trim()).slice(0, 4_096);
55
+ rows.push({
56
+ status: match[1] === "OK" ? "ok" : match[1] === "WARN" ? "warning" : "error",
57
+ check,
58
+ detail,
59
+ });
60
+ if (rows.length === 64) break;
61
+ }
62
+ return rows;
63
+ }
64
+
65
+ export function createContextModeActionHandlers(
66
+ settingsHandle: SettingsHandle,
67
+ dependencies: ActionHandlerDependencies = {},
68
+ ): ContextModeActionHandlers {
69
+ const resolveBinary = dependencies.resolveBinary ?? resolveContextModeBinary;
70
+ const inspect = dependencies.inspect ?? inspectContextMode;
71
+ const callTool = dependencies.callTool ?? callContextModeTool;
72
+ const osPlatform = dependencies.osPlatform ?? process.platform;
73
+
74
+ async function resolvedBinary(): Promise<Extract<BinaryResolution, { state: "found" }>> {
75
+ const settings = await settingsHandle.read();
76
+ if (settings.status !== "ready") {
77
+ throw new Error(`Context Mode settings are invalid: ${settings.error}`.slice(0, 4_096));
78
+ }
79
+ const binary = await resolveBinary(settings.values);
80
+ if (binary.state === "missing") throw new Error(binary.message.slice(0, 4_096));
81
+ return binary;
82
+ }
83
+
84
+ async function currentVersion(input: ContextModeActionInput): Promise<string | null> {
85
+ try {
86
+ const binary = await resolvedBinary();
87
+ const inspection = await inspect(binary.launch, {
88
+ env: contextModeEnvironmentFor(input.provider),
89
+ });
90
+ return inspection.version?.slice(0, 128) ?? null;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ return {
97
+ async doctor(input) {
98
+ const binary = await resolvedBinary();
99
+ const rawOutput = await callTool(
100
+ binary.launch,
101
+ "ctx_doctor",
102
+ {},
103
+ {
104
+ env: contextModeEnvironmentFor(input.provider),
105
+ },
106
+ );
107
+ return {
108
+ platform: contextModePlatformFor(input.provider),
109
+ rows: parseContextModeDoctorOutput(rawOutput),
110
+ rawOutput,
111
+ };
112
+ },
113
+ async install(input) {
114
+ const platform = contextModePlatformFor(input.provider);
115
+ return {
116
+ program: osPlatform === "win32" ? "npm.cmd" : "npm",
117
+ args: [
118
+ "install",
119
+ "-g",
120
+ `context-mode@${CONTEXT_MODE_TARGET_VERSION}`,
121
+ "--no-audit",
122
+ "--no-fund",
123
+ ],
124
+ currentVersion: await currentVersion(input),
125
+ targetVersion: CONTEXT_MODE_TARGET_VERSION,
126
+ platform,
127
+ requiresRestart: true,
128
+ };
129
+ },
130
+ async upgrade(input) {
131
+ const platform = contextModePlatformFor(input.provider);
132
+ const binary = await resolvedBinary();
133
+ const inspection = await inspect(binary.launch, {
134
+ env: contextModeEnvironmentFor(input.provider),
135
+ });
136
+ return {
137
+ program: binary.launch.program,
138
+ args: [...binary.launch.args, "upgrade", "--platform", platform],
139
+ currentVersion: inspection.version?.slice(0, 128) ?? null,
140
+ targetVersion: "latest",
141
+ platform,
142
+ requiresRestart: true,
143
+ };
144
+ },
145
+ };
146
+ }