copilot-hub 0.1.26 → 0.1.29

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,12 +1,16 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
- import dotenv from "dotenv";
5
4
  import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
6
5
  import { parseTurnActivityTimeoutSetting } from "@copilot-hub/core/codex-app-utils";
7
- import { resolveConfigBaseDir, resolveOptionalPathFromBase, resolvePathFromBase, } from "@copilot-hub/core/config-paths";
6
+ import { loadCopilotHubEnvironment } from "@copilot-hub/core";
7
+ import { resolveOptionalPathFromBase, resolvePathFromBase } from "@copilot-hub/core/config-paths";
8
8
  import { getDefaultExternalWorkspaceBasePath, getKernelRootPath, } from "@copilot-hub/core/workspace-paths";
9
- const envBaseDir = loadEnvironment();
9
+ const loadedEnvironment = loadEnvironment();
10
+ const envBaseDir = loadedEnvironment.baseDir;
11
+ const envFilePath = loadedEnvironment.envPath;
12
+ const envFileValues = loadedEnvironment.fileValues;
13
+ const envOverrideKeys = loadedEnvironment.overriddenKeys;
10
14
  const kernelRootPath = getKernelRootPath();
11
15
  const configuredDefaultWorkspaceRoot = String(process.env.DEFAULT_WORKSPACE_ROOT ?? "").trim();
12
16
  const defaultWorkspaceRoot = resolveWorkspaceRoot(configuredDefaultWorkspaceRoot || getDefaultExternalWorkspaceBasePath(kernelRootPath));
