JerrettDavis commited on
Commit
0d9ba62
·
1 Parent(s): 26ac47c

Restore pythonPath and proxyPort config support

Browse files
plugins/openclaw/README.md CHANGED
@@ -50,16 +50,17 @@ Why this matters:
50
 
51
  `proxyUrl` must be localhost (`127.0.0.1` or `localhost`). If the proxy is not running, the plugin will try to start it with `headroom proxy --host ... --port ...`.
52
  `proxyUrl` is optional. If omitted, the plugin auto-detects on:
53
- - `http://127.0.0.1:8787`
54
- - `http://localhost:8787`
55
- If no proxy is found, it will auto-start on `http://127.0.0.1:8787` (when `autoStart` is enabled).
56
 
57
- If set, `proxyUrl` must be localhost (`127.0.0.1` or `localhost`). If that proxy is not running, the plugin will try to start it with `headroom proxy --host ... --port ...`.
58
  Auto-start launch order is:
59
  1. `headroom` from `PATH`
60
  2. local npm bin (`node_modules/.bin/headroom`)
61
  3. global npm bin
62
  4. Python module (`python -m headroom.cli proxy ...`)
 
63
 
64
  ## Required Proxy Setup
65
 
@@ -94,7 +95,9 @@ Compression is lossless via CCR (Compress-Cache-Retrieve): originals are stored
94
 
95
  | Option | Default | Description |
96
  |--------|---------|-------------|
97
- | `proxyUrl` | auto-detected | Optional URL of a Headroom proxy (`http://127.0.0.1:<port>` or `http://localhost:<port>`). Defaults to probing `:8787`. |
 
 
98
  | `autoStart` | `true` | Auto-start `headroom proxy` if not already running |
99
  | `startupTimeoutMs` | `20000` | Time to wait for auto-started proxy to become healthy |
100
 
 
50
 
51
  `proxyUrl` must be localhost (`127.0.0.1` or `localhost`). If the proxy is not running, the plugin will try to start it with `headroom proxy --host ... --port ...`.
52
  `proxyUrl` is optional. If omitted, the plugin auto-detects on:
53
+ - `http://127.0.0.1:<proxyPort>`
54
+ - `http://localhost:<proxyPort>`
55
+ Default `proxyPort` is `8787`.
56
 
57
+ If set, `proxyUrl` must be localhost (`127.0.0.1` or `localhost`).
58
  Auto-start launch order is:
59
  1. `headroom` from `PATH`
60
  2. local npm bin (`node_modules/.bin/headroom`)
61
  3. global npm bin
62
  4. Python module (`python -m headroom.cli proxy ...`)
63
+ If `pythonPath` is set, it is tried first in the Python fallback step.
64
 
65
  ## Required Proxy Setup
66
 
 
95
 
96
  | Option | Default | Description |
97
  |--------|---------|-------------|
98
+ | `proxyUrl` | auto-detected | Optional URL of a Headroom proxy (`http://127.0.0.1:<port>` or `http://localhost:<port>`). |
99
+ | `proxyPort` | `8787` | Port used for default auto-detect/auto-start when `proxyUrl` is not set. |
100
+ | `pythonPath` | auto-detected | Optional Python executable override for Python fallback launcher. |
101
  | `autoStart` | `true` | Auto-start `headroom proxy` if not already running |
102
  | `startupTimeoutMs` | `20000` | Time to wait for auto-started proxy to become healthy |
103
 
plugins/openclaw/openclaw.plugin.json CHANGED
@@ -4,6 +4,14 @@
4
  "proxyUrl": {
5
  "label": "Proxy URL",
6
  "help": "Optional. URL of a Headroom proxy on localhost (example: http://127.0.0.1:8787). If omitted, plugin auto-detects defaults."
 
 
 
 
 
 
 
 
7
  }
8
  },
9
  "configSchema": {
@@ -17,6 +25,15 @@
17
  "type": "string",
18
  "pattern": "^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$"
19
  },
 
 
 
 
 
 
 
 
 
20
  "autoStart": {
21
  "type": "boolean",
22
  "default": true
 
4
  "proxyUrl": {
5
  "label": "Proxy URL",
6
  "help": "Optional. URL of a Headroom proxy on localhost (example: http://127.0.0.1:8787). If omitted, plugin auto-detects defaults."
7
+ },
8
+ "proxyPort": {
9
+ "label": "Proxy Port",
10
+ "help": "Default port used for auto-detect/auto-start when proxyUrl is not set (default: 8787)."
11
+ },
12
+ "pythonPath": {
13
+ "label": "Python Path",
14
+ "help": "Optional explicit python executable for python fallback launcher (for example: python, python3, py, or full path)."
15
  }
16
  },
17
  "configSchema": {
 
25
  "type": "string",
26
  "pattern": "^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$"
27
  },
