copilot-hub 0.1.29 → 0.1.30
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.
- package/apps/agent-engine/.env.example +6 -8
- package/apps/agent-engine/dist/config.js +7 -0
- package/apps/control-plane/.env.example +2 -1
- package/apps/control-plane/dist/config.js +8 -0
- package/package.json +2 -1
- package/packages/core/dist/env-config.d.ts +2 -1
- package/packages/core/dist/env-config.js +7 -3
- package/packages/core/dist/env-config.js.map +1 -1
- package/scripts/dist/cli.mjs +52 -1
- package/scripts/dist/configure.mjs +5 -61
- package/scripts/dist/env-file-utils.mjs +83 -0
- package/scripts/dist/install-layout.mjs +130 -1
- package/scripts/dist/supervisor.mjs +5 -0
- package/scripts/src/cli.mts +65 -1
- package/scripts/src/configure.mts +5 -73
- package/scripts/src/env-file-utils.mts +98 -0
- package/scripts/src/install-layout.mts +172 -2
- package/scripts/src/supervisor.mts +5 -0
- package/scripts/test/install-layout.test.mjs +54 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# Runtime state files are managed automatically by copilot-hub.
|
|
2
|
+
# Override only if you intentionally want a custom location.
|
|
3
|
+
# BOT_DATA_DIR=
|
|
4
|
+
# BOT_REGISTRY_FILE=
|
|
5
|
+
# SECRET_STORE_FILE=
|
|
6
6
|
|
|
7
7
|
# Worker defaults
|
|
8
8
|
# If empty, runtime uses Desktop/copilot_workspaces by default
|
|
@@ -27,7 +27,7 @@ SHARED_THREAD_ID=shared-main
|
|
|
27
27
|
TELEGRAM_ALLOWED_CHAT_IDS=
|
|
28
28
|
|
|
29
29
|
INSTANCE_LOCK_ENABLED=true
|
|
30
|
-
INSTANCE_LOCK_FILE
|
|
30
|
+
# INSTANCE_LOCK_FILE=
|
|
31
31
|
|
|
32
32
|
WEB_HOST=127.0.0.1
|
|
33
33
|
WEB_PORT=8787
|
|
@@ -37,5 +37,3 @@ WEB_PORT_SEARCH_MAX=30
|
|
|
37
37
|
|
|
38
38
|
# Optional worker token environment variables used in bot-registry.json
|
|
39
39
|
TELEGRAM_TOKEN_AGENT_1=
|
|
40
|
-
|
|
41
|
-
|
|
@@ -134,6 +134,13 @@ export const config = {
|
|
|
134
134
|
function loadEnvironment() {
|
|
135
135
|
return loadCopilotHubEnvironment({
|
|
136
136
|
cwd: process.cwd(),
|
|
137
|
+
preserveExistingKeys: [
|
|
138
|
+
"BOT_DATA_DIR",
|
|
139
|
+
"BOT_REGISTRY_FILE",
|
|
140
|
+
"SECRET_STORE_FILE",
|
|
141
|
+
"INSTANCE_LOCK_FILE",
|
|
142
|
+
"COPILOT_HUB_HOME_DIR",
|
|
143
|
+
],
|
|
137
144
|
});
|
|
138
145
|
}
|
|
139
146
|
function resolveCodexBin(rawValue) {
|
|
@@ -4,7 +4,8 @@ HUB_ID=copilot_hub
|
|
|
4
4
|
HUB_NAME=Copilot Hub
|
|
5
5
|
# Optional. If empty, defaults to Desktop/copilot_workspaces (Windows/macOS/Linux)
|
|
6
6
|
HUB_WORKSPACE_ROOT=
|
|
7
|
-
|
|
7
|
+
# Runtime state paths are managed automatically by copilot-hub.
|
|
8
|
+
# HUB_DATA_DIR=
|
|
8
9
|
HUB_THREAD_MODE=per_chat
|
|
9
10
|
HUB_SHARED_THREAD_ID=shared-copilot-hub
|
|
10
11
|
HUB_ALLOWED_CHAT_IDS=
|
|
@@ -141,6 +141,14 @@ export const config = {
|
|
|
141
141
|
function loadEnvironment() {
|
|
142
142
|
return loadCopilotHubEnvironment({
|
|
143
143
|
cwd: process.cwd(),
|
|
144
|
+
preserveExistingKeys: [
|
|
145
|
+
"BOT_DATA_DIR",
|
|
146
|
+
"BOT_REGISTRY_FILE",
|
|
147
|
+
"SECRET_STORE_FILE",
|
|
148
|
+
"INSTANCE_LOCK_FILE",
|
|
149
|
+
"HUB_DATA_DIR",
|
|
150
|
+
"COPILOT_HUB_HOME_DIR",
|
|
151
|
+
],
|
|
144
152
|
});
|
|
145
153
|
}
|
|
146
154
|
function resolveCodexBin(rawValue) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copilot-hub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "Copilot Hub CLI and runtime bundle",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"status": "npm run build:scripts --silent && node scripts/dist/cli.mjs status",
|
|
59
59
|
"logs": "npm run build:scripts --silent && node scripts/dist/cli.mjs logs",
|
|
60
60
|
"configure": "npm run build:scripts --silent && node scripts/dist/cli.mjs configure",
|
|
61
|
+
"reset-config": "npm run build:scripts --silent && node scripts/dist/cli.mjs reset-config",
|
|
61
62
|
"test:scripts": "npm run build:scripts --silent && node --test scripts/test/*.test.mjs",
|
|
62
63
|
"test": "npm run test:scripts && npm run test --workspaces --if-present",
|
|
63
64
|
"lint": "eslint .",
|
|
@@ -4,7 +4,8 @@ export interface LoadedCopilotHubEnvironment {
|
|
|
4
4
|
fileValues: Record<string, string>;
|
|
5
5
|
overriddenKeys: string[];
|
|
6
6
|
}
|
|
7
|
-
export declare function loadCopilotHubEnvironment({ env, cwd, }?: {
|
|
7
|
+
export declare function loadCopilotHubEnvironment({ env, cwd, preserveExistingKeys, }?: {
|
|
8
8
|
env?: NodeJS.ProcessEnv;
|
|
9
9
|
cwd?: string;
|
|
10
|
+
preserveExistingKeys?: Iterable<string>;
|
|
10
11
|
}): LoadedCopilotHubEnvironment;
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
5
|
import { resolveConfigBaseDir } from "./config-paths.js";
|
|
6
|
-
export function loadCopilotHubEnvironment({ env = process.env, cwd = process.cwd(), } = {}) {
|
|
6
|
+
export function loadCopilotHubEnvironment({ env = process.env, cwd = process.cwd(), preserveExistingKeys = [], } = {}) {
|
|
7
7
|
const configuredEnvPath = String(env.COPILOT_HUB_ENV_PATH ?? "").trim();
|
|
8
8
|
const resolvedEnvPath = configuredEnvPath ? path.resolve(configuredEnvPath) : "";
|
|
9
9
|
const discoveredEnvPath = resolvedEnvPath || resolveDefaultEnvPath(cwd);
|
|
@@ -13,7 +13,7 @@ export function loadCopilotHubEnvironment({ env = process.env, cwd = process.cwd
|
|
|
13
13
|
cwd,
|
|
14
14
|
});
|
|
15
15
|
const fileValues = loadEnvFileValues(discoveredEnvPath);
|
|
16
|
-
const overriddenKeys = applyEnvFileValues(env, fileValues);
|
|
16
|
+
const overriddenKeys = applyEnvFileValues(env, fileValues, preserveExistingKeys);
|
|
17
17
|
if (resolvedEnvPath) {
|
|
18
18
|
env.COPILOT_HUB_ENV_PATH = resolvedEnvPath;
|
|
19
19
|
}
|
|
@@ -41,10 +41,14 @@ function loadEnvFileValues(filePath) {
|
|
|
41
41
|
return {};
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
function applyEnvFileValues(env, fileValues) {
|
|
44
|
+
function applyEnvFileValues(env, fileValues, preserveExistingKeys) {
|
|
45
45
|
const overriddenKeys = [];
|
|
46
|
+
const preservedKeys = new Set([...preserveExistingKeys].map((key) => String(key ?? "").trim()).filter(Boolean));
|
|
46
47
|
for (const [key, value] of Object.entries(fileValues)) {
|
|
47
48
|
const previousValue = String(env[key] ?? "");
|
|
49
|
+
if (previousValue && preservedKeys.has(key)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
48
52
|
if (previousValue && previousValue !== value) {
|
|
49
53
|
overriddenKeys.push(key);
|
|
50
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-config.js","sourceRoot":"","sources":["../src/env-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AASzD,MAAM,UAAU,yBAAyB,CAAC,EACxC,GAAG,GAAG,OAAO,CAAC,GAAG,EACjB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"env-config.js","sourceRoot":"","sources":["../src/env-config.ts"],"names":[],"mappings":"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"}
|
package/scripts/dist/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawnCodexSync } from "./codex-spawn.mjs";
|
|
9
9
|
import { codexInstallPackageSpec } from "./codex-version.mjs";
|
|
10
|
-
import { initializeCopilotHubLayout, resolveCopilotHubLayout } from "./install-layout.mjs";
|
|
10
|
+
import { initializeCopilotHubLayout, resetCopilotHubConfig, resolveCopilotHubLayout, } from "./install-layout.mjs";
|
|
11
11
|
import { buildCodexCompatibilityError, buildCodexCompatibilityNotice, probeCodexVersion, resolveCodexBinForStart, resolveCompatibleInstalledCodexBin, } from "./codex-runtime.mjs";
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = path.dirname(__filename);
|
|
@@ -27,6 +27,7 @@ const rawArgs = process.argv
|
|
|
27
27
|
.filter(Boolean);
|
|
28
28
|
const wantsVersion = rawArgs.includes("--version") || rawArgs.includes("-v");
|
|
29
29
|
const wantsHelp = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
30
|
+
const wantsYes = rawArgs.includes("--yes") || rawArgs.includes("-y");
|
|
30
31
|
const action = String(rawArgs[0] ?? "start")
|
|
31
32
|
.trim()
|
|
32
33
|
.toLowerCase();
|
|
@@ -92,6 +93,13 @@ async function main() {
|
|
|
92
93
|
runNode(["scripts/dist/configure.mjs"]);
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
96
|
+
case "reset-config":
|
|
97
|
+
case "reset_config": {
|
|
98
|
+
await resetConfig({
|
|
99
|
+
force: wantsYes,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
95
103
|
case "service": {
|
|
96
104
|
const serviceAction = String(rawArgs[1] ?? "")
|
|
97
105
|
.trim()
|
|
@@ -111,6 +119,47 @@ async function main() {
|
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
}
|
|
122
|
+
async function resetConfig({ force }) {
|
|
123
|
+
if (!force) {
|
|
124
|
+
if (!process.stdin.isTTY) {
|
|
125
|
+
throw new Error("reset-config requires confirmation. Re-run with '--yes' in non-interactive mode.");
|
|
126
|
+
}
|
|
127
|
+
const rl = createInterface({ input, output });
|
|
128
|
+
try {
|
|
129
|
+
const confirmed = await askYesNo(rl, [
|
|
130
|
+
"Reset Copilot Hub config and runtime state?",
|
|
131
|
+
"This removes persisted config, bot registry, secrets, logs, and runtime state.",
|
|
132
|
+
"Agent workspaces are kept.",
|
|
133
|
+
].join(" "), false);
|
|
134
|
+
if (!confirmed) {
|
|
135
|
+
console.log("Reset canceled.");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
rl.close();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (isServiceAlreadyInstalled()) {
|
|
144
|
+
runNodeCapture(["scripts/dist/service.mjs", "stop"], "inherit");
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
runNodeCapture(["scripts/dist/supervisor.mjs", "down"], "inherit");
|
|
148
|
+
}
|
|
149
|
+
const reset = resetCopilotHubConfig({ layout });
|
|
150
|
+
initializeCopilotHubLayout({ repoRoot, layout });
|
|
151
|
+
console.log("Copilot Hub config reset completed.");
|
|
152
|
+
if (reset.removedPaths.length > 0) {
|
|
153
|
+
console.log("Removed:");
|
|
154
|
+
for (const removedPath of reset.removedPaths) {
|
|
155
|
+
console.log(`- ${removedPath}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log("Kept:");
|
|
159
|
+
console.log("- package installation");
|
|
160
|
+
console.log("- external workspaces (for example Desktop/copilot_workspaces)");
|
|
161
|
+
console.log("Next step: run 'copilot-hub configure' then 'copilot-hub start'.");
|
|
162
|
+
}
|
|
114
163
|
function runNode(scriptArgs) {
|
|
115
164
|
const result = runNodeCapture(scriptArgs, "inherit");
|
|
116
165
|
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
@@ -485,6 +534,8 @@ function spawnNpm(args, options) {
|
|
|
485
534
|
function printUsage() {
|
|
486
535
|
console.log([
|
|
487
536
|
"Usage: node scripts/dist/cli.mjs <start|stop|restart|status|logs|configure|service|version|help>",
|
|
537
|
+
"Reset persistent state:",
|
|
538
|
+
" node scripts/dist/cli.mjs reset-config [--yes]",
|
|
488
539
|
"Service management:",
|
|
489
540
|
" node scripts/dist/cli.mjs service <install|uninstall|status|start|stop|help>",
|
|
490
541
|
].join("\n"));
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import process, { stdin as input, stdout as output } from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { parseEnvMap, readEnvLines, setEnvValue, writeEnvLines } from "./env-file-utils.mjs";
|
|
7
8
|
import { initializeCopilotHubLayout, resolveCopilotHubLayout } from "./install-layout.mjs";
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = path.dirname(__filename);
|
|
@@ -23,8 +24,8 @@ await main();
|
|
|
23
24
|
async function main() {
|
|
24
25
|
ensureEnvFile(engineEnvPath, engineExamplePath);
|
|
25
26
|
ensureEnvFile(controlPlaneEnvPath, controlPlaneExamplePath);
|
|
26
|
-
const engineLines =
|
|
27
|
-
const controlPlaneLines =
|
|
27
|
+
const engineLines = readEnvLines(engineEnvPath);
|
|
28
|
+
const controlPlaneLines = readEnvLines(controlPlaneEnvPath);
|
|
28
29
|
const rl = createInterface({ input, output });
|
|
29
30
|
try {
|
|
30
31
|
if (requiredOnly) {
|
|
@@ -41,8 +42,8 @@ async function main() {
|
|
|
41
42
|
finally {
|
|
42
43
|
rl.close();
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
writeEnvLines(engineEnvPath, engineLines);
|
|
46
|
+
writeEnvLines(controlPlaneEnvPath, controlPlaneLines);
|
|
46
47
|
}
|
|
47
48
|
async function configureRequiredTokens({ rl, controlPlaneLines }) {
|
|
48
49
|
const controlPlaneTokenEnvName = migrateControlPlaneTokenEnv(controlPlaneLines);
|
|
@@ -102,67 +103,10 @@ function ensureEnvFile(envPath, examplePath) {
|
|
|
102
103
|
}
|
|
103
104
|
fs.writeFileSync(envPath, "", "utf8");
|
|
104
105
|
}
|
|
105
|
-
function readLines(filePath) {
|
|
106
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
107
|
-
return content.split(/\r?\n/);
|
|
108
|
-
}
|
|
109
|
-
function writeLines(filePath, lines) {
|
|
110
|
-
const normalized = [...lines];
|
|
111
|
-
if (normalized.length === 0 || normalized[normalized.length - 1] !== "") {
|
|
112
|
-
normalized.push("");
|
|
113
|
-
}
|
|
114
|
-
fs.writeFileSync(filePath, normalized.join("\n"), "utf8");
|
|
115
|
-
}
|
|
116
|
-
function parseEnvMap(lines) {
|
|
117
|
-
const map = {};
|
|
118
|
-
for (const line of lines) {
|
|
119
|
-
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
120
|
-
if (!match) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
const key = match[1];
|
|
124
|
-
const value = unquote(match[2] ?? "");
|
|
125
|
-
map[key] = value;
|
|
126
|
-
}
|
|
127
|
-
return map;
|
|
128
|
-
}
|
|
129
|
-
function setEnvValue(lines, key, value) {
|
|
130
|
-
const safeValue = sanitizeValue(value);
|
|
131
|
-
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
132
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
133
|
-
if (!pattern.test(lines[index])) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
lines[index] = `${key}=${safeValue}`;
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (lines.length > 0 && lines[lines.length - 1] !== "") {
|
|
140
|
-
lines.push("");
|
|
141
|
-
}
|
|
142
|
-
lines.push(`${key}=${safeValue}`);
|
|
143
|
-
}
|
|
144
|
-
function sanitizeValue(value) {
|
|
145
|
-
return String(value ?? "")
|
|
146
|
-
.replace(/[\r\n]/g, "")
|
|
147
|
-
.trim();
|
|
148
|
-
}
|
|
149
|
-
function unquote(value) {
|
|
150
|
-
const raw = String(value ?? "").trim();
|
|
151
|
-
if (!raw) {
|
|
152
|
-
return "";
|
|
153
|
-
}
|
|
154
|
-
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
155
|
-
return raw.slice(1, -1);
|
|
156
|
-
}
|
|
157
|
-
return raw;
|
|
158
|
-
}
|
|
159
106
|
function nonEmpty(value, fallback) {
|
|
160
107
|
const normalized = String(value ?? "").trim();
|
|
161
108
|
return normalized || fallback;
|
|
162
109
|
}
|
|
163
|
-
function escapeRegex(value) {
|
|
164
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
165
|
-
}
|
|
166
110
|
async function askRequired(rl, label) {
|
|
167
111
|
while (true) {
|
|
168
112
|
const value = await rl.question(`${label}: `);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export function ensureEnvTextFile(filePath) {
|
|
3
|
+
if (fs.existsSync(filePath)) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
fs.mkdirSync(requireParentDir(filePath), { recursive: true });
|
|
7
|
+
fs.writeFileSync(filePath, "", "utf8");
|
|
8
|
+
}
|
|
9
|
+
export function readEnvLines(filePath) {
|
|
10
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
11
|
+
return content.split(/\r?\n/);
|
|
12
|
+
}
|
|
13
|
+
export function writeEnvLines(filePath, lines) {
|
|
14
|
+
const normalized = [...lines];
|
|
15
|
+
if (normalized.length === 0 || normalized[normalized.length - 1] !== "") {
|
|
16
|
+
normalized.push("");
|
|
17
|
+
}
|
|
18
|
+
fs.writeFileSync(filePath, normalized.join("\n"), "utf8");
|
|
19
|
+
}
|
|
20
|
+
export function parseEnvMap(lines) {
|
|
21
|
+
const map = {};
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
24
|
+
if (!match) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const key = match[1];
|
|
28
|
+
const value = unquote(match[2] ?? "");
|
|
29
|
+
map[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
33
|
+
export function setEnvValue(lines, key, value) {
|
|
34
|
+
const safeValue = sanitizeEnvValue(value);
|
|
35
|
+
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
36
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
37
|
+
if (!pattern.test(lines[index])) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
lines[index] = `${key}=${safeValue}`;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (lines.length > 0 && lines[lines.length - 1] !== "") {
|
|
44
|
+
lines.push("");
|
|
45
|
+
}
|
|
46
|
+
lines.push(`${key}=${safeValue}`);
|
|
47
|
+
}
|
|
48
|
+
export function removeEnvKeys(lines, keys) {
|
|
49
|
+
const patterns = keys.map((key) => new RegExp(`^\\s*${escapeRegex(key)}\\s*=`));
|
|
50
|
+
const originalLength = lines.length;
|
|
51
|
+
const kept = lines.filter((line) => !patterns.some((pattern) => pattern.test(line)));
|
|
52
|
+
if (kept.length === originalLength) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
lines.splice(0, lines.length, ...kept);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
export function sanitizeEnvValue(value) {
|
|
59
|
+
return String(value ?? "")
|
|
60
|
+
.replace(/[\r\n]/g, "")
|
|
61
|
+
.trim();
|
|
62
|
+
}
|
|
63
|
+
function unquote(value) {
|
|
64
|
+
const raw = String(value ?? "").trim();
|
|
65
|
+
if (!raw) {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
69
|
+
return raw.slice(1, -1);
|
|
70
|
+
}
|
|
71
|
+
return raw;
|
|
72
|
+
}
|
|
73
|
+
function escapeRegex(value) {
|
|
74
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
75
|
+
}
|
|
76
|
+
function requireParentDir(filePath) {
|
|
77
|
+
const parts = String(filePath ?? "").split(/[\\/]/);
|
|
78
|
+
if (parts.length <= 1) {
|
|
79
|
+
return ".";
|
|
80
|
+
}
|
|
81
|
+
parts.pop();
|
|
82
|
+
return parts.join("/") || ".";
|
|
83
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { parseEnvMap, readEnvLines, removeEnvKeys, writeEnvLines } from "./env-file-utils.mjs";
|
|
5
7
|
export function resolveCopilotHubLayout({ repoRoot, env = process.env, platform = process.platform, homeDirectory = os.homedir(), }) {
|
|
6
8
|
const pathApi = getPathApi(platform);
|
|
7
9
|
const homeDir = resolveCopilotHubHomeDir({
|
|
@@ -30,7 +32,35 @@ export function resolveCopilotHubLayout({ repoRoot, env = process.env, platform
|
|
|
30
32
|
export function initializeCopilotHubLayout({ repoRoot, layout, }) {
|
|
31
33
|
ensureCopilotHubLayout(layout);
|
|
32
34
|
const migratedPaths = migrateLegacyLayout({ repoRoot, layout });
|
|
33
|
-
|
|
35
|
+
const normalizedEnvPaths = normalizePersistentEnvFiles(layout);
|
|
36
|
+
return { migratedPaths, normalizedEnvPaths };
|
|
37
|
+
}
|
|
38
|
+
export function resetCopilotHubConfig({ layout }) {
|
|
39
|
+
const removedPaths = [];
|
|
40
|
+
for (const target of [layout.configDir, layout.dataDir, layout.logsDir]) {
|
|
41
|
+
if (!fs.existsSync(target)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
45
|
+
removedPaths.push(target);
|
|
46
|
+
}
|
|
47
|
+
const runtimeTargets = [
|
|
48
|
+
path.join(layout.runtimeDir, "pids"),
|
|
49
|
+
path.join(layout.runtimeDir, "services"),
|
|
50
|
+
path.join(layout.runtimeDir, "last-startup-error.json"),
|
|
51
|
+
layout.servicePromptStatePath,
|
|
52
|
+
];
|
|
53
|
+
for (const target of runtimeTargets) {
|
|
54
|
+
if (!fs.existsSync(target)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
58
|
+
removedPaths.push(target);
|
|
59
|
+
}
|
|
60
|
+
ensureCopilotHubLayout(layout);
|
|
61
|
+
return {
|
|
62
|
+
removedPaths: removedPaths.sort(),
|
|
63
|
+
};
|
|
34
64
|
}
|
|
35
65
|
export function ensureCopilotHubLayout(layout) {
|
|
36
66
|
fs.mkdirSync(layout.homeDir, { recursive: true });
|
|
@@ -81,6 +111,98 @@ function migrateLegacyLayout({ repoRoot, layout, }) {
|
|
|
81
111
|
}
|
|
82
112
|
return migratedPaths;
|
|
83
113
|
}
|
|
114
|
+
function normalizePersistentEnvFiles(layout) {
|
|
115
|
+
const normalizedPaths = [];
|
|
116
|
+
if (normalizePersistentEnvFile(layout.agentEngineEnvPath, [
|
|
117
|
+
{
|
|
118
|
+
key: "BOT_DATA_DIR",
|
|
119
|
+
legacyValues: ["./data"],
|
|
120
|
+
wrongResolvedPath: path.join(layout.configDir, "data"),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
key: "BOT_REGISTRY_FILE",
|
|
124
|
+
legacyValues: ["./data/bot-registry.json"],
|
|
125
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "bot-registry.json"),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: "SECRET_STORE_FILE",
|
|
129
|
+
legacyValues: ["./data/secrets.json"],
|
|
130
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "secrets.json"),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "INSTANCE_LOCK_FILE",
|
|
134
|
+
legacyValues: ["./data/runtime.lock"],
|
|
135
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "runtime.lock"),
|
|
136
|
+
},
|
|
137
|
+
])) {
|
|
138
|
+
normalizedPaths.push(layout.agentEngineEnvPath);
|
|
139
|
+
}
|
|
140
|
+
if (normalizePersistentEnvFile(layout.controlPlaneEnvPath, [
|
|
141
|
+
{
|
|
142
|
+
key: "BOT_DATA_DIR",
|
|
143
|
+
legacyValues: ["./data"],
|
|
144
|
+
wrongResolvedPath: path.join(layout.configDir, "data"),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: "BOT_REGISTRY_FILE",
|
|
148
|
+
legacyValues: ["./data/bot-registry.json"],
|
|
149
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "bot-registry.json"),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
key: "SECRET_STORE_FILE",
|
|
153
|
+
legacyValues: ["./data/secrets.json"],
|
|
154
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "secrets.json"),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
key: "INSTANCE_LOCK_FILE",
|
|
158
|
+
legacyValues: ["./data/runtime.lock"],
|
|
159
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "runtime.lock"),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "HUB_DATA_DIR",
|
|
163
|
+
legacyValues: ["./data/copilot_hub"],
|
|
164
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "copilot_hub"),
|
|
165
|
+
},
|
|
166
|
+
])) {
|
|
167
|
+
normalizedPaths.push(layout.controlPlaneEnvPath);
|
|
168
|
+
}
|
|
169
|
+
return normalizedPaths.sort();
|
|
170
|
+
}
|
|
171
|
+
function normalizePersistentEnvFile(filePath, rules) {
|
|
172
|
+
if (!fs.existsSync(filePath)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const lines = readEnvLines(filePath);
|
|
176
|
+
const envMap = parseEnvMap(lines);
|
|
177
|
+
const keysToRemove = rules
|
|
178
|
+
.filter((rule) => shouldRemoveLegacyManagedPath(envMap[rule.key], {
|
|
179
|
+
legacyValues: rule.legacyValues,
|
|
180
|
+
wrongResolvedPath: rule.wrongResolvedPath,
|
|
181
|
+
configBaseDir: path.dirname(filePath),
|
|
182
|
+
}))
|
|
183
|
+
.map((rule) => rule.key);
|
|
184
|
+
if (keysToRemove.length === 0) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
removeEnvKeys(lines, keysToRemove);
|
|
188
|
+
writeEnvLines(filePath, lines);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
function shouldRemoveLegacyManagedPath(rawValue, { legacyValues, wrongResolvedPath, configBaseDir, }) {
|
|
192
|
+
const value = String(rawValue ?? "").trim();
|
|
193
|
+
if (!value) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const normalizedValue = normalizeForCompare(value);
|
|
197
|
+
if (legacyValues.some((entry) => normalizeForCompare(entry) === normalizedValue)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (path.isAbsolute(value)) {
|
|
201
|
+
return normalizeForCompare(value) === normalizeForCompare(wrongResolvedPath);
|
|
202
|
+
}
|
|
203
|
+
return (normalizeForCompare(path.resolve(configBaseDir, value)) ===
|
|
204
|
+
normalizeForCompare(wrongResolvedPath));
|
|
205
|
+
}
|
|
84
206
|
function resolveLegacyPaths(repoRoot) {
|
|
85
207
|
return {
|
|
86
208
|
agentEngineEnvPath: path.join(repoRoot, "apps", "agent-engine", ".env"),
|
|
@@ -129,6 +251,13 @@ function normalizePath(value, pathApi) {
|
|
|
129
251
|
const normalized = String(value ?? "").trim();
|
|
130
252
|
return normalized ? pathApi.resolve(normalized) : "";
|
|
131
253
|
}
|
|
254
|
+
function normalizeForCompare(value) {
|
|
255
|
+
const normalized = String(value ?? "").trim();
|
|
256
|
+
if (!normalized) {
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
260
|
+
}
|
|
132
261
|
function getPathApi(platform) {
|
|
133
262
|
return platform === "win32" ? path.win32 : path.posix;
|
|
134
263
|
}
|
|
@@ -307,6 +307,11 @@ function buildServiceEnvironment(service) {
|
|
|
307
307
|
BOT_REGISTRY_FILE: service.botRegistryFilePath,
|
|
308
308
|
SECRET_STORE_FILE: service.secretStoreFilePath,
|
|
309
309
|
INSTANCE_LOCK_FILE: service.instanceLockFilePath,
|
|
310
|
+
...(service.id === "control-plane"
|
|
311
|
+
? {
|
|
312
|
+
HUB_DATA_DIR: path.join(service.dataDir, "copilot_hub"),
|
|
313
|
+
}
|
|
314
|
+
: {}),
|
|
310
315
|
};
|
|
311
316
|
}
|
|
312
317
|
function printTail(filePath, lines) {
|
package/scripts/src/cli.mts
CHANGED
|
@@ -7,7 +7,11 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawnCodexSync } from "./codex-spawn.mjs";
|
|
9
9
|
import { codexInstallPackageSpec } from "./codex-version.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
initializeCopilotHubLayout,
|
|
12
|
+
resetCopilotHubConfig,
|
|
13
|
+
resolveCopilotHubLayout,
|
|
14
|
+
} from "./install-layout.mjs";
|
|
11
15
|
import {
|
|
12
16
|
buildCodexCompatibilityError,
|
|
13
17
|
buildCodexCompatibilityNotice,
|
|
@@ -35,6 +39,7 @@ const rawArgs = process.argv
|
|
|
35
39
|
.filter(Boolean);
|
|
36
40
|
const wantsVersion = rawArgs.includes("--version") || rawArgs.includes("-v");
|
|
37
41
|
const wantsHelp = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
42
|
+
const wantsYes = rawArgs.includes("--yes") || rawArgs.includes("-y");
|
|
38
43
|
|
|
39
44
|
const action = String(rawArgs[0] ?? "start")
|
|
40
45
|
.trim()
|
|
@@ -106,6 +111,13 @@ async function main() {
|
|
|
106
111
|
runNode(["scripts/dist/configure.mjs"]);
|
|
107
112
|
return;
|
|
108
113
|
}
|
|
114
|
+
case "reset-config":
|
|
115
|
+
case "reset_config": {
|
|
116
|
+
await resetConfig({
|
|
117
|
+
force: wantsYes,
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
109
121
|
case "service": {
|
|
110
122
|
const serviceAction = String(rawArgs[1] ?? "")
|
|
111
123
|
.trim()
|
|
@@ -126,6 +138,56 @@ async function main() {
|
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
140
|
|
|
141
|
+
async function resetConfig({ force }: { force: boolean }): Promise<void> {
|
|
142
|
+
if (!force) {
|
|
143
|
+
if (!process.stdin.isTTY) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"reset-config requires confirmation. Re-run with '--yes' in non-interactive mode.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const rl = createInterface({ input, output });
|
|
150
|
+
try {
|
|
151
|
+
const confirmed = await askYesNo(
|
|
152
|
+
rl,
|
|
153
|
+
[
|
|
154
|
+
"Reset Copilot Hub config and runtime state?",
|
|
155
|
+
"This removes persisted config, bot registry, secrets, logs, and runtime state.",
|
|
156
|
+
"Agent workspaces are kept.",
|
|
157
|
+
].join(" "),
|
|
158
|
+
false,
|
|
159
|
+
);
|
|
160
|
+
if (!confirmed) {
|
|
161
|
+
console.log("Reset canceled.");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
} finally {
|
|
165
|
+
rl.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (isServiceAlreadyInstalled()) {
|
|
170
|
+
runNodeCapture(["scripts/dist/service.mjs", "stop"], "inherit");
|
|
171
|
+
} else {
|
|
172
|
+
runNodeCapture(["scripts/dist/supervisor.mjs", "down"], "inherit");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const reset = resetCopilotHubConfig({ layout });
|
|
176
|
+
initializeCopilotHubLayout({ repoRoot, layout });
|
|
177
|
+
|
|
178
|
+
console.log("Copilot Hub config reset completed.");
|
|
179
|
+
if (reset.removedPaths.length > 0) {
|
|
180
|
+
console.log("Removed:");
|
|
181
|
+
for (const removedPath of reset.removedPaths) {
|
|
182
|
+
console.log(`- ${removedPath}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
console.log("Kept:");
|
|
186
|
+
console.log("- package installation");
|
|
187
|
+
console.log("- external workspaces (for example Desktop/copilot_workspaces)");
|
|
188
|
+
console.log("Next step: run 'copilot-hub configure' then 'copilot-hub start'.");
|
|
189
|
+
}
|
|
190
|
+
|
|
129
191
|
function runNode(scriptArgs) {
|
|
130
192
|
const result = runNodeCapture(scriptArgs, "inherit");
|
|
131
193
|
const code = Number.isInteger(result.status) ? result.status : 1;
|
|
@@ -577,6 +639,8 @@ function printUsage() {
|
|
|
577
639
|
console.log(
|
|
578
640
|
[
|
|
579
641
|
"Usage: node scripts/dist/cli.mjs <start|stop|restart|status|logs|configure|service|version|help>",
|
|
642
|
+
"Reset persistent state:",
|
|
643
|
+
" node scripts/dist/cli.mjs reset-config [--yes]",
|
|
580
644
|
"Service management:",
|
|
581
645
|
" node scripts/dist/cli.mjs service <install|uninstall|status|start|stop|help>",
|
|
582
646
|
].join("\n"),
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import process, { stdin as input, stdout as output } from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { parseEnvMap, readEnvLines, setEnvValue, writeEnvLines } from "./env-file-utils.mjs";
|
|
7
8
|
import { initializeCopilotHubLayout, resolveCopilotHubLayout } from "./install-layout.mjs";
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -30,8 +31,8 @@ async function main() {
|
|
|
30
31
|
ensureEnvFile(engineEnvPath, engineExamplePath);
|
|
31
32
|
ensureEnvFile(controlPlaneEnvPath, controlPlaneExamplePath);
|
|
32
33
|
|
|
33
|
-
const engineLines =
|
|
34
|
-
const controlPlaneLines =
|
|
34
|
+
const engineLines = readEnvLines(engineEnvPath);
|
|
35
|
+
const controlPlaneLines = readEnvLines(controlPlaneEnvPath);
|
|
35
36
|
|
|
36
37
|
const rl = createInterface({ input, output });
|
|
37
38
|
|
|
@@ -49,8 +50,8 @@ async function main() {
|
|
|
49
50
|
rl.close();
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
writeEnvLines(engineEnvPath, engineLines);
|
|
54
|
+
writeEnvLines(controlPlaneEnvPath, controlPlaneLines);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
async function configureRequiredTokens({ rl, controlPlaneLines }) {
|
|
@@ -132,80 +133,11 @@ function ensureEnvFile(envPath, examplePath) {
|
|
|
132
133
|
fs.writeFileSync(envPath, "", "utf8");
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
function readLines(filePath) {
|
|
136
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
137
|
-
return content.split(/\r?\n/);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function writeLines(filePath, lines) {
|
|
141
|
-
const normalized = [...lines];
|
|
142
|
-
if (normalized.length === 0 || normalized[normalized.length - 1] !== "") {
|
|
143
|
-
normalized.push("");
|
|
144
|
-
}
|
|
145
|
-
fs.writeFileSync(filePath, normalized.join("\n"), "utf8");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function parseEnvMap(lines) {
|
|
149
|
-
const map: Record<string, string> = {};
|
|
150
|
-
for (const line of lines) {
|
|
151
|
-
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
152
|
-
if (!match) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const key = match[1];
|
|
157
|
-
const value = unquote(match[2] ?? "");
|
|
158
|
-
map[key] = value;
|
|
159
|
-
}
|
|
160
|
-
return map;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function setEnvValue(lines, key, value) {
|
|
164
|
-
const safeValue = sanitizeValue(value);
|
|
165
|
-
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
166
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
167
|
-
if (!pattern.test(lines[index])) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
lines[index] = `${key}=${safeValue}`;
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (lines.length > 0 && lines[lines.length - 1] !== "") {
|
|
176
|
-
lines.push("");
|
|
177
|
-
}
|
|
178
|
-
lines.push(`${key}=${safeValue}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function sanitizeValue(value) {
|
|
182
|
-
return String(value ?? "")
|
|
183
|
-
.replace(/[\r\n]/g, "")
|
|
184
|
-
.trim();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function unquote(value) {
|
|
188
|
-
const raw = String(value ?? "").trim();
|
|
189
|
-
if (!raw) {
|
|
190
|
-
return "";
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
194
|
-
return raw.slice(1, -1);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return raw;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
136
|
function nonEmpty(value, fallback) {
|
|
201
137
|
const normalized = String(value ?? "").trim();
|
|
202
138
|
return normalized || fallback;
|
|
203
139
|
}
|
|
204
140
|
|
|
205
|
-
function escapeRegex(value) {
|
|
206
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
141
|
async function askRequired(rl, label) {
|
|
210
142
|
while (true) {
|
|
211
143
|
const value = await rl.question(`${label}: `);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
export function ensureEnvTextFile(filePath: string): void {
|
|
4
|
+
if (fs.existsSync(filePath)) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
fs.mkdirSync(requireParentDir(filePath), { recursive: true });
|
|
8
|
+
fs.writeFileSync(filePath, "", "utf8");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readEnvLines(filePath: string): string[] {
|
|
12
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
13
|
+
return content.split(/\r?\n/);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function writeEnvLines(filePath: string, lines: string[]): void {
|
|
17
|
+
const normalized = [...lines];
|
|
18
|
+
if (normalized.length === 0 || normalized[normalized.length - 1] !== "") {
|
|
19
|
+
normalized.push("");
|
|
20
|
+
}
|
|
21
|
+
fs.writeFileSync(filePath, normalized.join("\n"), "utf8");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseEnvMap(lines: string[]): Record<string, string> {
|
|
25
|
+
const map: Record<string, string> = {};
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
28
|
+
if (!match) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = match[1];
|
|
33
|
+
const value = unquote(match[2] ?? "");
|
|
34
|
+
map[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function setEnvValue(lines: string[], key: string, value: unknown): void {
|
|
40
|
+
const safeValue = sanitizeEnvValue(value);
|
|
41
|
+
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
42
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
43
|
+
if (!pattern.test(lines[index])) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lines[index] = `${key}=${safeValue}`;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (lines.length > 0 && lines[lines.length - 1] !== "") {
|
|
52
|
+
lines.push("");
|
|
53
|
+
}
|
|
54
|
+
lines.push(`${key}=${safeValue}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function removeEnvKeys(lines: string[], keys: readonly string[]): boolean {
|
|
58
|
+
const patterns = keys.map((key) => new RegExp(`^\\s*${escapeRegex(key)}\\s*=`));
|
|
59
|
+
const originalLength = lines.length;
|
|
60
|
+
const kept = lines.filter((line) => !patterns.some((pattern) => pattern.test(line)));
|
|
61
|
+
if (kept.length === originalLength) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
lines.splice(0, lines.length, ...kept);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sanitizeEnvValue(value: unknown): string {
|
|
69
|
+
return String(value ?? "")
|
|
70
|
+
.replace(/[\r\n]/g, "")
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function unquote(value: string): string {
|
|
75
|
+
const raw = String(value ?? "").trim();
|
|
76
|
+
if (!raw) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
81
|
+
return raw.slice(1, -1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return raw;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function escapeRegex(value: string): string {
|
|
88
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function requireParentDir(filePath: string): string {
|
|
92
|
+
const parts = String(filePath ?? "").split(/[\\/]/);
|
|
93
|
+
if (parts.length <= 1) {
|
|
94
|
+
return ".";
|
|
95
|
+
}
|
|
96
|
+
parts.pop();
|
|
97
|
+
return parts.join("/") || ".";
|
|
98
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { parseEnvMap, readEnvLines, removeEnvKeys, writeEnvLines } from "./env-file-utils.mjs";
|
|
5
7
|
|
|
6
8
|
export type CopilotHubLayout = {
|
|
7
9
|
homeDir: string;
|
|
@@ -60,10 +62,44 @@ export function initializeCopilotHubLayout({
|
|
|
60
62
|
}: {
|
|
61
63
|
repoRoot: string;
|
|
62
64
|
layout: CopilotHubLayout;
|
|
63
|
-
}): { migratedPaths: string[] } {
|
|
65
|
+
}): { migratedPaths: string[]; normalizedEnvPaths: string[] } {
|
|
64
66
|
ensureCopilotHubLayout(layout);
|
|
65
67
|
const migratedPaths = migrateLegacyLayout({ repoRoot, layout });
|
|
66
|
-
|
|
68
|
+
const normalizedEnvPaths = normalizePersistentEnvFiles(layout);
|
|
69
|
+
return { migratedPaths, normalizedEnvPaths };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resetCopilotHubConfig({ layout }: { layout: CopilotHubLayout }): {
|
|
73
|
+
removedPaths: string[];
|
|
74
|
+
} {
|
|
75
|
+
const removedPaths: string[] = [];
|
|
76
|
+
|
|
77
|
+
for (const target of [layout.configDir, layout.dataDir, layout.logsDir]) {
|
|
78
|
+
if (!fs.existsSync(target)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
82
|
+
removedPaths.push(target);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const runtimeTargets = [
|
|
86
|
+
path.join(layout.runtimeDir, "pids"),
|
|
87
|
+
path.join(layout.runtimeDir, "services"),
|
|
88
|
+
path.join(layout.runtimeDir, "last-startup-error.json"),
|
|
89
|
+
layout.servicePromptStatePath,
|
|
90
|
+
];
|
|
91
|
+
for (const target of runtimeTargets) {
|
|
92
|
+
if (!fs.existsSync(target)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
96
|
+
removedPaths.push(target);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ensureCopilotHubLayout(layout);
|
|
100
|
+
return {
|
|
101
|
+
removedPaths: removedPaths.sort(),
|
|
102
|
+
};
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
export function ensureCopilotHubLayout(layout: CopilotHubLayout): void {
|
|
@@ -137,6 +173,132 @@ function migrateLegacyLayout({
|
|
|
137
173
|
return migratedPaths;
|
|
138
174
|
}
|
|
139
175
|
|
|
176
|
+
function normalizePersistentEnvFiles(layout: CopilotHubLayout): string[] {
|
|
177
|
+
const normalizedPaths: string[] = [];
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
normalizePersistentEnvFile(layout.agentEngineEnvPath, [
|
|
181
|
+
{
|
|
182
|
+
key: "BOT_DATA_DIR",
|
|
183
|
+
legacyValues: ["./data"],
|
|
184
|
+
wrongResolvedPath: path.join(layout.configDir, "data"),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: "BOT_REGISTRY_FILE",
|
|
188
|
+
legacyValues: ["./data/bot-registry.json"],
|
|
189
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "bot-registry.json"),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: "SECRET_STORE_FILE",
|
|
193
|
+
legacyValues: ["./data/secrets.json"],
|
|
194
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "secrets.json"),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "INSTANCE_LOCK_FILE",
|
|
198
|
+
legacyValues: ["./data/runtime.lock"],
|
|
199
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "runtime.lock"),
|
|
200
|
+
},
|
|
201
|
+
])
|
|
202
|
+
) {
|
|
203
|
+
normalizedPaths.push(layout.agentEngineEnvPath);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
normalizePersistentEnvFile(layout.controlPlaneEnvPath, [
|
|
208
|
+
{
|
|
209
|
+
key: "BOT_DATA_DIR",
|
|
210
|
+
legacyValues: ["./data"],
|
|
211
|
+
wrongResolvedPath: path.join(layout.configDir, "data"),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
key: "BOT_REGISTRY_FILE",
|
|
215
|
+
legacyValues: ["./data/bot-registry.json"],
|
|
216
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "bot-registry.json"),
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
key: "SECRET_STORE_FILE",
|
|
220
|
+
legacyValues: ["./data/secrets.json"],
|
|
221
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "secrets.json"),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
key: "INSTANCE_LOCK_FILE",
|
|
225
|
+
legacyValues: ["./data/runtime.lock"],
|
|
226
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "runtime.lock"),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
key: "HUB_DATA_DIR",
|
|
230
|
+
legacyValues: ["./data/copilot_hub"],
|
|
231
|
+
wrongResolvedPath: path.join(layout.configDir, "data", "copilot_hub"),
|
|
232
|
+
},
|
|
233
|
+
])
|
|
234
|
+
) {
|
|
235
|
+
normalizedPaths.push(layout.controlPlaneEnvPath);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return normalizedPaths.sort();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizePersistentEnvFile(
|
|
242
|
+
filePath: string,
|
|
243
|
+
rules: Array<{ key: string; legacyValues: string[]; wrongResolvedPath: string }>,
|
|
244
|
+
): boolean {
|
|
245
|
+
if (!fs.existsSync(filePath)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const lines = readEnvLines(filePath);
|
|
250
|
+
const envMap = parseEnvMap(lines);
|
|
251
|
+
const keysToRemove = rules
|
|
252
|
+
.filter((rule) =>
|
|
253
|
+
shouldRemoveLegacyManagedPath(envMap[rule.key], {
|
|
254
|
+
legacyValues: rule.legacyValues,
|
|
255
|
+
wrongResolvedPath: rule.wrongResolvedPath,
|
|
256
|
+
configBaseDir: path.dirname(filePath),
|
|
257
|
+
}),
|
|
258
|
+
)
|
|
259
|
+
.map((rule) => rule.key);
|
|
260
|
+
|
|
261
|
+
if (keysToRemove.length === 0) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
removeEnvKeys(lines, keysToRemove);
|
|
266
|
+
writeEnvLines(filePath, lines);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function shouldRemoveLegacyManagedPath(
|
|
271
|
+
rawValue: string | undefined,
|
|
272
|
+
{
|
|
273
|
+
legacyValues,
|
|
274
|
+
wrongResolvedPath,
|
|
275
|
+
configBaseDir,
|
|
276
|
+
}: {
|
|
277
|
+
legacyValues: string[];
|
|
278
|
+
wrongResolvedPath: string;
|
|
279
|
+
configBaseDir: string;
|
|
280
|
+
},
|
|
281
|
+
): boolean {
|
|
282
|
+
const value = String(rawValue ?? "").trim();
|
|
283
|
+
if (!value) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const normalizedValue = normalizeForCompare(value);
|
|
288
|
+
if (legacyValues.some((entry) => normalizeForCompare(entry) === normalizedValue)) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (path.isAbsolute(value)) {
|
|
293
|
+
return normalizeForCompare(value) === normalizeForCompare(wrongResolvedPath);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
normalizeForCompare(path.resolve(configBaseDir, value)) ===
|
|
298
|
+
normalizeForCompare(wrongResolvedPath)
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
140
302
|
function resolveLegacyPaths(repoRoot: string): {
|
|
141
303
|
agentEngineEnvPath: string;
|
|
142
304
|
controlPlaneEnvPath: string;
|
|
@@ -195,6 +357,14 @@ function normalizePath(value: unknown, pathApi: typeof path.posix | typeof path.
|
|
|
195
357
|
return normalized ? pathApi.resolve(normalized) : "";
|
|
196
358
|
}
|
|
197
359
|
|
|
360
|
+
function normalizeForCompare(value: unknown): string {
|
|
361
|
+
const normalized = String(value ?? "").trim();
|
|
362
|
+
if (!normalized) {
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
366
|
+
}
|
|
367
|
+
|
|
198
368
|
function getPathApi(platform: NodeJS.Platform): typeof path.posix | typeof path.win32 {
|
|
199
369
|
return platform === "win32" ? path.win32 : path.posix;
|
|
200
370
|
}
|
|
@@ -354,6 +354,11 @@ function buildServiceEnvironment(service) {
|
|
|
354
354
|
BOT_REGISTRY_FILE: service.botRegistryFilePath,
|
|
355
355
|
SECRET_STORE_FILE: service.secretStoreFilePath,
|
|
356
356
|
INSTANCE_LOCK_FILE: service.instanceLockFilePath,
|
|
357
|
+
...(service.id === "control-plane"
|
|
358
|
+
? {
|
|
359
|
+
HUB_DATA_DIR: path.join(service.dataDir, "copilot_hub"),
|
|
360
|
+
}
|
|
361
|
+
: {}),
|
|
357
362
|
};
|
|
358
363
|
}
|
|
359
364
|
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import {
|
|
7
7
|
initializeCopilotHubLayout,
|
|
8
|
+
resetCopilotHubConfig,
|
|
8
9
|
resolveCopilotHubHomeDir,
|
|
9
10
|
resolveCopilotHubLayout,
|
|
10
11
|
} from "../dist/install-layout.mjs";
|
|
@@ -45,8 +46,16 @@ test("initializeCopilotHubLayout migrates legacy env and data files once", () =>
|
|
|
45
46
|
fs.mkdirSync(path.dirname(legacyControlEnvPath), { recursive: true });
|
|
46
47
|
fs.mkdirSync(path.dirname(legacyEngineDataFile), { recursive: true });
|
|
47
48
|
fs.mkdirSync(path.dirname(legacyPromptStatePath), { recursive: true });
|
|
48
|
-
fs.writeFileSync(
|
|
49
|
-
|
|
49
|
+
fs.writeFileSync(
|
|
50
|
+
legacyEngineEnvPath,
|
|
51
|
+
["TELEGRAM_TOKEN_AGENT_1=123:abc", "BOT_REGISTRY_FILE=./data/bot-registry.json", ""].join("\n"),
|
|
52
|
+
"utf8",
|
|
53
|
+
);
|
|
54
|
+
fs.writeFileSync(
|
|
55
|
+
legacyControlEnvPath,
|
|
56
|
+
["HUB_TELEGRAM_TOKEN=456:def", "HUB_DATA_DIR=./data/copilot_hub", ""].join("\n"),
|
|
57
|
+
"utf8",
|
|
58
|
+
);
|
|
50
59
|
fs.writeFileSync(legacyEngineDataFile, '{"ok":true}\n', "utf8");
|
|
51
60
|
fs.writeFileSync(legacyEngineLockPath, "stale-lock\n", "utf8");
|
|
52
61
|
fs.writeFileSync(legacyPromptStatePath, '{"decision":"accepted"}\n', "utf8");
|
|
@@ -79,4 +88,47 @@ test("initializeCopilotHubLayout migrates legacy env and data files once", () =>
|
|
|
79
88
|
|
|
80
89
|
const secondPass = initializeCopilotHubLayout({ repoRoot, layout });
|
|
81
90
|
assert.deepEqual(secondPass.migratedPaths, []);
|
|
91
|
+
assert.deepEqual(secondPass.normalizedEnvPaths, []);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("resetCopilotHubConfig removes persisted state but keeps the layout shell", () => {
|
|
95
|
+
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "copilot-hub-reset-"));
|
|
96
|
+
const layout = resolveCopilotHubLayout({
|
|
97
|
+
repoRoot,
|
|
98
|
+
env: {
|
|
99
|
+
COPILOT_HUB_HOME_DIR: path.join(repoRoot, "user-home"),
|
|
100
|
+
},
|
|
101
|
+
homeDirectory: repoRoot,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
initializeCopilotHubLayout({ repoRoot, layout });
|
|
105
|
+
fs.writeFileSync(layout.agentEngineEnvPath, "TELEGRAM_TOKEN_AGENT_1=123:abc\n", "utf8");
|
|
106
|
+
fs.mkdirSync(layout.agentEngineDataDir, { recursive: true });
|
|
107
|
+
fs.writeFileSync(
|
|
108
|
+
path.join(layout.agentEngineDataDir, "bot-registry.json"),
|
|
109
|
+
'{"version":3}\n',
|
|
110
|
+
"utf8",
|
|
111
|
+
);
|
|
112
|
+
fs.mkdirSync(path.join(layout.runtimeDir, "pids"), { recursive: true });
|
|
113
|
+
fs.writeFileSync(path.join(layout.runtimeDir, "pids", "daemon.json"), '{"pid":1}\n', "utf8");
|
|
114
|
+
fs.writeFileSync(layout.servicePromptStatePath, '{"decision":"accepted"}\n', "utf8");
|
|
115
|
+
fs.writeFileSync(
|
|
116
|
+
path.join(layout.runtimeDir, "windows-daemon-launcher.vbs"),
|
|
117
|
+
"' launcher\n",
|
|
118
|
+
"utf8",
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const reset = resetCopilotHubConfig({ layout });
|
|
122
|
+
|
|
123
|
+
assert.ok(reset.removedPaths.includes(layout.configDir));
|
|
124
|
+
assert.ok(reset.removedPaths.includes(layout.dataDir));
|
|
125
|
+
assert.ok(reset.removedPaths.includes(layout.logsDir));
|
|
126
|
+
assert.ok(fs.existsSync(layout.configDir));
|
|
127
|
+
assert.ok(fs.existsSync(layout.dataDir));
|
|
128
|
+
assert.ok(fs.existsSync(layout.logsDir));
|
|
129
|
+
assert.equal(fs.existsSync(layout.agentEngineEnvPath), false);
|
|
130
|
+
assert.equal(fs.existsSync(path.join(layout.agentEngineDataDir, "bot-registry.json")), false);
|
|
131
|
+
assert.equal(fs.existsSync(path.join(layout.runtimeDir, "pids")), false);
|
|
132
|
+
assert.equal(fs.existsSync(layout.servicePromptStatePath), false);
|
|
133
|
+
assert.equal(fs.existsSync(path.join(layout.runtimeDir, "windows-daemon-launcher.vbs")), true);
|
|
82
134
|
});
|