copilot-hub 0.1.0
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/LICENSE +21 -0
- package/README.md +215 -0
- package/apps/agent-engine/.env.example +41 -0
- package/apps/agent-engine/LICENSE +21 -0
- package/apps/agent-engine/README.md +57 -0
- package/apps/agent-engine/bot-registry.example.json +28 -0
- package/apps/agent-engine/capabilities/example/index.js +3 -0
- package/apps/agent-engine/capabilities/example/manifest.json +14 -0
- package/apps/agent-engine/dist/agent-worker.js +241 -0
- package/apps/agent-engine/dist/config.js +225 -0
- package/apps/agent-engine/dist/index.js +352 -0
- package/apps/agent-engine/dist/test/project-fingerprint.test.js +40 -0
- package/apps/agent-engine/dist/test/thread-id.test.js +12 -0
- package/apps/agent-engine/package.json +28 -0
- package/apps/control-plane/.env.example +25 -0
- package/apps/control-plane/README.md +35 -0
- package/apps/control-plane/bot-registry.example.json +40 -0
- package/apps/control-plane/capabilities/example/index.js +3 -0
- package/apps/control-plane/capabilities/example/manifest.json +14 -0
- package/apps/control-plane/dist/agent-worker.js +243 -0
- package/apps/control-plane/dist/channels/channel-factory.js +21 -0
- package/apps/control-plane/dist/channels/hub-ops-commands.js +752 -0
- package/apps/control-plane/dist/channels/telegram-channel.js +743 -0
- package/apps/control-plane/dist/channels/whatsapp-channel.js +35 -0
- package/apps/control-plane/dist/config.js +230 -0
- package/apps/control-plane/dist/copilot-hub.js +138 -0
- package/apps/control-plane/dist/index.js +349 -0
- package/apps/control-plane/dist/kernel/admin-contract.js +51 -0
- package/apps/control-plane/dist/test/project-fingerprint.test.js +40 -0
- package/apps/control-plane/dist/test/thread-id.test.js +12 -0
- package/apps/control-plane/package.json +27 -0
- package/package.json +89 -0
- package/packages/contracts/README.md +10 -0
- package/packages/contracts/dist/control-plane.d.ts +24 -0
- package/packages/contracts/dist/control-plane.js +37 -0
- package/packages/contracts/dist/control-plane.js.map +1 -0
- package/packages/contracts/dist/index.d.ts +1 -0
- package/packages/contracts/dist/index.js +2 -0
- package/packages/contracts/dist/index.js.map +1 -0
- package/packages/contracts/package.json +27 -0
- package/packages/core/README.md +33 -0
- package/packages/core/dist/agent-supervisor.d.ts +39 -0
- package/packages/core/dist/agent-supervisor.js +552 -0
- package/packages/core/dist/agent-supervisor.js.map +1 -0
- package/packages/core/dist/bot-manager.d.ts +66 -0
- package/packages/core/dist/bot-manager.js +333 -0
- package/packages/core/dist/bot-manager.js.map +1 -0
- package/packages/core/dist/bot-registry.d.ts +60 -0
- package/packages/core/dist/bot-registry.js +381 -0
- package/packages/core/dist/bot-registry.js.map +1 -0
- package/packages/core/dist/bot-runtime.d.ts +135 -0
- package/packages/core/dist/bot-runtime.js +349 -0
- package/packages/core/dist/bot-runtime.js.map +1 -0
- package/packages/core/dist/bridge-service.d.ts +39 -0
- package/packages/core/dist/bridge-service.js +272 -0
- package/packages/core/dist/bridge-service.js.map +1 -0
- package/packages/core/dist/capability-manager.d.ts +18 -0
- package/packages/core/dist/capability-manager.js +335 -0
- package/packages/core/dist/capability-manager.js.map +1 -0
- package/packages/core/dist/capability-scaffold.d.ts +26 -0
- package/packages/core/dist/capability-scaffold.js +118 -0
- package/packages/core/dist/capability-scaffold.js.map +1 -0
- package/packages/core/dist/channel-factory.d.ts +6 -0
- package/packages/core/dist/channel-factory.js +22 -0
- package/packages/core/dist/channel-factory.js.map +1 -0
- package/packages/core/dist/codex-app-client.d.ts +56 -0
- package/packages/core/dist/codex-app-client.js +762 -0
- package/packages/core/dist/codex-app-client.js.map +1 -0
- package/packages/core/dist/codex-provider.d.ts +31 -0
- package/packages/core/dist/codex-provider.js +64 -0
- package/packages/core/dist/codex-provider.js.map +1 -0
- package/packages/core/dist/control-permission.d.ts +19 -0
- package/packages/core/dist/control-permission.js +106 -0
- package/packages/core/dist/control-permission.js.map +1 -0
- package/packages/core/dist/control-plane-actions.d.ts +1 -0
- package/packages/core/dist/control-plane-actions.js +2 -0
- package/packages/core/dist/control-plane-actions.js.map +1 -0
- package/packages/core/dist/example-capability.d.ts +17 -0
- package/packages/core/dist/example-capability.js +22 -0
- package/packages/core/dist/example-capability.js.map +1 -0
- package/packages/core/dist/extension-contract.d.ts +22 -0
- package/packages/core/dist/extension-contract.js +28 -0
- package/packages/core/dist/extension-contract.js.map +1 -0
- package/packages/core/dist/index.d.ts +26 -0
- package/packages/core/dist/index.js +27 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/instance-lock.d.ts +9 -0
- package/packages/core/dist/instance-lock.js +74 -0
- package/packages/core/dist/instance-lock.js.map +1 -0
- package/packages/core/dist/kernel-control-plane.d.ts +16 -0
- package/packages/core/dist/kernel-control-plane.js +500 -0
- package/packages/core/dist/kernel-control-plane.js.map +1 -0
- package/packages/core/dist/kernel-version.d.ts +1 -0
- package/packages/core/dist/kernel-version.js +2 -0
- package/packages/core/dist/kernel-version.js.map +1 -0
- package/packages/core/dist/project-fingerprint.d.ts +11 -0
- package/packages/core/dist/project-fingerprint.js +33 -0
- package/packages/core/dist/project-fingerprint.js.map +1 -0
- package/packages/core/dist/provider-factory.d.ts +7 -0
- package/packages/core/dist/provider-factory.js +21 -0
- package/packages/core/dist/provider-factory.js.map +1 -0
- package/packages/core/dist/secret-store.d.ts +18 -0
- package/packages/core/dist/secret-store.js +110 -0
- package/packages/core/dist/secret-store.js.map +1 -0
- package/packages/core/dist/state-store.d.ts +50 -0
- package/packages/core/dist/state-store.js +324 -0
- package/packages/core/dist/state-store.js.map +1 -0
- package/packages/core/dist/telegram-channel.d.ts +27 -0
- package/packages/core/dist/telegram-channel.js +951 -0
- package/packages/core/dist/telegram-channel.js.map +1 -0
- package/packages/core/dist/thread-id.d.ts +1 -0
- package/packages/core/dist/thread-id.js +12 -0
- package/packages/core/dist/thread-id.js.map +1 -0
- package/packages/core/dist/whatsapp-channel.d.ts +26 -0
- package/packages/core/dist/whatsapp-channel.js +36 -0
- package/packages/core/dist/whatsapp-channel.js.map +1 -0
- package/packages/core/dist/workspace-paths.d.ts +5 -0
- package/packages/core/dist/workspace-paths.js +77 -0
- package/packages/core/dist/workspace-paths.js.map +1 -0
- package/packages/core/dist/workspace-policy.d.ts +30 -0
- package/packages/core/dist/workspace-policy.js +104 -0
- package/packages/core/dist/workspace-policy.js.map +1 -0
- package/packages/core/package.json +126 -0
- package/scripts/cli.mjs +537 -0
- package/scripts/configure.mjs +254 -0
- package/scripts/ensure-shared-build.mjs +96 -0
- package/scripts/run-node-tests.mjs +52 -0
- package/scripts/supervisor.mjs +332 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
export class WhatsAppChannel {
|
|
3
|
+
constructor({ channelConfig, runtime }) {
|
|
4
|
+
this.kind = "whatsapp";
|
|
5
|
+
this.id = String(channelConfig.id ?? "whatsapp");
|
|
6
|
+
this.config = channelConfig;
|
|
7
|
+
this.runtime = runtime;
|
|
8
|
+
this.running = false;
|
|
9
|
+
this.error = null;
|
|
10
|
+
}
|
|
11
|
+
async start() {
|
|
12
|
+
this.running = false;
|
|
13
|
+
this.error =
|
|
14
|
+
"WhatsApp adapter is declared but not wired yet. Provide implementation in src/channels/whatsapp-channel.js.";
|
|
15
|
+
return this.getStatus();
|
|
16
|
+
}
|
|
17
|
+
async stop() {
|
|
18
|
+
this.running = false;
|
|
19
|
+
return this.getStatus();
|
|
20
|
+
}
|
|
21
|
+
async shutdown() {
|
|
22
|
+
await this.stop();
|
|
23
|
+
}
|
|
24
|
+
async notifyApproval() {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
getStatus() {
|
|
28
|
+
return {
|
|
29
|
+
kind: this.kind,
|
|
30
|
+
id: this.id,
|
|
31
|
+
running: this.running,
|
|
32
|
+
error: this.error,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
|
|
6
|
+
import { normalizeAdminBotId, normalizeAdminTokenEnv } from "./kernel/admin-contract.js";
|
|
7
|
+
import { getDefaultExternalWorkspaceBasePath, getKernelRootPath, } from "@copilot-hub/core/workspace-paths";
|
|
8
|
+
dotenv.config();
|
|
9
|
+
const kernelRootPath = getKernelRootPath();
|
|
10
|
+
const defaultWorkspaceRoot = resolveWorkspaceRoot(process.env.DEFAULT_WORKSPACE_ROOT ?? getDefaultExternalWorkspaceBasePath(kernelRootPath));
|
|
11
|
+
const projectsBaseDir = path.resolve(process.env.PROJECTS_BASE_DIR ?? defaultWorkspaceRoot);
|
|
12
|
+
const workspaceStrictMode = parseBoolean(process.env.WORKSPACE_STRICT_MODE ?? "true");
|
|
13
|
+
const workspaceAllowedRoots = parseWorkspaceAllowedRoots(process.env.WORKSPACE_ALLOWED_ROOTS ?? "", {
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
});
|
|
16
|
+
const workspacePolicy = createWorkspaceBoundaryPolicy({
|
|
17
|
+
kernelRootPath,
|
|
18
|
+
defaultWorkspaceRoot,
|
|
19
|
+
projectsBaseDir,
|
|
20
|
+
strictMode: workspaceStrictMode,
|
|
21
|
+
additionalAllowedRoots: workspaceAllowedRoots,
|
|
22
|
+
});
|
|
23
|
+
assertWorkspaceAllowed({
|
|
24
|
+
workspaceRoot: defaultWorkspaceRoot,
|
|
25
|
+
policy: workspacePolicy,
|
|
26
|
+
label: "DEFAULT_WORKSPACE_ROOT",
|
|
27
|
+
});
|
|
28
|
+
const dataDir = path.resolve(process.env.BOT_DATA_DIR ?? path.join(process.cwd(), "data"));
|
|
29
|
+
const botRegistryFilePath = path.resolve(process.env.BOT_REGISTRY_FILE ?? path.join(dataDir, "bot-registry.json"));
|
|
30
|
+
const secretStoreFilePath = path.resolve(process.env.SECRET_STORE_FILE ?? path.join(dataDir, "secrets.json"));
|
|
31
|
+
const adminBotId = normalizeAdminBotId(process.env.ADMIN_BOT_ID ?? "admin_agent");
|
|
32
|
+
const adminTelegramTokenEnvName = normalizeAdminTokenEnv(process.env.ADMIN_TELEGRAM_TOKEN_ENV ?? "TELEGRAM_TOKEN_ADMIN");
|
|
33
|
+
const adminTelegramToken = String(process.env[adminTelegramTokenEnvName] ?? "").trim();
|
|
34
|
+
const instanceLockEnabled = parseBoolean(process.env.INSTANCE_LOCK_ENABLED ?? "true");
|
|
35
|
+
const instanceLockFilePath = path.resolve(process.env.INSTANCE_LOCK_FILE ?? path.join(dataDir, "runtime.lock"));
|
|
36
|
+
const bootstrapTelegramToken = String(process.env.TELEGRAM_BOT_TOKEN ?? "").trim();
|
|
37
|
+
const defaultProviderKind = normalizeProviderKind(process.env.DEFAULT_PROVIDER_KIND ?? "codex");
|
|
38
|
+
const codexBin = resolveCodexBin(process.env.CODEX_BIN);
|
|
39
|
+
const codexHomeDir = resolveOptionalPath(process.env.CODEX_HOME_DIR);
|
|
40
|
+
const codexSandbox = normalizeCodexSandbox(process.env.CODEX_SANDBOX ?? "danger-full-access");
|
|
41
|
+
const codexApprovalPolicy = normalizeApprovalPolicy(process.env.CODEX_APPROVAL_POLICY ?? "never");
|
|
42
|
+
const turnActivityTimeoutMs = Number.parseInt(process.env.TURN_ACTIVITY_TIMEOUT_MS ?? "3600000", 10);
|
|
43
|
+
if (!Number.isFinite(turnActivityTimeoutMs) || turnActivityTimeoutMs < 10000) {
|
|
44
|
+
throw new Error("TURN_ACTIVITY_TIMEOUT_MS must be an integer >= 10000.");
|
|
45
|
+
}
|
|
46
|
+
const maxMessages = Number.parseInt(process.env.MAX_THREAD_MESSAGES ?? "200", 10);
|
|
47
|
+
if (!Number.isFinite(maxMessages) || maxMessages < 20) {
|
|
48
|
+
throw new Error("MAX_THREAD_MESSAGES must be an integer >= 20.");
|
|
49
|
+
}
|
|
50
|
+
const webHost = (process.env.WEB_HOST ?? "127.0.0.1").trim() || "127.0.0.1";
|
|
51
|
+
const webPort = Number.parseInt(process.env.WEB_PORT ?? "8787", 10);
|
|
52
|
+
if (!Number.isFinite(webPort) || webPort < 1 || webPort > 65535) {
|
|
53
|
+
throw new Error("WEB_PORT must be an integer in range 1..65535.");
|
|
54
|
+
}
|
|
55
|
+
const webPublicBaseUrl = (process.env.WEB_PUBLIC_BASE_URL ?? `http://localhost:${webPort}`).trim();
|
|
56
|
+
const webPublicBaseUrlExplicit = Boolean((process.env.WEB_PUBLIC_BASE_URL ?? "").trim());
|
|
57
|
+
const webPortAutoIncrement = parseBoolean(process.env.WEB_PORT_AUTO_INCREMENT ?? "true");
|
|
58
|
+
const webPortSearchMax = Number.parseInt(process.env.WEB_PORT_SEARCH_MAX ?? "30", 10);
|
|
59
|
+
if (!Number.isFinite(webPortSearchMax) || webPortSearchMax < 1 || webPortSearchMax > 1000) {
|
|
60
|
+
throw new Error("WEB_PORT_SEARCH_MAX must be an integer in range 1..1000.");
|
|
61
|
+
}
|
|
62
|
+
const agentHeartbeatEnabled = parseBoolean(process.env.AGENT_HEARTBEAT_ENABLED ?? "true");
|
|
63
|
+
const agentHeartbeatIntervalMs = Number.parseInt(process.env.AGENT_HEARTBEAT_INTERVAL_MS ?? "5000", 10);
|
|
64
|
+
if (!Number.isFinite(agentHeartbeatIntervalMs) ||
|
|
65
|
+
agentHeartbeatIntervalMs < 1000 ||
|
|
66
|
+
agentHeartbeatIntervalMs > 600000) {
|
|
67
|
+
throw new Error("AGENT_HEARTBEAT_INTERVAL_MS must be an integer in range 1000..600000.");
|
|
68
|
+
}
|
|
69
|
+
const agentHeartbeatTimeoutMs = Number.parseInt(process.env.AGENT_HEARTBEAT_TIMEOUT_MS ?? "4000", 10);
|
|
70
|
+
if (!Number.isFinite(agentHeartbeatTimeoutMs) ||
|
|
71
|
+
agentHeartbeatTimeoutMs < 500 ||
|
|
72
|
+
agentHeartbeatTimeoutMs > 60000) {
|
|
73
|
+
throw new Error("AGENT_HEARTBEAT_TIMEOUT_MS must be an integer in range 500..60000.");
|
|
74
|
+
}
|
|
75
|
+
const defaultThreadMode = normalizeThreadMode(process.env.THREAD_MODE);
|
|
76
|
+
const defaultSharedThreadId = String(process.env.SHARED_THREAD_ID ?? "shared-main").trim();
|
|
77
|
+
if (!/^[A-Za-z0-9:_-]{1,120}$/.test(defaultSharedThreadId)) {
|
|
78
|
+
throw new Error("SHARED_THREAD_ID has invalid format.");
|
|
79
|
+
}
|
|
80
|
+
const defaultAllowedChatIds = new Set((process.env.TELEGRAM_ALLOWED_CHAT_IDS ?? "")
|
|
81
|
+
.split(",")
|
|
82
|
+
.map((value) => value.trim())
|
|
83
|
+
.filter(Boolean));
|
|
84
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
85
|
+
export const config = {
|
|
86
|
+
defaultProviderKind,
|
|
87
|
+
providerDefaults: {
|
|
88
|
+
defaultKind: defaultProviderKind,
|
|
89
|
+
codexBin,
|
|
90
|
+
codexHomeDir,
|
|
91
|
+
codexSandbox,
|
|
92
|
+
codexApprovalPolicy,
|
|
93
|
+
},
|
|
94
|
+
codexBin,
|
|
95
|
+
codexHomeDir,
|
|
96
|
+
codexSandbox,
|
|
97
|
+
codexApprovalPolicy,
|
|
98
|
+
kernelRootPath,
|
|
99
|
+
workspaceStrictMode,
|
|
100
|
+
workspaceAllowedRoots,
|
|
101
|
+
workspacePolicy,
|
|
102
|
+
defaultWorkspaceRoot,
|
|
103
|
+
projectsBaseDir,
|
|
104
|
+
dataDir,
|
|
105
|
+
botRegistryFilePath,
|
|
106
|
+
secretStoreFilePath,
|
|
107
|
+
adminBotId,
|
|
108
|
+
adminTelegramTokenEnvName,
|
|
109
|
+
adminTelegramToken,
|
|
110
|
+
instanceLockEnabled,
|
|
111
|
+
instanceLockFilePath,
|
|
112
|
+
bootstrapTelegramToken,
|
|
113
|
+
turnActivityTimeoutMs,
|
|
114
|
+
maxMessages,
|
|
115
|
+
webHost,
|
|
116
|
+
webPort,
|
|
117
|
+
webPublicBaseUrl,
|
|
118
|
+
webPublicBaseUrlExplicit,
|
|
119
|
+
webPortAutoIncrement,
|
|
120
|
+
webPortSearchMax,
|
|
121
|
+
agentHeartbeatEnabled,
|
|
122
|
+
agentHeartbeatIntervalMs,
|
|
123
|
+
agentHeartbeatTimeoutMs,
|
|
124
|
+
defaultThreadMode,
|
|
125
|
+
defaultSharedThreadId,
|
|
126
|
+
defaultAllowedChatIds,
|
|
127
|
+
};
|
|
128
|
+
function resolveCodexBin(rawValue) {
|
|
129
|
+
const value = (rawValue ?? "").trim();
|
|
130
|
+
const normalized = value.toLowerCase();
|
|
131
|
+
if (value && normalized !== "codex") {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
if (process.platform === "win32") {
|
|
135
|
+
const vscodeCodex = findVscodeCodexExe();
|
|
136
|
+
if (vscodeCodex) {
|
|
137
|
+
return vscodeCodex;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return value || "codex";
|
|
141
|
+
}
|
|
142
|
+
function findVscodeCodexExe() {
|
|
143
|
+
const userProfile = process.env.USERPROFILE;
|
|
144
|
+
if (!userProfile) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const extensionsDir = path.join(userProfile, ".vscode", "extensions");
|
|
148
|
+
if (!fs.existsSync(extensionsDir)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const candidates = fs
|
|
152
|
+
.readdirSync(extensionsDir, { withFileTypes: true })
|
|
153
|
+
.filter((entry) => entry.isDirectory())
|
|
154
|
+
.map((entry) => entry.name)
|
|
155
|
+
.filter((name) => name.startsWith("openai.chatgpt-"))
|
|
156
|
+
.sort()
|
|
157
|
+
.reverse();
|
|
158
|
+
for (const folder of candidates) {
|
|
159
|
+
const exePath = path.join(extensionsDir, folder, "bin", "windows-x86_64", "codex.exe");
|
|
160
|
+
if (fs.existsSync(exePath)) {
|
|
161
|
+
return exePath;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
function normalizeThreadMode(value) {
|
|
167
|
+
const mode = String(value ?? "single")
|
|
168
|
+
.trim()
|
|
169
|
+
.toLowerCase();
|
|
170
|
+
if (mode === "single" || mode === "per_chat") {
|
|
171
|
+
return mode;
|
|
172
|
+
}
|
|
173
|
+
throw new Error("THREAD_MODE must be either 'single' or 'per_chat'.");
|
|
174
|
+
}
|
|
175
|
+
function parseBoolean(value) {
|
|
176
|
+
const normalized = String(value ?? "")
|
|
177
|
+
.trim()
|
|
178
|
+
.toLowerCase();
|
|
179
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
throw new Error("Invalid boolean value in environment.");
|
|
186
|
+
}
|
|
187
|
+
function resolveOptionalPath(value) {
|
|
188
|
+
const raw = String(value ?? "").trim();
|
|
189
|
+
if (!raw) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return path.resolve(raw);
|
|
193
|
+
}
|
|
194
|
+
function normalizeCodexSandbox(value) {
|
|
195
|
+
const mode = String(value ?? "")
|
|
196
|
+
.trim()
|
|
197
|
+
.toLowerCase();
|
|
198
|
+
if (mode === "read-only" || mode === "workspace-write" || mode === "danger-full-access") {
|
|
199
|
+
return mode;
|
|
200
|
+
}
|
|
201
|
+
throw new Error("CODEX_SANDBOX must be one of: read-only, workspace-write, danger-full-access.");
|
|
202
|
+
}
|
|
203
|
+
function normalizeApprovalPolicy(value) {
|
|
204
|
+
const mode = String(value ?? "")
|
|
205
|
+
.trim()
|
|
206
|
+
.toLowerCase();
|
|
207
|
+
if (mode === "untrusted" || mode === "on-failure" || mode === "on-request" || mode === "never") {
|
|
208
|
+
return mode;
|
|
209
|
+
}
|
|
210
|
+
throw new Error("CODEX_APPROVAL_POLICY must be one of: untrusted, on-failure, on-request, never.");
|
|
211
|
+
}
|
|
212
|
+
function normalizeProviderKind(value) {
|
|
213
|
+
const kind = String(value ?? "")
|
|
214
|
+
.trim()
|
|
215
|
+
.toLowerCase();
|
|
216
|
+
if (!kind) {
|
|
217
|
+
return "codex";
|
|
218
|
+
}
|
|
219
|
+
if (kind === "codex") {
|
|
220
|
+
return kind;
|
|
221
|
+
}
|
|
222
|
+
throw new Error("DEFAULT_PROVIDER_KIND must currently be 'codex'.");
|
|
223
|
+
}
|
|
224
|
+
function resolveWorkspaceRoot(value) {
|
|
225
|
+
const raw = String(value ?? "").trim();
|
|
226
|
+
if (!raw) {
|
|
227
|
+
throw new Error("DEFAULT_WORKSPACE_ROOT must not be empty.");
|
|
228
|
+
}
|
|
229
|
+
return path.resolve(raw);
|
|
230
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { BotRuntime } from "@copilot-hub/core/bot-runtime";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { assertWorkspaceAllowed } from "@copilot-hub/core/workspace-policy";
|
|
6
|
+
import { createChannelAdapter } from "./channels/channel-factory.js";
|
|
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();
|
|
9
|
+
if (!hubToken) {
|
|
10
|
+
throw new Error([
|
|
11
|
+
`Hub Telegram token is missing (${tokenEnvName}).`,
|
|
12
|
+
"Set this token to start copilot-hub.",
|
|
13
|
+
].join("\n"));
|
|
14
|
+
}
|
|
15
|
+
const hubId = String(process.env.HUB_ID ?? "copilot_hub").trim() || "copilot_hub";
|
|
16
|
+
const hubName = String(process.env.HUB_NAME ?? "Copilot Hub").trim() || "Copilot Hub";
|
|
17
|
+
const hubWorkspaceRootRaw = path.resolve(String(process.env.HUB_WORKSPACE_ROOT ?? config.defaultWorkspaceRoot));
|
|
18
|
+
const hubWorkspaceRoot = assertWorkspaceAllowed({
|
|
19
|
+
workspaceRoot: hubWorkspaceRootRaw,
|
|
20
|
+
policy: config.workspacePolicy,
|
|
21
|
+
label: "HUB_WORKSPACE_ROOT",
|
|
22
|
+
});
|
|
23
|
+
const hubDataDir = path.resolve(String(process.env.HUB_DATA_DIR ?? path.join(config.dataDir, "copilot_hub")));
|
|
24
|
+
const hubThreadMode = normalizeThreadMode(process.env.HUB_THREAD_MODE ?? "per_chat");
|
|
25
|
+
const hubSharedThreadId = String(process.env.HUB_SHARED_THREAD_ID ?? "shared-copilot-hub").trim() || "shared-copilot-hub";
|
|
26
|
+
const allowedChatIds = parseCsvSet(process.env.HUB_ALLOWED_CHAT_IDS ?? "");
|
|
27
|
+
const hubImmutableCore = parseBoolean(process.env.HUB_IMMUTABLE_CORE ?? "true");
|
|
28
|
+
let hubSandboxMode = String(process.env.HUB_CODEX_SANDBOX ?? config.codexSandbox ?? "workspace-write").trim();
|
|
29
|
+
let hubApprovalPolicy = String(process.env.HUB_CODEX_APPROVAL_POLICY ?? config.codexApprovalPolicy ?? "on-request").trim();
|
|
30
|
+
if (hubImmutableCore && hubSandboxMode === "danger-full-access" && hubApprovalPolicy === "never") {
|
|
31
|
+
console.warn("HUB_IMMUTABLE_CORE=true: forcing safer hub policy (workspace-write + on-request) instead of danger-full-access + never.");
|
|
32
|
+
hubSandboxMode = "workspace-write";
|
|
33
|
+
hubApprovalPolicy = "on-request";
|
|
34
|
+
}
|
|
35
|
+
const runtime = new BotRuntime({
|
|
36
|
+
botConfig: {
|
|
37
|
+
id: hubId,
|
|
38
|
+
name: hubName,
|
|
39
|
+
enabled: true,
|
|
40
|
+
autoStart: true,
|
|
41
|
+
workspaceRoot: hubWorkspaceRoot,
|
|
42
|
+
dataDir: hubDataDir,
|
|
43
|
+
threadMode: hubThreadMode,
|
|
44
|
+
sharedThreadId: hubSharedThreadId,
|
|
45
|
+
provider: {
|
|
46
|
+
kind: config.defaultProviderKind,
|
|
47
|
+
options: {
|
|
48
|
+
sandboxMode: hubSandboxMode,
|
|
49
|
+
approvalPolicy: hubApprovalPolicy,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
kernelAccess: {
|
|
53
|
+
enabled: false,
|
|
54
|
+
allowedActions: [],
|
|
55
|
+
allowedChatIds: [],
|
|
56
|
+
},
|
|
57
|
+
channels: [
|
|
58
|
+
{
|
|
59
|
+
kind: "telegram",
|
|
60
|
+
id: "telegram_copilot_hub",
|
|
61
|
+
token: hubToken,
|
|
62
|
+
allowedChatIds: [...allowedChatIds],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
capabilities: [],
|
|
66
|
+
},
|
|
67
|
+
providerDefaults: config.providerDefaults,
|
|
68
|
+
turnActivityTimeoutMs: config.turnActivityTimeoutMs,
|
|
69
|
+
maxMessages: config.maxMessages,
|
|
70
|
+
channelAdapterFactory: createChannelAdapter,
|
|
71
|
+
});
|
|
72
|
+
let shuttingDown = false;
|
|
73
|
+
await bootstrap();
|
|
74
|
+
async function bootstrap() {
|
|
75
|
+
await runtime.startChannels();
|
|
76
|
+
console.log(`[copilot_hub] online as '${hubId}' on workspace '${hubWorkspaceRoot}'.`);
|
|
77
|
+
registerSignals();
|
|
78
|
+
}
|
|
79
|
+
function registerSignals() {
|
|
80
|
+
process.on("SIGINT", () => {
|
|
81
|
+
void shutdown(0);
|
|
82
|
+
});
|
|
83
|
+
process.on("SIGTERM", () => {
|
|
84
|
+
void shutdown(0);
|
|
85
|
+
});
|
|
86
|
+
process.on("uncaughtException", (error) => {
|
|
87
|
+
console.error(`[copilot_hub] uncaught exception: ${sanitizeError(error)}`);
|
|
88
|
+
void shutdown(1);
|
|
89
|
+
});
|
|
90
|
+
process.on("unhandledRejection", (reason) => {
|
|
91
|
+
console.error(`[copilot_hub] unhandled rejection: ${sanitizeError(reason)}`);
|
|
92
|
+
void shutdown(1);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function shutdown(exitCode) {
|
|
96
|
+
if (shuttingDown) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
shuttingDown = true;
|
|
100
|
+
try {
|
|
101
|
+
await runtime.shutdown();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`[copilot_hub] shutdown error: ${sanitizeError(error)}`);
|
|
105
|
+
}
|
|
106
|
+
process.exit(exitCode);
|
|
107
|
+
}
|
|
108
|
+
function normalizeThreadMode(value) {
|
|
109
|
+
const mode = String(value ?? "per_chat")
|
|
110
|
+
.trim()
|
|
111
|
+
.toLowerCase();
|
|
112
|
+
if (mode === "single" || mode === "per_chat") {
|
|
113
|
+
return mode;
|
|
114
|
+
}
|
|
115
|
+
return "per_chat";
|
|
116
|
+
}
|
|
117
|
+
function parseCsvSet(value) {
|
|
118
|
+
return new Set(String(value ?? "")
|
|
119
|
+
.split(",")
|
|
120
|
+
.map((entry) => entry.trim())
|
|
121
|
+
.filter(Boolean));
|
|
122
|
+
}
|
|
123
|
+
function parseBoolean(value) {
|
|
124
|
+
const normalized = String(value ?? "")
|
|
125
|
+
.trim()
|
|
126
|
+
.toLowerCase();
|
|
127
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
throw new Error("Invalid boolean value.");
|
|
134
|
+
}
|
|
135
|
+
function sanitizeError(error) {
|
|
136
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
137
|
+
return raw.split(/\r?\n/).slice(0, 12).join("\n");
|
|
138
|
+
}
|