28
+ "proxyPort": {
29
+ "type": "integer",
30
+ "minimum": 1,
31
+ "maximum": 65535,
32
+ "default": 8787
33
+ },
34
+ "pythonPath": {
35
+ "type": "string"
36
+ },
37
  "autoStart": {
38
  "type": "boolean",
39
  "default": true
plugins/openclaw/src/proxy-manager.ts CHANGED
@@ -14,6 +14,8 @@ import { fileURLToPath } from "node:url";
14
 
15
  export interface ProxyManagerConfig {
16
  proxyUrl?: string;
 
 
17
  autoStart?: boolean;
18
  startupTimeoutMs?: number;
19
  }
@@ -39,11 +41,6 @@ interface LaunchSpec {
39
  checkArgs: string[];
40
  }
41
 
42
- const DEFAULT_PROXY_CANDIDATES = [
43
- "http://127.0.0.1:8787",
44
- "http://localhost:8787",
45
- ];
46
-
47
  const defaultLogger: ProxyManagerLogger = {
48
  info: (m) => console.log(`[headroom] ${m}`),
49
  warn: (m) => console.warn(`[headroom] ${m}`),
@@ -65,11 +62,13 @@ export class ProxyManager {
65
  * Ensure a proxy is available. Returns the normalized URL origin.
66
  */
67
  async start(): Promise<string> {
 
68
  const explicitUrl =
69
  typeof this.config.proxyUrl === "string" && this.config.proxyUrl.trim().length > 0
70
  ? normalizeAndValidateProxyUrl(this.config.proxyUrl)
71
  : null;
72
- const candidateUrls = explicitUrl ? [explicitUrl] : [...DEFAULT_PROXY_CANDIDATES];
 
73
  const probeByUrl = new Map<string, ProxyProbeResult>();
74
 
75
  for (const url of candidateUrls) {
@@ -92,7 +91,7 @@ export class ProxyManager {
92
  }
93
 
94
  if (this.config.autoStart !== false) {
95
- const startupUrl = explicitUrl ?? DEFAULT_PROXY_CANDIDATES[0];
96
  const startupProbe = probeByUrl.get(startupUrl);
97
  if (startupProbe?.reachable && !startupProbe.isHeadroom) {
98
  throw new Error(
@@ -126,11 +125,24 @@ export class ProxyManager {
126
  }
127
 
128
  throw new Error(
129
- `Headroom proxy not detected on default endpoints (${DEFAULT_PROXY_CANDIDATES.join(", ")}). ` +
130
  "Set proxyUrl explicitly or enable autoStart.",
131
  );
132
  }
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  /**
135
  * No-op: plugin never starts or manages external processes.
136
  */
@@ -228,7 +240,8 @@ export class ProxyManager {
228
  }
229
 
230
  // 4) Python module fallback
231
- for (const pyCmd of ["python", "python3", "py"]) {
 
232
  specs.push({
233
  label: `Python: ${pyCmd} -m headroom.cli`,
234
  command: pyCmd,
@@ -241,6 +254,20 @@ export class ProxyManager {
241
  return specs;
242
  }
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  private canExecute(command: string, args: string[]): boolean {
245
  try {
246
  const result = spawnSync(command, args, {
 
14
 
15
  export interface ProxyManagerConfig {
16
  proxyUrl?: string;
17
+ proxyPort?: number;
18
+ pythonPath?: string;
19
  autoStart?: boolean;
20
  startupTimeoutMs?: number;
21
  }
 
41
  checkArgs: string[];
42
  }
43
 
 
 
 
 
 
44
  const defaultLogger: ProxyManagerLogger = {
45
  info: (m) => console.log(`[headroom] ${m}`),
46
  warn: (m) => console.warn(`[headroom] ${m}`),
 
62
  * Ensure a proxy is available. Returns the normalized URL origin.
63
  */
64
  async start(): Promise<string> {
65
+ const port = this.getProxyPort();
66
  const explicitUrl =
67
  typeof this.config.proxyUrl === "string" && this.config.proxyUrl.trim().length > 0
68
  ? normalizeAndValidateProxyUrl(this.config.proxyUrl)
69
  : null;
70
+ const defaultCandidates = this.getDefaultProxyCandidates(port);
71
+ const candidateUrls = explicitUrl ? [explicitUrl] : [...defaultCandidates];
72
  const probeByUrl = new Map<string, ProxyProbeResult>();
73
 
74
  for (const url of candidateUrls) {
 
91
  }
92
 
93
  if (this.config.autoStart !== false) {
94
+ const startupUrl = explicitUrl ?? defaultCandidates[0];
95
  const startupProbe = probeByUrl.get(startupUrl);
96
  if (startupProbe?.reachable && !startupProbe.isHeadroom) {
97
  throw new Error(
 
125
  }
126
 
127
  throw new Error(
128
+ `Headroom proxy not detected on default endpoints (${defaultCandidates.join(", ")}). ` +
129
  "Set proxyUrl explicitly or enable autoStart.",
130
  );
131
  }
132
 
133
+ private getProxyPort(): number {
134
+ const rawPort = this.config.proxyPort;
135
+ if (!Number.isInteger(rawPort) || rawPort === undefined) return 8787;
136
+ if (rawPort < 1 || rawPort > 65535) {
137
+ throw new Error("proxyPort must be an integer between 1 and 65535");
138
+ }
139
+ return rawPort;
140
+ }
141
+
142
+ private getDefaultProxyCandidates(port: number): string[] {
143
+ return [`http://127.0.0.1:${port}`, `http://localhost:${port}`];
144
+ }
145
+
146
  /**
147
  * No-op: plugin never starts or manages external processes.
148
  */
 
240
  }
241
 
242
  // 4) Python module fallback
243
+ const pythonCommands = this.getPythonCommands();
244
+ for (const pyCmd of pythonCommands) {
245
  specs.push({
246
  label: `Python: ${pyCmd} -m headroom.cli`,
247
  command: pyCmd,
 
254
  return specs;
255
  }
256
 
257
+ private getPythonCommands(): string[] {
258
+ const commands: string[] = [];
259
+ const configured = typeof this.config.pythonPath === "string"
260
+ ? this.config.pythonPath.trim()
261
+ : "";
262
+ if (configured.length > 0) {
263
+ commands.push(configured);
264
+ }
265
+ for (const fallback of ["python", "python3", "py"]) {
266
+ if (!commands.includes(fallback)) commands.push(fallback);
267
+ }
268
+ return commands;
269
+ }
270
+
271
  private canExecute(command: string, args: string[]): boolean {
272
  try {
273
  const result = spawnSync(command, args, {