@@ -85,6 +89,9 @@ const defaultAllowedChatIds = new Set((process.env.TELEGRAM_ALLOWED_CHAT_IDS ??
85
89
  fs.mkdirSync(dataDir, { recursive: true });
86
90
  export const config = {
87
91
  envBaseDir,
92
+ envFilePath,
93
+ envFileValues,
94
+ envOverrideKeys,
88
95
  defaultProviderKind,
89
96
  providerDefaults: {
90
97
  defaultKind: defaultProviderKind,
@@ -125,28 +132,15 @@ export const config = {
125
132
  defaultAllowedChatIds,
126
133
  };
127
134
  function loadEnvironment() {
128
- const configuredEnvPath = String(process.env.COPILOT_HUB_ENV_PATH ?? "").trim();
129
- const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
130
- const baseDir = resolveConfigBaseDir({
131
- configuredBaseDir: process.env.COPILOT_HUB_ENV_BASE_DIR,
132
- configuredEnvPath: resolvedEnvPath,
135
+ return loadCopilotHubEnvironment({
133
136
  cwd: process.cwd(),
134
137
  });
135
- if (configuredEnvPath) {
136
- process.env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
137
- dotenv.config({ path: resolvedEnvPath });
138
- }
139
- else {
140
- dotenv.config();
141
- }
142
- process.env.COPILOT_HUB_ENV_BASE_DIR = baseDir;
143
- return baseDir;
144
138
  }
145
139
  function resolveCodexBin(rawValue) {
146
140
  const value = String(rawValue ?? "").trim();
147
141
  const normalized = value.toLowerCase();
148
142
  if (value && normalized !== "codex") {
149
- return value;
143
+ return normalizeConfiguredCodexBin(value);
150
144
  }
151
145
  if (process.platform === "win32") {
152
146
  const npmGlobalCodex = findWindowsNpmGlobalCodexBin();
@@ -160,6 +154,24 @@ function resolveCodexBin(rawValue) {
160
154
  }
161
155
  return value || "codex";
162
156
  }
157
+ function normalizeConfiguredCodexBin(value) {
158
+ const normalizedValue = String(value ?? "").trim();
159
+ if (process.platform !== "win32" || !normalizedValue) {
160
+ return normalizedValue;
161
+ }
162
+ const basename = path.win32.basename(normalizedValue).toLowerCase();
163
+ if (basename !== "codex.cmd" && basename !== "codex.bat") {
164
+ return normalizedValue;
165
+ }
166
+ if (path.win32.isAbsolute(normalizedValue)) {
167
+ const wrapperDir = path.win32.dirname(normalizedValue);
168
+ const entrypoint = path.win32.join(wrapperDir, "node_modules", "@openai", "codex", "bin", "codex.js");
169
+ if (fs.existsSync(entrypoint)) {
170
+ return entrypoint;
171
+ }
172
+ }
173
+ return findWindowsNpmGlobalCodexBin() || normalizedValue;
174
+ }
163
175
  function findVscodeCodexExe() {
164
176
  const userProfile = process.env.USERPROFILE;
165
177
  if (!userProfile) {
@@ -1,5 +1,5 @@
1
- HUB_TELEGRAM_TOKEN_ENV=HUB_TELEGRAM_TOKEN
2
- HUB_TELEGRAM_TOKEN=123456:replace_me
1
+ HUB_TELEGRAM_TOKEN_ENV=HUB_TELEGRAM_TOKEN_FILE
2
+ HUB_TELEGRAM_TOKEN_FILE=123456:replace_me
3
3
  HUB_ID=copilot_hub
4
4
  HUB_NAME=Copilot Hub
5
5
  # Optional. If empty, defaults to Desktop/copilot_workspaces (Windows/macOS/Linux)
@@ -21,4 +21,3 @@ CODEX_SANDBOX=danger-full-access
21
21
  CODEX_APPROVAL_POLICY=never
22
22
  TURN_ACTIVITY_TIMEOUT_MS=0
23
23
  MAX_THREAD_MESSAGES=200
24
-
@@ -1,13 +1,17 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
- import dotenv from "dotenv";
4
+ import { loadCopilotHubEnvironment } from "@copilot-hub/core";
5
5
  import { parseTurnActivityTimeoutSetting } from "@copilot-hub/core/codex-app-utils";
6
- import { resolveConfigBaseDir, resolveOptionalPathFromBase, resolvePathFromBase, } from "@copilot-hub/core/config-paths";
6
+ import { resolveOptionalPathFromBase, resolvePathFromBase } from "@copilot-hub/core/config-paths";
7
7
  import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
8
8
  import { normalizeAdminBotId, normalizeAdminTokenEnv } from "./kernel/admin-contract.js";
9
9
  import { getDefaultExternalWorkspaceBasePath, getKernelRootPath, } from "@copilot-hub/core/workspace-paths";
10
- const envBaseDir = loadEnvironment();
10
+ const loadedEnvironment = loadEnvironment();
11
+ const envBaseDir = loadedEnvironment.baseDir;
12
+ const envFilePath = loadedEnvironment.envPath;
13
+ const envFileValues = loadedEnvironment.fileValues;
14
+ const envOverrideKeys = loadedEnvironment.overriddenKeys;
11
15
  const kernelRootPath = getKernelRootPath();
12
16
  const configuredDefaultWorkspaceRoot = String(process.env.DEFAULT_WORKSPACE_ROOT ?? "").trim();
13
17
  const defaultWorkspaceRoot = resolveWorkspaceRoot(configuredDefaultWorkspaceRoot || getDefaultExternalWorkspaceBasePath(kernelRootPath));
@@ -89,6 +93,9 @@ const defaultAllowedChatIds = new Set((process.env.TELEGRAM_ALLOWED_CHAT_IDS ??
89
93
  fs.mkdirSync(dataDir, { recursive: true });
90
94
  export const config = {
91
95
  envBaseDir,
96
+ envFilePath,
97
+ envFileValues,
98
+ envOverrideKeys,
92
99
  defaultProviderKind,
93
100
  providerDefaults: {
94
101
  defaultKind: defaultProviderKind,
@@ -132,28 +139,15 @@ export const config = {
132
139
  defaultAllowedChatIds,
133
140
  };
134
141
  function loadEnvironment() {
135
- const configuredEnvPath = String(process.env.COPILOT_HUB_ENV_PATH ?? "").trim();
136
- const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
137
- const baseDir = resolveConfigBaseDir({
138
- configuredBaseDir: process.env.COPILOT_HUB_ENV_BASE_DIR,
139
- configuredEnvPath: resolvedEnvPath,
142
+ return loadCopilotHubEnvironment({
140
143
  cwd: process.cwd(),
141
144
  });
142
- if (configuredEnvPath) {
143
- process.env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
144
- dotenv.config({ path: resolvedEnvPath });
145
- }
146
- else {
147
- dotenv.config();
148
- }
149
- process.env.COPILOT_HUB_ENV_BASE_DIR = baseDir;
150
- return baseDir;
151
145
  }
152
146
  function resolveCodexBin(rawValue) {
153
147
  const value = String(rawValue ?? "").trim();
154
148
  const normalized = value.toLowerCase();
155
149
  if (value && normalized !== "codex") {
156
- return value;
150
+ return normalizeConfiguredCodexBin(value);
157
151
  }
158
152
  if (process.platform === "win32") {
159
153
  const npmGlobalCodex = findWindowsNpmGlobalCodexBin();
@@ -167,6 +161,24 @@ function resolveCodexBin(rawValue) {
167
161
  }
168
162
  return value || "codex";
169
163
  }
164
+ function normalizeConfiguredCodexBin(value) {
165
+ const normalizedValue = String(value ?? "").trim();
166
+ if (process.platform !== "win32" || !normalizedValue) {
167
+ return normalizedValue;
168
+ }
169
+ const basename = path.win32.basename(normalizedValue).toLowerCase();
170
+ if (basename !== "codex.cmd" && basename !== "codex.bat") {
171
+ return normalizedValue;
172
+ }
173
+ if (path.win32.isAbsolute(normalizedValue)) {
174
+ const wrapperDir = path.win32.dirname(normalizedValue);
175
+ const entrypoint = path.win32.join(wrapperDir, "node_modules", "@openai", "codex", "bin", "codex.js");
176
+ if (fs.existsSync(entrypoint)) {
177
+ return entrypoint;
178
+ }
179
+ }
180
+ return findWindowsNpmGlobalCodexBin() || normalizedValue;
181
+ }
170
182
  function findVscodeCodexExe() {
171
183
  const userProfile = process.env.USERPROFILE;
172
184
  if (!userProfile) {
@@ -3,16 +3,20 @@ import { BotRuntime } from "@copilot-hub/core/bot-runtime";
3
3
  import { config } from "./config.js";
4
4
  import { assertWorkspaceAllowed } from "@copilot-hub/core/workspace-policy";
5
5
  import { createChannelAdapter } from "./channels/channel-factory.js";
6
- const TELEGRAM_TOKEN_PATTERN = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
7
- const tokenEnvName = String(process.env.HUB_TELEGRAM_TOKEN_ENV ?? "HUB_TELEGRAM_TOKEN").trim() || "HUB_TELEGRAM_TOKEN";
8
- const hubToken = String(process.env[tokenEnvName] ?? "").trim();
6
+ import { isUsableTelegramToken, resolveHubTelegramToken } from "./hub-token-config.js";
7
+ const tokenResolution = resolveHubTelegramToken({
8
+ env: process.env,
9
+ envFileValues: config.envFileValues,
10
+ });
11
+ const tokenEnvName = tokenResolution.tokenEnvName;
12
+ const hubToken = tokenResolution.token;
9
13
  if (!hubToken) {
10
14
  throw new Error([
11
15
  `Hub Telegram token is missing (${tokenEnvName}).`,
12
16
  "Set this token to start copilot-hub.",
13
17
  ].join("\n"));
14
18
  }
15
- if (!TELEGRAM_TOKEN_PATTERN.test(hubToken) || hubToken.toLowerCase().includes("replace_me")) {
19
+ if (!isUsableTelegramToken(hubToken)) {
16
20
  throw new Error([
17
21
  `Hub Telegram token in ${tokenEnvName} is invalid.`,
18
22
  "Run 'copilot-hub configure' and paste a real BotFather token.",
@@ -73,6 +77,14 @@ const runtime = new BotRuntime({
73
77
  let shuttingDown = false;
74
78
  await bootstrap();
75
79
  async function bootstrap() {
80
+ for (const warning of tokenResolution.warnings) {
81
+ console.warn(`[copilot_hub] ${warning}`);
82
+ }
83
+ if (config.envOverrideKeys.includes("HUB_TELEGRAM_TOKEN_ENV") ||
84
+ config.envOverrideKeys.includes(tokenEnvName)) {
85
+ console.warn(`[copilot_hub] Config file '${config.envFilePath ?? ".env"}' overrode a pre-existing process environment value for the hub token settings.`);
86
+ }
87
+ console.log(`[copilot_hub] using hub Telegram token variable '${tokenEnvName}' from ${tokenResolution.source === "env_file" ? "config file" : "process environment"}.`);
76
88
  await runtime.startChannels();
77
89
  console.log(`[copilot_hub] online as '${hubId}' on workspace '${hubWorkspaceRoot}'.`);
78
90
  registerSignals();
@@ -0,0 +1,37 @@
1
+ const DEFAULT_HUB_TELEGRAM_TOKEN_ENV = "HUB_TELEGRAM_TOKEN_FILE";
2
+ const LEGACY_HUB_TELEGRAM_TOKEN_ENV = "HUB_TELEGRAM_TOKEN";
3
+ const TELEGRAM_TOKEN_PATTERN = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
4
+ export function resolveHubTelegramToken({ env = process.env, envFileValues = {}, } = {}) {
5
+ const fileTokenEnvName = String(envFileValues.HUB_TELEGRAM_TOKEN_ENV ?? "").trim();
6
+ const processTokenEnvName = String(env.HUB_TELEGRAM_TOKEN_ENV ?? "").trim();
7
+ const tokenEnvName = fileTokenEnvName || processTokenEnvName || DEFAULT_HUB_TELEGRAM_TOKEN_ENV;
8
+ const fileToken = String(envFileValues[tokenEnvName] ?? "").trim();
9
+ const processToken = String(env[tokenEnvName] ?? "").trim();
10
+ const token = fileToken || processToken;
11
+ const warnings = [];
12
+ if (fileTokenEnvName && processTokenEnvName && fileTokenEnvName !== processTokenEnvName) {
13
+ warnings.push(`Config file selects hub token variable '${fileTokenEnvName}', overriding pre-existing process env selector '${processTokenEnvName}'.`);
14
+ }
15
+ if (tokenEnvName === LEGACY_HUB_TELEGRAM_TOKEN_ENV) {
16
+ warnings.push(`Hub token variable '${LEGACY_HUB_TELEGRAM_TOKEN_ENV}' is deprecated. Run 'copilot-hub configure' to migrate to '${DEFAULT_HUB_TELEGRAM_TOKEN_ENV}'.`);
17
+ }
18
+ if (fileToken && processToken && fileToken !== processToken) {
19
+ const detail = isUsableTelegramToken(fileToken)
20
+ ? `Using the token saved in the config file for '${tokenEnvName}' instead of the conflicting process environment value.`
21
+ : `Config file value for '${tokenEnvName}' overrides the conflicting process environment value.`;
22
+ warnings.push(detail);
23
+ }
24
+ return {
25
+ tokenEnvName,
26
+ token,
27
+ source: fileToken ? "env_file" : "process_env",
28
+ warnings,
29
+ };
30
+ }
31
+ export function isUsableTelegramToken(value) {
32
+ const token = String(value ?? "").trim();
33
+ if (!token || token.toLowerCase().includes("replace_me")) {
34
+ return false;
35
+ }
36
+ return TELEGRAM_TOKEN_PATTERN.test(token);
37
+ }
@@ -0,0 +1,46 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ let configPromise = null;
4
+ async function loadConfig() {
5
+ if (!configPromise) {
6
+ const specifier = ["..", "hub-token-config.js"].join("/");
7
+ configPromise = import(specifier);
8
+ }
9
+ return configPromise;
10
+ }
11
+ test("resolveHubTelegramToken prefers the config file token over a conflicting process env token", async () => {
12
+ const { resolveHubTelegramToken } = await loadConfig();
13
+ const result = resolveHubTelegramToken({
14
+ env: {
15
+ HUB_TELEGRAM_TOKEN_ENV: "HUB_TELEGRAM_TOKEN",
16
+ HUB_TELEGRAM_TOKEN: "123456:invalid_process_value_ABCDEFGHIJKLM",
17
+ },
18
+ envFileValues: {
19
+ HUB_TELEGRAM_TOKEN_ENV: "HUB_TELEGRAM_TOKEN",
20
+ HUB_TELEGRAM_TOKEN: "123456:valid_file_value_ABCDEFGHIJKLMNOPQRSTUVWXYZ",
21
+ },
22
+ });
23
+ assert.equal(result.tokenEnvName, "HUB_TELEGRAM_TOKEN");
24
+ assert.equal(result.source, "env_file");
25
+ assert.equal(result.token, "123456:valid_file_value_ABCDEFGHIJKLMNOPQRSTUVWXYZ");
26
+ assert.match(result.warnings.join("\n"), /deprecated/i);
27
+ });
28
+ test("resolveHubTelegramToken uses the dedicated file token variable by default", async () => {
29
+ const { resolveHubTelegramToken } = await loadConfig();
30
+ const result = resolveHubTelegramToken({
31
+ env: {},
32
+ envFileValues: {
33
+ HUB_TELEGRAM_TOKEN_ENV: "HUB_TELEGRAM_TOKEN_FILE",
34
+ HUB_TELEGRAM_TOKEN_FILE: "123456:valid_file_value_ABCDEFGHIJKLMNOPQRSTUVWXYZ",
35
+ },
36
+ });
37
+ assert.equal(result.tokenEnvName, "HUB_TELEGRAM_TOKEN_FILE");
38
+ assert.equal(result.source, "env_file");
39
+ assert.equal(result.warnings.length, 0);
40
+ });
41
+ test("isUsableTelegramToken recognizes valid telegram token formats", async () => {
42
+ const { isUsableTelegramToken } = await loadConfig();
43
+ assert.equal(isUsableTelegramToken("123456:valid_file_value_ABCDEFGHIJKLMNOPQRSTUVWXYZ"), true);
44
+ assert.equal(isUsableTelegramToken("123456:replace_me"), false);
45
+ assert.equal(isUsableTelegramToken(""), false);
46
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-hub",
3
- "version": "0.1.26",
3
+ "version": "0.1.29",
4
4
  "description": "Copilot Hub CLI and runtime bundle",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,10 @@
1
+ export interface LoadedCopilotHubEnvironment {
2
+ baseDir: string;
3
+ envPath: string | null;
4
+ fileValues: Record<string, string>;
5
+ overriddenKeys: string[];
6
+ }
7
+ export declare function loadCopilotHubEnvironment({ env, cwd, }?: {
8
+ env?: NodeJS.ProcessEnv;
9
+ cwd?: string;
10
+ }): LoadedCopilotHubEnvironment;
@@ -0,0 +1,66 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import dotenv from "dotenv";
5
+ import { resolveConfigBaseDir } from "./config-paths.js";
6
+ export function loadCopilotHubEnvironment({ env = process.env, cwd = process.cwd(), } = {}) {
7
+ const configuredEnvPath = String(env.COPILOT_HUB_ENV_PATH ?? "").trim();
8
+ const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
9
+ const discoveredEnvPath = resolvedEnvPath || resolveDefaultEnvPath(cwd);
10
+ const baseDir = resolveConfigBaseDir({
11
+ configuredBaseDir: env.COPILOT_HUB_ENV_BASE_DIR,
12
+ configuredEnvPath: discoveredEnvPath,
13
+ cwd,
14
+ });
15
+ const fileValues = loadEnvFileValues(discoveredEnvPath);
16
+ const overriddenKeys = applyEnvFileValues(env, fileValues);
17
+ if (resolvedEnvPath) {
18
+ env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
19
+ }
20
+ env.COPILOT_HUB_ENV_BASE_DIR = baseDir;
21
+ return {
22
+ baseDir,
23
+ envPath: discoveredEnvPath || null,
24
+ fileValues,
25
+ overriddenKeys,
26
+ };
27
+ }
28
+ function resolveDefaultEnvPath(cwd) {
29
+ const candidate = path.resolve(String(cwd ?? process.cwd()), ".env");
30
+ return fs.existsSync(candidate) ? candidate : "";
31
+ }
32
+ function loadEnvFileValues(filePath) {
33
+ if (!filePath || !fs.existsSync(filePath)) {
34
+ return {};
35
+ }
36
+ try {
37
+ const raw = fs.readFileSync(filePath, "utf8");
38
+ return normalizeEnvMap(dotenv.parse(raw));
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ function applyEnvFileValues(env, fileValues) {
45
+ const overriddenKeys = [];
46
+ for (const [key, value] of Object.entries(fileValues)) {
47
+ const previousValue = String(env[key] ?? "");
48
+ if (previousValue && previousValue !== value) {
49
+ overriddenKeys.push(key);
50
+ }
51
+ env[key] = value;
52
+ }
53
+ return overriddenKeys.sort();
54
+ }
55
+ function normalizeEnvMap(value) {
56
+ const output = {};
57
+ for (const [key, entry] of Object.entries(value ?? {})) {
58
+ const normalizedKey = String(key ?? "").trim();
59
+ if (!normalizedKey) {
60
+ continue;
61
+ }
62
+ output[normalizedKey] = String(entry ?? "").trim();
63
+ }
64
+ return output;
65
+ }
66
+ //# sourceMappingURL=env-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-config.js","sourceRoot":"","sources":["../src/env-config.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  export { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, isPathInside, normalizeAbsolutePath, parseWorkspaceAllowedRoots, } from "./workspace-policy.js";
2
2
  export { normalizeThreadId } from "./thread-id.js";
3
3
  export { createProjectFingerprint } from "./project-fingerprint.js";
4
- export { DEFAULT_EXTERNAL_WORKSPACES_DIRNAME, getKernelRootPath, getDefaultExternalWorkspaceBasePath, resolveDefaultWorkspaceForBot, isPathInside as isWorkspacePathInside, } from "./workspace-paths.js";
4
+ export { DEFAULT_EXTERNAL_WORKSPACES_DIRNAME, getKernelRootPath, resolveKernelRootPath, getDefaultExternalWorkspaceBasePath, resolveExternalWorkspaceBasePath, resolveDefaultWorkspaceForBot, isPathInside as isWorkspacePathInside, } from "./workspace-paths.js";
5
5
  export { resolveConfigBaseDir, resolveProcessConfigBaseDir, resolvePathFromBase, resolveOptionalPathFromBase, } from "./config-paths.js";
6
+ export { loadCopilotHubEnvironment } from "./env-config.js";
6
7
  export { InstanceLock } from "./instance-lock.js";
7
8
  export { JsonStateStore } from "./state-store.js";
8
9
  export { assertControlPermission } from "./control-permission.js";
@@ -1,8 +1,9 @@
1
1
  export { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, isPathInside, normalizeAbsolutePath, parseWorkspaceAllowedRoots, } from "./workspace-policy.js";
2
2
  export { normalizeThreadId } from "./thread-id.js";
3
3
  export { createProjectFingerprint } from "./project-fingerprint.js";
4
- export { DEFAULT_EXTERNAL_WORKSPACES_DIRNAME, getKernelRootPath, getDefaultExternalWorkspaceBasePath, resolveDefaultWorkspaceForBot, isPathInside as isWorkspacePathInside, } from "./workspace-paths.js";
4
+ export { DEFAULT_EXTERNAL_WORKSPACES_DIRNAME, getKernelRootPath, resolveKernelRootPath, getDefaultExternalWorkspaceBasePath, resolveExternalWorkspaceBasePath, resolveDefaultWorkspaceForBot, isPathInside as isWorkspacePathInside, } from "./workspace-paths.js";
5
5
  export { resolveConfigBaseDir, resolveProcessConfigBaseDir, resolvePathFromBase, resolveOptionalPathFromBase, } from "./config-paths.js";
6
+ export { loadCopilotHubEnvironment } from "./env-config.js";
6
7
  export { InstanceLock } from "./instance-lock.js";
7
8
  export { JsonStateStore } from "./state-store.js";
8
9
  export { assertControlPermission } from "./control-permission.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,YAAY,EACZ,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,mCAAmC,EACnC,iBAAiB,EACjB,mCAAmC,EACnC,6BAA6B,EAC7B,YAAY,IAAI,qBAAqB,GACtC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,YAAY,EACZ,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,mCAAmC,EACnC,iBAAiB,EACjB,qBAAqB,EACrB,mCAAmC,EACnC,gCAAgC,EAChC,6BAA6B,EAC7B,YAAY,IAAI,qBAAqB,GACtC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -1,5 +1,16 @@
1
1
  export declare const DEFAULT_EXTERNAL_WORKSPACES_DIRNAME = "copilot_workspaces";
2
2
  export declare function getKernelRootPath(): string;
3
- export declare function getDefaultExternalWorkspaceBasePath(_kernelRoot?: string): string;
3
+ export declare function resolveKernelRootPath({ env, moduleUrl, cwd, }?: {
4
+ env?: NodeJS.ProcessEnv;
5
+ moduleUrl?: string;
6
+ cwd?: string;
7
+ }): string;
8
+ export declare function getDefaultExternalWorkspaceBasePath(kernelRoot?: string): string;
9
+ export declare function resolveExternalWorkspaceBasePath({ kernelRootPath, desktopCandidates, homeDir, tempDir, }: {
10
+ kernelRootPath: string;
11
+ desktopCandidates?: readonly string[];
12
+ homeDir?: string;
13
+ tempDir?: string;
14
+ }): string;
4
15
  export declare function resolveDefaultWorkspaceForBot(botId: unknown, kernelRoot?: string): string;
5
16
  export declare function isPathInside(parentPath: unknown, candidatePath: unknown): boolean;
@@ -1,18 +1,42 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
+ import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
4
6
  export const DEFAULT_EXTERNAL_WORKSPACES_DIRNAME = "copilot_workspaces";
5
7
  const DESKTOP_DIRNAME = "Desktop";
6
8
  export function getKernelRootPath() {
7
- return path.resolve(process.cwd());
9
+ return resolveKernelRootPath();
8
10
  }
9
- export function getDefaultExternalWorkspaceBasePath(_kernelRoot = getKernelRootPath()) {
10
- const desktopCandidates = getDesktopCandidates();
11
- const fallbackDesktop = path.resolve(process.cwd(), DESKTOP_DIRNAME);
11
+ export function resolveKernelRootPath({ env = process.env, moduleUrl = import.meta.url, cwd = process.cwd(), } = {}) {
12
+ const configuredRoot = String(env.COPILOT_HUB_KERNEL_ROOT ?? "").trim();
13
+ if (configuredRoot) {
14
+ return path.resolve(configuredRoot);
15
+ }
16
+ const moduleFilePath = String(moduleUrl ?? "").trim();
17
+ if (moduleFilePath) {
18
+ return path.resolve(path.dirname(fileURLToPath(moduleFilePath)), "..", "..", "..");
19
+ }
20
+ return path.resolve(String(cwd ?? process.cwd()));
21
+ }
22
+ export function getDefaultExternalWorkspaceBasePath(kernelRoot = getKernelRootPath()) {
23
+ return resolveExternalWorkspaceBasePath({
24
+ kernelRootPath: kernelRoot,
25
+ });
26
+ }
27
+ export function resolveExternalWorkspaceBasePath({ kernelRootPath, desktopCandidates = getDesktopCandidates(), homeDir = os.homedir(), tempDir = os.tmpdir(), }) {
12
28
  const desktopRoot = desktopCandidates.find((candidate) => directoryExists(candidate)) ??
13
29
  desktopCandidates[0] ??
14
- fallbackDesktop;
15
- return path.resolve(desktopRoot, DEFAULT_EXTERNAL_WORKSPACES_DIRNAME);
30
+ path.resolve(String(homeDir ?? "").trim() || process.cwd(), DESKTOP_DIRNAME);
31
+ const preferredRoot = path.resolve(desktopRoot, DEFAULT_EXTERNAL_WORKSPACES_DIRNAME);
32
+ if (!isPathInside(kernelRootPath, preferredRoot)) {
33
+ return preferredRoot;
34
+ }
35
+ const homeFallbackRoot = path.resolve(String(homeDir ?? "").trim() || process.cwd(), DEFAULT_EXTERNAL_WORKSPACES_DIRNAME);
36
+ if (!isPathInside(kernelRootPath, homeFallbackRoot)) {
37
+ return homeFallbackRoot;
38
+ }
39
+ return path.resolve(String(tempDir ?? "").trim() || process.cwd(), DEFAULT_EXTERNAL_WORKSPACES_DIRNAME);
16
40
  }
17
41
  export function resolveDefaultWorkspaceForBot(botId, kernelRoot = getKernelRootPath()) {
18
42
  const id = String(botId ?? "").trim();
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-paths.js","sourceRoot":"","sources":["../src/workspace-paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-paths.js","sourceRoot":"","sources":["../src/workspace-paths.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,10 @@
34
34
  "types": "./dist/config-paths.d.ts",
35
35
  "import": "./dist/config-paths.js"
36
36
  },
37
+ "./env-config": {
38
+ "types": "./dist/env-config.d.ts",
39
+ "import": "./dist/env-config.js"
40
+ },
37
41
  "./instance-lock": {
38
42
  "types": "./dist/instance-lock.d.ts",
39
43
  "import": "./dist/instance-lock.js"
@@ -42,9 +42,9 @@ export function resolveCodexBinForStart({ repoRoot, agentEngineEnvPath, controlP
42
42
  userConfigured: false,
43
43
  };
44
44
  }
45
- export function resolveCompatibleInstalledCodexBin({ repoRoot, env = process.env, }) {
45
+ export function resolveCompatibleInstalledCodexBin({ repoRoot, env = process.env, platform = process.platform, }) {
46
46
  const matches = [];
47
- for (const candidate of listCodexBinCandidates(env, repoRoot)) {
47
+ for (const candidate of listCodexBinCandidates(env, repoRoot, platform)) {
48
48
  const probe = probeCodexVersion({
49
49
  codexBin: candidate,
50
50
  repoRoot,
@@ -55,7 +55,7 @@ export function resolveCompatibleInstalledCodexBin({ repoRoot, env = process.env
55
55
  matches.push({
56
56
  candidate,
57
57
  version: probe.version,
58
- priority: getCodexCandidatePriority(candidate, env, repoRoot),
58
+ priority: getCodexCandidatePriority(candidate, env, repoRoot, platform),
59
59
  });
60
60
  }
61
61
  if (matches.length === 0) {
@@ -134,12 +134,17 @@ export function buildCodexCompatibilityError({ resolved, probe, includeInstallHi
134
134
  return lines.join("\n");
135
135
  }
136
136
  function buildResolvedCodexBin({ value, source, env, repoRoot, }) {
137
- const normalized = String(value ?? "")
137
+ const normalizedValue = normalizeConfiguredCodexBin({
138
+ value,
139
+ env,
140
+ repoRoot,
141
+ });
142
+ const normalized = String(normalizedValue ?? "")
138
143
  .trim()
139
144
  .toLowerCase();
140
145
  if (normalized && normalized !== "codex") {
141
146
  return {
142
- bin: value,
147
+ bin: normalizedValue,
143
148
  source,
144
149
  userConfigured: true,
145
150
  };
@@ -151,20 +156,43 @@ function buildResolvedCodexBin({ value, source, env, repoRoot, }) {
151
156
  userConfigured: false,
152
157
  };
153
158
  }
159
+ export function normalizeConfiguredCodexBin({ value, env = process.env, repoRoot, platform = process.platform, }) {
160
+ const normalizedValue = String(value ?? "").trim();
161
+ if (!normalizedValue || platform !== "win32") {
162
+ return normalizedValue;
163
+ }
164
+ const normalizedBasename = path.win32.basename(normalizedValue).toLowerCase();
165
+ if (normalizedBasename !== "codex.cmd" && normalizedBasename !== "codex.bat") {
166
+ return normalizedValue;
167
+ }
168
+ if (path.win32.isAbsolute(normalizedValue)) {
169
+ const pathModule = selectPathModule(normalizedValue);
170
+ const wrapperDir = pathModule.dirname(normalizedValue);
171
+ const entrypoint = pathModule.join(wrapperDir, "node_modules", "@openai", "codex", "bin", "codex.js");
172
+ if (fs.existsSync(entrypoint)) {
173
+ return entrypoint;
174
+ }
175
+ }
176
+ return findWindowsNpmGlobalCodexBin(env, repoRoot, platform) || normalizedValue;
177
+ }
154
178
  function findDetectedCodexBin(env, repoRoot) {
155
179
  if (process.platform !== "win32") {
156
180
  return "";
157
181
  }
158
- return findWindowsNpmGlobalCodexBin(env, repoRoot) || findVscodeCodexExe(env) || "";
182
+ return (findWindowsNpmGlobalCodexBin(env, repoRoot, process.platform) || findVscodeCodexExe(env) || "");
159
183
  }
160
- function listCodexBinCandidates(env, repoRoot) {
161
- return dedupe(["codex", findWindowsNpmGlobalCodexBin(env, repoRoot), findVscodeCodexExe(env)]);
184
+ function listCodexBinCandidates(env, repoRoot, platform) {
185
+ return dedupe([
186
+ "codex",
187
+ findWindowsNpmGlobalCodexBin(env, repoRoot, platform),
188
+ findVscodeCodexExe(env),
189
+ ]);
162
190
  }
163
- function getCodexCandidatePriority(candidate, env, repoRoot) {
191
+ function getCodexCandidatePriority(candidate, env, repoRoot, platform) {
164
192
  if (candidate === "codex") {
165
193
  return 0;
166
194
  }
167
- const npmGlobal = findWindowsNpmGlobalCodexBin(env, repoRoot);
195
+ const npmGlobal = findWindowsNpmGlobalCodexBin(env, repoRoot, platform);
168
196
  if (npmGlobal && candidate === npmGlobal) {
169
197
  return 1;
170
198
  }
@@ -198,8 +226,8 @@ function findVscodeCodexExe(env) {
198
226
  }
199
227
  return "";
200
228
  }
201
- function findWindowsNpmGlobalCodexBin(env, repoRoot) {
202
- if (process.platform !== "win32") {
229
+ function findWindowsNpmGlobalCodexBin(env, repoRoot, platform) {
230
+ if (platform !== "win32") {
203
231
  return "";
204
232
  }
205
233
  const packageRoots = [];
@@ -231,6 +259,13 @@ function findWindowsNpmGlobalCodexBin(env, repoRoot) {
231
259
  }
232
260
  return "";
233
261
  }
262
+ function selectPathModule(filePath) {
263
+ const normalized = String(filePath ?? "").trim();
264
+ if (/^[A-Za-z]:[\\/]/.test(normalized) || normalized.includes("\\")) {
265
+ return path.win32;
266
+ }
267
+ return path.posix;
268
+ }
234
269
  function readNpmPrefix(repoRoot) {
235
270
  const result = spawnNpm(["config", "get", "prefix"], repoRoot);
236
271
  if (result.error || result.status !== 0) {
@@ -15,7 +15,8 @@ const engineExamplePath = path.join(repoRoot, "apps", "agent-engine", ".env.exam
15
15
  const controlPlaneEnvPath = layout.controlPlaneEnvPath;
16
16
  const controlPlaneExamplePath = path.join(repoRoot, "apps", "control-plane", ".env.example");
17
17
  const TELEGRAM_TOKEN_PATTERN = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
18
- const DEFAULT_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN";
18
+ const DEFAULT_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN_FILE";
19
+ const LEGACY_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN";
19
20
  const args = new Set(process.argv.slice(2));
20
21
  const requiredOnly = args.has("--required-only");
21
22
  await main();
@@ -44,9 +45,7 @@ async function main() {
44
45
  writeLines(controlPlaneEnvPath, controlPlaneLines);
45
46
  }
46
47
  async function configureRequiredTokens({ rl, controlPlaneLines }) {
47
- const controlPlaneMap = parseEnvMap(controlPlaneLines);
48
- const controlPlaneTokenEnvName = nonEmpty(controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV, DEFAULT_CONTROL_PLANE_TOKEN_ENV);
49
- setEnvValue(controlPlaneLines, "HUB_TELEGRAM_TOKEN_ENV", controlPlaneTokenEnvName);
48
+ const controlPlaneTokenEnvName = migrateControlPlaneTokenEnv(controlPlaneLines);
50
49
  const postControlPlaneMap = parseEnvMap(controlPlaneLines);
51
50
  const currentToken = String(postControlPlaneMap[controlPlaneTokenEnvName] ?? "").trim();
52
51
  if (isUsableTelegramToken(currentToken)) {
@@ -62,10 +61,8 @@ async function configureRequiredTokens({ rl, controlPlaneLines }) {
62
61
  console.log("Required token saved.");
63
62
  }
64
63
  async function configureAll({ rl, controlPlaneLines }) {
65
- const controlPlaneMap = parseEnvMap(controlPlaneLines);
66
64
  console.log("\nCopilot Hub control-plane configuration\n");
67
- const controlPlaneTokenEnvDefault = nonEmpty(controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV, DEFAULT_CONTROL_PLANE_TOKEN_ENV);
68
- setEnvValue(controlPlaneLines, "HUB_TELEGRAM_TOKEN_ENV", controlPlaneTokenEnvDefault);
65
+ const controlPlaneTokenEnvDefault = migrateControlPlaneTokenEnv(controlPlaneLines);
69
66
  const currentControlPlaneToken = String(parseEnvMap(controlPlaneLines)[controlPlaneTokenEnvDefault] ?? "").trim();
70
67
  const newControlPlaneToken = currentControlPlaneToken
71
68
  ? await askTelegramToken(rl, "Control-plane Telegram token (press Enter to keep current)", true)
@@ -77,6 +74,23 @@ async function configureAll({ rl, controlPlaneLines }) {
77
74
  console.log("- Control-plane token left unchanged.");
78
75
  }
79
76
  }
77
+ function migrateControlPlaneTokenEnv(lines) {
78
+ const controlPlaneMap = parseEnvMap(lines);
79
+ const configuredTokenEnvName = nonEmpty(controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV, DEFAULT_CONTROL_PLANE_TOKEN_ENV);
80
+ const shouldMigrateLegacyName = configuredTokenEnvName === LEGACY_CONTROL_PLANE_TOKEN_ENV;
81
+ const nextTokenEnvName = shouldMigrateLegacyName
82
+ ? DEFAULT_CONTROL_PLANE_TOKEN_ENV
83
+ : configuredTokenEnvName;
84
+ setEnvValue(lines, "HUB_TELEGRAM_TOKEN_ENV", nextTokenEnvName);
85
+ if (shouldMigrateLegacyName) {
86
+ const legacyToken = String(controlPlaneMap[LEGACY_CONTROL_PLANE_TOKEN_ENV] ?? "").trim();
87
+ const dedicatedToken = String(controlPlaneMap[DEFAULT_CONTROL_PLANE_TOKEN_ENV] ?? "").trim();
88
+ if (legacyToken && !dedicatedToken) {
89
+ setEnvValue(lines, DEFAULT_CONTROL_PLANE_TOKEN_ENV, legacyToken);
90
+ }
91
+ }
92
+ return nextTokenEnvName;
93
+ }
80
94
  function ensureEnvFile(envPath, examplePath) {
81
95
  fs.mkdirSync(path.dirname(envPath), { recursive: true });
82
96
  if (fs.existsSync(envPath)) {
@@ -508,6 +508,23 @@ function detectFatalStartupError(ensureResult) {
508
508
  detectedAt: new Date().toISOString(),
509
509
  };
510
510
  }
511
+ const invalidHubTokenLine = findLineContaining(evidenceChunks, (line) => line.includes("hub telegram token in") && line.includes("is invalid"));
512
+ if (invalidHubTokenLine) {
513
+ return {
514
+ reason: invalidHubTokenLine,
515
+ action: "Run 'copilot-hub configure' to save a valid hub token in the control-plane config, then retry service.",
516
+ detectedAt: new Date().toISOString(),
517
+ };
518
+ }
519
+ const workspaceRootLine = findLineContaining(evidenceChunks, (line) => line.includes("default_workspace_root must be outside kernel directory") ||
520
+ line.includes("hub_workspace_root must be outside kernel directory"));
521
+ if (workspaceRootLine) {
522
+ return {
523
+ reason: workspaceRootLine,
524
+ action: "Set DEFAULT_WORKSPACE_ROOT to a folder outside the copilot-hub installation, then retry service.",
525
+ detectedAt: new Date().toISOString(),
526
+ };
527
+ }
511
528
  return null;
512
529
  }
513
530
  function readLogTail(filePath, maxLines = 120) {
@@ -82,13 +82,15 @@ export function resolveCodexBinForStart({
82
82
  export function resolveCompatibleInstalledCodexBin({
83
83
  repoRoot,
84
84
  env = process.env,
85
+ platform = process.platform,
85
86
  }: {
86
87
  repoRoot: string;
87
88
  env?: NodeJS.ProcessEnv;
89
+ platform?: NodeJS.Platform;
88
90
  }): string {
89
91
  const matches: Array<{ candidate: string; version: string; priority: number }> = [];
90
92
 
91
- for (const candidate of listCodexBinCandidates(env, repoRoot)) {
93
+ for (const candidate of listCodexBinCandidates(env, repoRoot, platform)) {
92
94
  const probe = probeCodexVersion({
93
95
  codexBin: candidate,
94
96
  repoRoot,
@@ -100,7 +102,7 @@ export function resolveCompatibleInstalledCodexBin({
100
102
  matches.push({
101
103
  candidate,
102
104
  version: probe.version,
103
- priority: getCodexCandidatePriority(candidate, env, repoRoot),
105
+ priority: getCodexCandidatePriority(candidate, env, repoRoot, platform),
104
106
  });
105
107
  }
106
108
 
@@ -230,12 +232,17 @@ function buildResolvedCodexBin({
230
232
  env: NodeJS.ProcessEnv;
231
233
  repoRoot: string;
232
234
  }): ResolvedCodexBin {
233
- const normalized = String(value ?? "")
235
+ const normalizedValue = normalizeConfiguredCodexBin({
236
+ value,
237
+ env,
238
+ repoRoot,
239
+ });
240
+ const normalized = String(normalizedValue ?? "")
234
241
  .trim()
235
242
  .toLowerCase();
236
243
  if (normalized && normalized !== "codex") {
237
244
  return {
238
- bin: value,
245
+ bin: normalizedValue,
239
246
  source,
240
247
  userConfigured: true,
241
248
  };
@@ -249,28 +256,79 @@ function buildResolvedCodexBin({
249
256
  };
250
257
  }
251
258
 
259
+ export function normalizeConfiguredCodexBin({
260
+ value,
261
+ env = process.env,
262
+ repoRoot,
263
+ platform = process.platform,
264
+ }: {
265
+ value: string;
266
+ env?: NodeJS.ProcessEnv;
267
+ repoRoot: string;
268
+ platform?: NodeJS.Platform;
269
+ }): string {
270
+ const normalizedValue = String(value ?? "").trim();
271
+ if (!normalizedValue || platform !== "win32") {
272
+ return normalizedValue;
273
+ }
274
+
275
+ const normalizedBasename = path.win32.basename(normalizedValue).toLowerCase();
276
+ if (normalizedBasename !== "codex.cmd" && normalizedBasename !== "codex.bat") {
277
+ return normalizedValue;
278
+ }
279
+
280
+ if (path.win32.isAbsolute(normalizedValue)) {
281
+ const pathModule = selectPathModule(normalizedValue);
282
+ const wrapperDir = pathModule.dirname(normalizedValue);
283
+ const entrypoint = pathModule.join(
284
+ wrapperDir,
285
+ "node_modules",
286
+ "@openai",
287
+ "codex",
288
+ "bin",
289
+ "codex.js",
290
+ );
291
+ if (fs.existsSync(entrypoint)) {
292
+ return entrypoint;
293
+ }
294
+ }
295
+
296
+ return findWindowsNpmGlobalCodexBin(env, repoRoot, platform) || normalizedValue;
297
+ }
298
+
252
299
  function findDetectedCodexBin(env: NodeJS.ProcessEnv, repoRoot: string): string {
253
300
  if (process.platform !== "win32") {
254
301
  return "";
255
302
  }
256
303
 
257
- return findWindowsNpmGlobalCodexBin(env, repoRoot) || findVscodeCodexExe(env) || "";
304
+ return (
305
+ findWindowsNpmGlobalCodexBin(env, repoRoot, process.platform) || findVscodeCodexExe(env) || ""
306
+ );
258
307
  }
259
308
 
260
- function listCodexBinCandidates(env: NodeJS.ProcessEnv, repoRoot: string): string[] {
261
- return dedupe(["codex", findWindowsNpmGlobalCodexBin(env, repoRoot), findVscodeCodexExe(env)]);
309
+ function listCodexBinCandidates(
310
+ env: NodeJS.ProcessEnv,
311
+ repoRoot: string,
312
+ platform: NodeJS.Platform,
313
+ ): string[] {
314
+ return dedupe([
315
+ "codex",
316
+ findWindowsNpmGlobalCodexBin(env, repoRoot, platform),
317
+ findVscodeCodexExe(env),
318
+ ]);
262
319
  }
263
320
 
264
321
  function getCodexCandidatePriority(
265
322
  candidate: string,
266
323
  env: NodeJS.ProcessEnv,
267
324
  repoRoot: string,
325
+ platform: NodeJS.Platform,
268
326
  ): number {
269
327
  if (candidate === "codex") {
270
328
  return 0;
271
329
  }
272
330
 
273
- const npmGlobal = findWindowsNpmGlobalCodexBin(env, repoRoot);
331
+ const npmGlobal = findWindowsNpmGlobalCodexBin(env, repoRoot, platform);
274
332
  if (npmGlobal && candidate === npmGlobal) {
275
333
  return 1;
276
334
  }
@@ -312,8 +370,12 @@ function findVscodeCodexExe(env: NodeJS.ProcessEnv): string {
312
370
  return "";
313
371
  }
314
372
 
315
- function findWindowsNpmGlobalCodexBin(env: NodeJS.ProcessEnv, repoRoot: string): string {
316
- if (process.platform !== "win32") {
373
+ function findWindowsNpmGlobalCodexBin(
374
+ env: NodeJS.ProcessEnv,
375
+ repoRoot: string,
376
+ platform: NodeJS.Platform,
377
+ ): string {
378
+ if (platform !== "win32") {
317
379
  return "";
318
380
  }
319
381
 
@@ -351,6 +413,14 @@ function findWindowsNpmGlobalCodexBin(env: NodeJS.ProcessEnv, repoRoot: string):
351
413
  return "";
352
414
  }
353
415
 
416
+ function selectPathModule(filePath: string): typeof path.posix | typeof path.win32 {
417
+ const normalized = String(filePath ?? "").trim();
418
+ if (/^[A-Za-z]:[\\/]/.test(normalized) || normalized.includes("\\")) {
419
+ return path.win32;
420
+ }
421
+ return path.posix;
422
+ }
423
+
354
424
  function readNpmPrefix(repoRoot: string): string {
355
425
  const result = spawnNpm(["config", "get", "prefix"], repoRoot);
356
426
  if (result.error || result.status !== 0) {
@@ -18,7 +18,8 @@ const engineExamplePath = path.join(repoRoot, "apps", "agent-engine", ".env.exam
18
18
  const controlPlaneEnvPath = layout.controlPlaneEnvPath;
19
19
  const controlPlaneExamplePath = path.join(repoRoot, "apps", "control-plane", ".env.example");
20
20
  const TELEGRAM_TOKEN_PATTERN = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
21
- const DEFAULT_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN";
21
+ const DEFAULT_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN_FILE";
22
+ const LEGACY_CONTROL_PLANE_TOKEN_ENV = "HUB_TELEGRAM_TOKEN";
22
23
 
23
24
  const args = new Set(process.argv.slice(2));
24
25
  const requiredOnly = args.has("--required-only");
@@ -53,13 +54,7 @@ async function main() {
53
54
  }
54
55
 
55
56
  async function configureRequiredTokens({ rl, controlPlaneLines }) {
56
- const controlPlaneMap = parseEnvMap(controlPlaneLines);
57
-
58
- const controlPlaneTokenEnvName = nonEmpty(
59
- controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV,
60
- DEFAULT_CONTROL_PLANE_TOKEN_ENV,
61
- );
62
- setEnvValue(controlPlaneLines, "HUB_TELEGRAM_TOKEN_ENV", controlPlaneTokenEnvName);
57
+ const controlPlaneTokenEnvName = migrateControlPlaneTokenEnv(controlPlaneLines);
63
58
 
64
59
  const postControlPlaneMap = parseEnvMap(controlPlaneLines);
65
60
  const currentToken = String(postControlPlaneMap[controlPlaneTokenEnvName] ?? "").trim();
@@ -82,15 +77,9 @@ async function configureRequiredTokens({ rl, controlPlaneLines }) {
82
77
  }
83
78
 
84
79
  async function configureAll({ rl, controlPlaneLines }) {
85
- const controlPlaneMap = parseEnvMap(controlPlaneLines);
86
-
87
80
  console.log("\nCopilot Hub control-plane configuration\n");
88
81
 
89
- const controlPlaneTokenEnvDefault = nonEmpty(
90
- controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV,
91
- DEFAULT_CONTROL_PLANE_TOKEN_ENV,
92
- );
93
- setEnvValue(controlPlaneLines, "HUB_TELEGRAM_TOKEN_ENV", controlPlaneTokenEnvDefault);
82
+ const controlPlaneTokenEnvDefault = migrateControlPlaneTokenEnv(controlPlaneLines);
94
83
  const currentControlPlaneToken = String(
95
84
  parseEnvMap(controlPlaneLines)[controlPlaneTokenEnvDefault] ?? "",
96
85
  ).trim();
@@ -106,6 +95,29 @@ async function configureAll({ rl, controlPlaneLines }) {
106
95
  }
107
96
  }
108
97
 
98
+ function migrateControlPlaneTokenEnv(lines) {
99
+ const controlPlaneMap = parseEnvMap(lines);
100
+ const configuredTokenEnvName = nonEmpty(
101
+ controlPlaneMap.HUB_TELEGRAM_TOKEN_ENV,
102
+ DEFAULT_CONTROL_PLANE_TOKEN_ENV,
103
+ );
104
+ const shouldMigrateLegacyName = configuredTokenEnvName === LEGACY_CONTROL_PLANE_TOKEN_ENV;
105
+ const nextTokenEnvName = shouldMigrateLegacyName
106
+ ? DEFAULT_CONTROL_PLANE_TOKEN_ENV
107
+ : configuredTokenEnvName;
108
+ setEnvValue(lines, "HUB_TELEGRAM_TOKEN_ENV", nextTokenEnvName);
109
+
110
+ if (shouldMigrateLegacyName) {
111
+ const legacyToken = String(controlPlaneMap[LEGACY_CONTROL_PLANE_TOKEN_ENV] ?? "").trim();
112
+ const dedicatedToken = String(controlPlaneMap[DEFAULT_CONTROL_PLANE_TOKEN_ENV] ?? "").trim();
113
+ if (legacyToken && !dedicatedToken) {
114
+ setEnvValue(lines, DEFAULT_CONTROL_PLANE_TOKEN_ENV, legacyToken);
115
+ }
116
+ }
117
+
118
+ return nextTokenEnvName;
119
+ }
120
+
109
121
  function ensureEnvFile(envPath, examplePath) {
110
122
  fs.mkdirSync(path.dirname(envPath), { recursive: true });
111
123
  if (fs.existsSync(envPath)) {
@@ -600,6 +600,34 @@ function detectFatalStartupError(ensureResult) {
600
600
  };
601
601
  }
602
602
 
603
+ const invalidHubTokenLine = findLineContaining(
604
+ evidenceChunks,
605
+ (line) => line.includes("hub telegram token in") && line.includes("is invalid"),
606
+ );
607
+ if (invalidHubTokenLine) {
608
+ return {
609
+ reason: invalidHubTokenLine,
610
+ action:
611
+ "Run 'copilot-hub configure' to save a valid hub token in the control-plane config, then retry service.",
612
+ detectedAt: new Date().toISOString(),
613
+ };
614
+ }
615
+
616
+ const workspaceRootLine = findLineContaining(
617
+ evidenceChunks,
618
+ (line) =>
619
+ line.includes("default_workspace_root must be outside kernel directory") ||
620
+ line.includes("hub_workspace_root must be outside kernel directory"),
621
+ );
622
+ if (workspaceRootLine) {
623
+ return {
624
+ reason: workspaceRootLine,
625
+ action:
626
+ "Set DEFAULT_WORKSPACE_ROOT to a folder outside the copilot-hub installation, then retry service.",
627
+ detectedAt: new Date().toISOString(),
628
+ };
629
+ }
630
+
603
631
  return null;
604
632
  }
605
633
 
@@ -0,0 +1,59 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { normalizeConfiguredCodexBin } from "../dist/codex-runtime.mjs";
7
+
8
+ test("normalizeConfiguredCodexBin remaps absolute Windows npm wrappers to codex.js", () => {
9
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "copilot-hub-codex-bin-"));
10
+ const wrapperDir = path.join(tempDir, "npm");
11
+ const packageDir = path.join(wrapperDir, "node_modules", "@openai", "codex", "bin");
12
+ fs.mkdirSync(packageDir, { recursive: true });
13
+
14
+ const wrapperPath = path.join(wrapperDir, "codex.cmd");
15
+ const entrypointPath = path.join(packageDir, "codex.js");
16
+ fs.writeFileSync(wrapperPath, "@echo off\r\n", "utf8");
17
+ fs.writeFileSync(entrypointPath, "console.log('ok');\n", "utf8");
18
+
19
+ const resolved = normalizeConfiguredCodexBin({
20
+ value: wrapperPath,
21
+ env: {},
22
+ repoRoot: tempDir,
23
+ platform: "win32",
24
+ });
25
+
26
+ assert.equal(resolved, entrypointPath);
27
+ });
28
+
29
+ test("normalizeConfiguredCodexBin resolves bare codex.cmd through detected npm install", () => {
30
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "copilot-hub-codex-bin-"));
31
+ const appDataDir = path.join(tempDir, "AppData", "Roaming");
32
+ const packageDir = path.join(appDataDir, "npm", "node_modules", "@openai", "codex", "bin");
33
+ fs.mkdirSync(packageDir, { recursive: true });
34
+
35
+ const entrypointPath = path.join(packageDir, "codex.js");
36
+ fs.writeFileSync(entrypointPath, "console.log('ok');\n", "utf8");
37
+
38
+ const resolved = normalizeConfiguredCodexBin({
39
+ value: "codex.cmd",
40
+ env: {
41
+ APPDATA: appDataDir,
42
+ },
43
+ repoRoot: tempDir,
44
+ platform: "win32",
45
+ });
46
+
47
+ assert.equal(resolved, entrypointPath);
48
+ });
49
+
50
+ test("normalizeConfiguredCodexBin preserves non-wrapper commands", () => {
51
+ const resolved = normalizeConfiguredCodexBin({
52
+ value: "C:/tools/codex.exe",
53
+ env: {},
54
+ repoRoot: process.cwd(),
55
+ platform: "win32",
56
+ });
57
+
58
+ assert.equal(resolved, "C:/tools/codex.exe");
59
+ });