copilot-hub 0.1.14 → 0.1.16
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/dist/agent-worker.js +28 -20
- package/apps/agent-engine/dist/config.js +1 -2
- package/apps/agent-engine/dist/index.js +146 -41
- package/apps/control-plane/dist/agent-worker.js +28 -20
- package/apps/control-plane/dist/channels/channel-factory.js +1 -2
- package/apps/control-plane/dist/channels/hub-model-utils.js +280 -0
- package/apps/control-plane/dist/channels/hub-ops-commands.js +859 -143
- package/apps/control-plane/dist/channels/telegram-channel.js +197 -53
- package/apps/control-plane/dist/channels/whatsapp-channel.js +6 -1
- package/apps/control-plane/dist/config.js +1 -2
- package/apps/control-plane/dist/copilot-hub.js +2 -3
- package/apps/control-plane/dist/index.js +41 -23
- package/apps/control-plane/dist/kernel/admin-contract.js +1 -2
- package/apps/control-plane/dist/test/hub-model-utils.test.js +170 -0
- package/package.json +4 -2
- package/packages/core/dist/agent-supervisor.d.ts +109 -28
- package/packages/core/dist/agent-supervisor.js +99 -45
- package/packages/core/dist/agent-supervisor.js.map +1 -1
- package/packages/core/dist/bot-manager.d.ts +86 -45
- package/packages/core/dist/bot-manager.js +17 -3
- package/packages/core/dist/bot-manager.js.map +1 -1
- package/packages/core/dist/bot-runtime.d.ts +225 -125
- package/packages/core/dist/bot-runtime.js +240 -52
- package/packages/core/dist/bot-runtime.js.map +1 -1
- package/packages/core/dist/bridge-service.d.ts +158 -31
- package/packages/core/dist/bridge-service.js +33 -22
- package/packages/core/dist/bridge-service.js.map +1 -1
- package/packages/core/dist/capability-manager.d.ts +60 -12
- package/packages/core/dist/capability-manager.js +74 -37
- package/packages/core/dist/capability-manager.js.map +1 -1
- package/packages/core/dist/channel-factory.d.ts +9 -4
- package/packages/core/dist/channel-factory.js +1 -2
- package/packages/core/dist/channel-factory.js.map +1 -1
- package/packages/core/dist/codex-app-client.d.ts +110 -43
- package/packages/core/dist/codex-app-client.js +182 -333
- package/packages/core/dist/codex-app-client.js.map +1 -1
- package/packages/core/dist/codex-app-events.d.ts +30 -0
- package/packages/core/dist/codex-app-events.js +266 -0
- package/packages/core/dist/codex-app-events.js.map +1 -0
- package/packages/core/dist/codex-app-utils.d.ts +28 -0
- package/packages/core/dist/codex-app-utils.js +164 -0
- package/packages/core/dist/codex-app-utils.js.map +1 -0
- package/packages/core/dist/codex-provider.d.ts +36 -27
- package/packages/core/dist/codex-provider.js +12 -11
- package/packages/core/dist/codex-provider.js.map +1 -1
- package/packages/core/dist/codex-quota-display.d.ts +12 -0
- package/packages/core/dist/codex-quota-display.js +56 -0
- package/packages/core/dist/codex-quota-display.js.map +1 -0
- package/packages/core/dist/extension-contract.d.ts +1 -1
- package/packages/core/dist/extension-contract.js +0 -1
- package/packages/core/dist/extension-contract.js.map +1 -1
- package/packages/core/dist/kernel-control-plane.d.ts +52 -12
- package/packages/core/dist/kernel-control-plane.js +95 -32
- package/packages/core/dist/kernel-control-plane.js.map +1 -1
- package/packages/core/dist/provider-factory.d.ts +20 -6
- package/packages/core/dist/provider-factory.js +20 -8
- package/packages/core/dist/provider-factory.js.map +1 -1
- package/packages/core/dist/telegram-channel.d.ts +103 -16
- package/packages/core/dist/telegram-channel.js +195 -59
- package/packages/core/dist/telegram-channel.js.map +1 -1
- package/packages/core/dist/whatsapp-channel.d.ts +21 -20
- package/packages/core/dist/whatsapp-channel.js +6 -1
- package/packages/core/dist/whatsapp-channel.js.map +1 -1
- package/packages/core/package.json +4 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { BotRuntime } from "@copilot-hub/core/bot-runtime";
|
|
3
2
|
const rawBotConfig = String(process.env.AGENT_BOT_CONFIG_JSON ?? "").trim();
|
|
4
3
|
const rawProviderDefaults = String(process.env.AGENT_PROVIDER_DEFAULTS_JSON ?? "").trim();
|
|
@@ -48,19 +47,20 @@ sendEvent("workerReady", {
|
|
|
48
47
|
name: runtime.name,
|
|
49
48
|
});
|
|
50
49
|
async function handleInboundMessage(message) {
|
|
51
|
-
const
|
|
50
|
+
const record = asRecord(message);
|
|
51
|
+
const type = String(record.type ?? "request").trim();
|
|
52
52
|
if (type === "kernelResponse") {
|
|
53
|
-
handleKernelResponse(
|
|
53
|
+
handleKernelResponse(record);
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
if (type !== "request") {
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
try {
|
|
60
|
-
await handleWorkerRequest(
|
|
60
|
+
await handleWorkerRequest(record);
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
|
-
const requestId =
|
|
63
|
+
const requestId = record.requestId ?? null;
|
|
64
64
|
if (requestId !== null && requestId !== undefined) {
|
|
65
65
|
sendResponse({
|
|
66
66
|
requestId,
|
|
@@ -73,9 +73,9 @@ async function handleInboundMessage(message) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
async function handleWorkerRequest(message) {
|
|
76
|
-
const requestId = message
|
|
77
|
-
const action = String(message
|
|
78
|
-
const payload = message
|
|
76
|
+
const requestId = message.requestId;
|
|
77
|
+
const action = String(message.action ?? "").trim();
|
|
78
|
+
const payload = asRecord(message.payload);
|
|
79
79
|
if (!requestId) {
|
|
80
80
|
throw new Error("requestId is required.");
|
|
81
81
|
}
|
|
@@ -101,27 +101,27 @@ async function handleWorkerRequest(message) {
|
|
|
101
101
|
break;
|
|
102
102
|
}
|
|
103
103
|
case "listPendingApprovals": {
|
|
104
|
-
const threadId = payload
|
|
104
|
+
const threadId = payload.threadId ? String(payload.threadId) : undefined;
|
|
105
105
|
result = await runtime.listPendingApprovals(threadId);
|
|
106
106
|
break;
|
|
107
107
|
}
|
|
108
108
|
case "resolvePendingApproval": {
|
|
109
109
|
result = await runtime.resolvePendingApproval({
|
|
110
|
-
threadId: String(payload
|
|
111
|
-
approvalId: String(payload
|
|
112
|
-
decision: String(payload
|
|
110
|
+
threadId: String(payload.threadId ?? ""),
|
|
111
|
+
approvalId: String(payload.approvalId ?? ""),
|
|
112
|
+
decision: String(payload.decision ?? ""),
|
|
113
113
|
});
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
116
116
|
case "reloadCapabilities": {
|
|
117
|
-
const capabilityDefinitions = Array.isArray(payload
|
|
117
|
+
const capabilityDefinitions = Array.isArray(payload.capabilityDefinitions)
|
|
118
118
|
? payload.capabilityDefinitions
|
|
119
119
|
: null;
|
|
120
120
|
result = await runtime.reloadCapabilities(capabilityDefinitions);
|
|
121
121
|
break;
|
|
122
122
|
}
|
|
123
123
|
case "setProjectRoot": {
|
|
124
|
-
const projectRoot = String(payload
|
|
124
|
+
const projectRoot = String(payload.projectRoot ?? "").trim();
|
|
125
125
|
if (!projectRoot) {
|
|
126
126
|
throw new Error("projectRoot is required.");
|
|
127
127
|
}
|
|
@@ -130,7 +130,7 @@ async function handleWorkerRequest(message) {
|
|
|
130
130
|
break;
|
|
131
131
|
}
|
|
132
132
|
case "setWebPublicBaseUrl": {
|
|
133
|
-
const value = String(payload
|
|
133
|
+
const value = String(payload.webPublicBaseUrl ?? "").trim();
|
|
134
134
|
if (!value) {
|
|
135
135
|
throw new Error("webPublicBaseUrl is required.");
|
|
136
136
|
}
|
|
@@ -184,7 +184,7 @@ function sendEvent(event, payload) {
|
|
|
184
184
|
payload,
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
|
-
function requestKernelAction({ action, payload, context }) {
|
|
187
|
+
function requestKernelAction({ action, payload, context, }) {
|
|
188
188
|
if (!process.send) {
|
|
189
189
|
return Promise.reject(new Error("Kernel IPC is unavailable."));
|
|
190
190
|
}
|
|
@@ -193,6 +193,11 @@ function requestKernelAction({ action, payload, context }) {
|
|
|
193
193
|
? kernelRequestTimeoutMs
|
|
194
194
|
: 20000;
|
|
195
195
|
return new Promise((resolve, reject) => {
|
|
196
|
+
const send = process.send;
|
|
197
|
+
if (!send) {
|
|
198
|
+
reject(new Error("Kernel IPC is unavailable."));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
196
201
|
const timer = setTimeout(() => {
|
|
197
202
|
pendingKernelRequests.delete(requestId);
|
|
198
203
|
reject(new Error(`Kernel request '${String(action ?? "")}' timed out after ${timeoutMs}ms.`));
|
|
@@ -203,7 +208,7 @@ function requestKernelAction({ action, payload, context }) {
|
|
|
203
208
|
timer,
|
|
204
209
|
});
|
|
205
210
|
try {
|
|
206
|
-
|
|
211
|
+
send({
|
|
207
212
|
type: "kernelRequest",
|
|
208
213
|
requestId,
|
|
209
214
|
action,
|
|
@@ -219,7 +224,7 @@ function requestKernelAction({ action, payload, context }) {
|
|
|
219
224
|
});
|
|
220
225
|
}
|
|
221
226
|
function handleKernelResponse(message) {
|
|
222
|
-
const requestId = String(message
|
|
227
|
+
const requestId = String(message.requestId ?? "").trim();
|
|
223
228
|
if (!requestId) {
|
|
224
229
|
return;
|
|
225
230
|
}
|
|
@@ -229,13 +234,16 @@ function handleKernelResponse(message) {
|
|
|
229
234
|
}
|
|
230
235
|
pendingKernelRequests.delete(requestId);
|
|
231
236
|
clearTimeout(pending.timer);
|
|
232
|
-
if (message
|
|
237
|
+
if (message.ok) {
|
|
233
238
|
pending.resolve(message.result);
|
|
234
239
|
return;
|
|
235
240
|
}
|
|
236
|
-
pending.reject(new Error(String(message
|
|
241
|
+
pending.reject(new Error(String(message.error ?? "Unknown kernel response error.")));
|
|
237
242
|
}
|
|
238
243
|
function sanitizeError(error) {
|
|
239
244
|
const raw = error instanceof Error ? error.message : String(error);
|
|
240
245
|
return raw.split(/\r?\n/).slice(0, 12).join("\n");
|
|
241
246
|
}
|
|
247
|
+
function asRecord(value) {
|
|
248
|
+
return value && typeof value === "object" ? value : {};
|
|
249
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import dotenv from "dotenv";
|
|
@@ -121,7 +120,7 @@ export const config = {
|
|
|
121
120
|
defaultAllowedChatIds,
|
|
122
121
|
};
|
|
123
122
|
function resolveCodexBin(rawValue) {
|
|
124
|
-
const value = (rawValue ?? "").trim();
|
|
123
|
+
const value = String(rawValue ?? "").trim();
|
|
125
124
|
const normalized = value.toLowerCase();
|
|
126
125
|
if (value && normalized !== "codex") {
|
|
127
126
|
return value;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { randomBytes } from "node:crypto";
|
|
4
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
import express from "express";
|
|
7
6
|
import { BotManager } from "@copilot-hub/core/bot-manager";
|
|
7
|
+
import { CodexAppClient } from "@copilot-hub/core/codex-app-client";
|
|
8
8
|
import { loadBotRegistry } from "@copilot-hub/core/bot-registry";
|
|
9
9
|
import { config } from "./config.js";
|
|
10
10
|
import { InstanceLock } from "@copilot-hub/core/instance-lock";
|
|
@@ -26,11 +26,13 @@ await bootstrap();
|
|
|
26
26
|
async function bootstrap() {
|
|
27
27
|
try {
|
|
28
28
|
if (config.instanceLockEnabled) {
|
|
29
|
-
|
|
30
|
-
await
|
|
29
|
+
const lock = new InstanceLock(config.instanceLockFilePath);
|
|
30
|
+
await lock.acquire();
|
|
31
|
+
instanceLock = lock;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
await
|
|
33
|
+
const kernelSecretStore = new KernelSecretStore(config.secretStoreFilePath);
|
|
34
|
+
await kernelSecretStore.init();
|
|
35
|
+
secretStore = kernelSecretStore;
|
|
34
36
|
const registry = await loadBotRegistry({
|
|
35
37
|
filePath: config.botRegistryFilePath,
|
|
36
38
|
dataDir: config.dataDir,
|
|
@@ -41,11 +43,12 @@ async function bootstrap() {
|
|
|
41
43
|
bootstrapTelegramToken: config.bootstrapTelegramToken,
|
|
42
44
|
defaultProviderKind: config.defaultProviderKind,
|
|
43
45
|
workspacePolicy: config.workspacePolicy,
|
|
44
|
-
resolveSecret: (name) =>
|
|
46
|
+
resolveSecret: (name) => kernelSecretStore.getSecret(name),
|
|
45
47
|
});
|
|
46
48
|
const runtimeBots = registry.bots.filter((bot) => bot.enabled !== false);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
const botDefinitions = runtimeBots;
|
|
50
|
+
const manager = new BotManager({
|
|
51
|
+
botDefinitions,
|
|
49
52
|
providerDefaults: config.providerDefaults,
|
|
50
53
|
turnActivityTimeoutMs: config.turnActivityTimeoutMs,
|
|
51
54
|
maxMessages: config.maxMessages,
|
|
@@ -57,10 +60,11 @@ async function bootstrap() {
|
|
|
57
60
|
heartbeatIntervalMs: config.agentHeartbeatIntervalMs,
|
|
58
61
|
heartbeatTimeoutMs: config.agentHeartbeatTimeoutMs,
|
|
59
62
|
});
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
botManager = manager;
|
|
64
|
+
const controlPlaneDeps = {
|
|
65
|
+
botManager: manager,
|
|
62
66
|
registryFilePath: registry.filePath,
|
|
63
|
-
secretStore,
|
|
67
|
+
secretStore: kernelSecretStore,
|
|
64
68
|
registryLoadOptions: {
|
|
65
69
|
dataDir: config.dataDir,
|
|
66
70
|
defaultWorkspaceRoot: config.defaultWorkspaceRoot,
|
|
@@ -70,13 +74,14 @@ async function bootstrap() {
|
|
|
70
74
|
bootstrapTelegramToken: config.bootstrapTelegramToken,
|
|
71
75
|
defaultProviderKind: config.defaultProviderKind,
|
|
72
76
|
workspacePolicy: config.workspacePolicy,
|
|
73
|
-
resolveSecret: (name) => secretStore.getSecret(name),
|
|
74
77
|
},
|
|
75
|
-
}
|
|
76
|
-
|
|
78
|
+
};
|
|
79
|
+
const kernelControlPlane = new KernelControlPlane(controlPlaneDeps);
|
|
80
|
+
controlPlane = kernelControlPlane;
|
|
81
|
+
manager.setKernelActionHandler((request) => kernelControlPlane.handleAgentAction(request));
|
|
77
82
|
const app = buildApiApp({
|
|
78
|
-
botManager,
|
|
79
|
-
controlPlane,
|
|
83
|
+
botManager: manager,
|
|
84
|
+
controlPlane: kernelControlPlane,
|
|
80
85
|
registryFilePath: registry.filePath,
|
|
81
86
|
});
|
|
82
87
|
const started = await startWebServer({
|
|
@@ -94,8 +99,8 @@ async function bootstrap() {
|
|
|
94
99
|
host: config.webHost,
|
|
95
100
|
port: activeWebPort,
|
|
96
101
|
});
|
|
97
|
-
|
|
98
|
-
await
|
|
102
|
+
manager.setWebPublicBaseUrl(runtimeWebPublicBaseUrl);
|
|
103
|
+
await manager.startAutoBots();
|
|
99
104
|
registerSignals();
|
|
100
105
|
console.log(`HTTP API listening on http://${config.webHost}:${activeWebPort}`);
|
|
101
106
|
console.log(`Bot registry loaded: ${registry.filePath}`);
|
|
@@ -110,7 +115,7 @@ async function bootstrap() {
|
|
|
110
115
|
process.exit(1);
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
|
-
function buildApiApp({ botManager, controlPlane, registryFilePath }) {
|
|
118
|
+
function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
114
119
|
const app = express();
|
|
115
120
|
app.use(express.json({ limit: "1mb" }));
|
|
116
121
|
app.use((req, res, next) => {
|
|
@@ -144,6 +149,21 @@ function buildApiApp({ botManager, controlPlane, registryFilePath }) {
|
|
|
144
149
|
deviceAuth,
|
|
145
150
|
});
|
|
146
151
|
}));
|
|
152
|
+
app.get("/api/system/codex/models", wrapAsync(async (req, res) => {
|
|
153
|
+
const modelsResult = await readCodexModelCatalog();
|
|
154
|
+
if (!modelsResult.ok) {
|
|
155
|
+
res.status(400).json({
|
|
156
|
+
error: modelsResult.error,
|
|
157
|
+
codexBin: modelsResult.codexBin,
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
res.json({
|
|
162
|
+
ok: true,
|
|
163
|
+
codexBin: modelsResult.codexBin,
|
|
164
|
+
models: modelsResult.models,
|
|
165
|
+
});
|
|
166
|
+
}));
|
|
147
167
|
app.post("/api/system/codex/device_auth/start", wrapAsync(async (req, res) => {
|
|
148
168
|
const session = startCodexDeviceAuthSession();
|
|
149
169
|
const ready = await waitForDeviceCode(session, 12_000);
|
|
@@ -247,6 +267,9 @@ function buildApiApp({ botManager, controlPlane, registryFilePath }) {
|
|
|
247
267
|
const approvalPolicy = String(req.body?.approvalPolicy ?? "")
|
|
248
268
|
.trim()
|
|
249
269
|
.toLowerCase();
|
|
270
|
+
const hasModel = Object.prototype.hasOwnProperty.call(req.body ?? {}, "model");
|
|
271
|
+
const rawModel = req.body?.model;
|
|
272
|
+
const model = rawModel === null || rawModel === undefined ? null : String(rawModel).trim();
|
|
250
273
|
if (!sandboxMode) {
|
|
251
274
|
res.status(400).json({ error: "Field 'sandboxMode' is required." });
|
|
252
275
|
return;
|
|
@@ -255,11 +278,15 @@ function buildApiApp({ botManager, controlPlane, registryFilePath }) {
|
|
|
255
278
|
res.status(400).json({ error: "Field 'approvalPolicy' is required." });
|
|
256
279
|
return;
|
|
257
280
|
}
|
|
258
|
-
const
|
|
281
|
+
const payload = {
|
|
259
282
|
botId,
|
|
260
283
|
sandboxMode,
|
|
261
284
|
approvalPolicy,
|
|
262
|
-
}
|
|
285
|
+
};
|
|
286
|
+
if (hasModel) {
|
|
287
|
+
payload.model = model;
|
|
288
|
+
}
|
|
289
|
+
const result = await controlPlane.runSystemAction(CONTROL_ACTIONS.BOTS_SET_POLICY, payload);
|
|
263
290
|
res.json(result);
|
|
264
291
|
}));
|
|
265
292
|
app.get("/api/projects", wrapAsync(async (req, res) => {
|
|
@@ -355,17 +382,17 @@ function registerSignals() {
|
|
|
355
382
|
}
|
|
356
383
|
function wrapAsync(handler) {
|
|
357
384
|
return (req, res, next) => {
|
|
358
|
-
Promise.resolve(handler(req, res, next)).catch(next);
|
|
385
|
+
void Promise.resolve(handler(req, res, next)).catch(next);
|
|
359
386
|
};
|
|
360
387
|
}
|
|
361
|
-
function resolveRuntimeWebPublicBaseUrl({ explicit, configuredBaseUrl, host, port }) {
|
|
388
|
+
function resolveRuntimeWebPublicBaseUrl({ explicit, configuredBaseUrl, host, port, }) {
|
|
362
389
|
if (explicit) {
|
|
363
390
|
return configuredBaseUrl;
|
|
364
391
|
}
|
|
365
392
|
const exposedHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
366
393
|
return `http://${exposedHost}:${port}`;
|
|
367
394
|
}
|
|
368
|
-
async function startWebServer({ app, host, basePort, autoIncrement, maxAttempts }) {
|
|
395
|
+
async function startWebServer({ app, host, basePort, autoIncrement, maxAttempts, }) {
|
|
369
396
|
let port = basePort;
|
|
370
397
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
371
398
|
try {
|
|
@@ -373,7 +400,7 @@ async function startWebServer({ app, host, basePort, autoIncrement, maxAttempts
|
|
|
373
400
|
return { server: startedServer, port };
|
|
374
401
|
}
|
|
375
402
|
catch (error) {
|
|
376
|
-
const occupied = error &&
|
|
403
|
+
const occupied = isErrnoException(error) && error.code === "EADDRINUSE";
|
|
377
404
|
if (!occupied || !autoIncrement || port >= 65535) {
|
|
378
405
|
throw error;
|
|
379
406
|
}
|
|
@@ -382,7 +409,7 @@ async function startWebServer({ app, host, basePort, autoIncrement, maxAttempts
|
|
|
382
409
|
}
|
|
383
410
|
throw new Error(`Could not find a free web port after ${maxAttempts} attempts starting from ${basePort}.`);
|
|
384
411
|
}
|
|
385
|
-
function listenOnce({ app, host, port }) {
|
|
412
|
+
function listenOnce({ app, host, port, }) {
|
|
386
413
|
return new Promise((resolve, reject) => {
|
|
387
414
|
const candidate = app.listen(port, host);
|
|
388
415
|
candidate.once("listening", () => resolve(candidate));
|
|
@@ -401,10 +428,12 @@ async function cleanupBeforeExit() {
|
|
|
401
428
|
if (botManager) {
|
|
402
429
|
await botManager.shutdownAll();
|
|
403
430
|
}
|
|
404
|
-
|
|
431
|
+
const activeServer = server;
|
|
432
|
+
if (activeServer) {
|
|
405
433
|
await new Promise((resolve) => {
|
|
406
|
-
|
|
434
|
+
activeServer.close(() => resolve());
|
|
407
435
|
});
|
|
436
|
+
server = null;
|
|
408
437
|
}
|
|
409
438
|
if (instanceLock) {
|
|
410
439
|
await instanceLock.release();
|
|
@@ -415,7 +444,8 @@ function sanitizeError(error) {
|
|
|
415
444
|
return raw.split(/\r?\n/).slice(0, 12).join("\n");
|
|
416
445
|
}
|
|
417
446
|
function parseDeleteModeFromRequest(body) {
|
|
418
|
-
const
|
|
447
|
+
const payload = isRecord(body) ? body : {};
|
|
448
|
+
const value = String(payload.deleteMode ?? "")
|
|
419
449
|
.trim()
|
|
420
450
|
.toLowerCase();
|
|
421
451
|
if (value === "soft" || value === "purge_data" || value === "purge_all") {
|
|
@@ -508,9 +538,9 @@ function getCodexDeviceAuthSnapshot() {
|
|
|
508
538
|
return {
|
|
509
539
|
status: session.status,
|
|
510
540
|
startedAt: session.startedAt,
|
|
511
|
-
loginUrl: session.loginUrl
|
|
512
|
-
code: session.code
|
|
513
|
-
detail: session.error
|
|
541
|
+
...(session.loginUrl ? { loginUrl: session.loginUrl } : {}),
|
|
542
|
+
...(session.code ? { code: session.code } : {}),
|
|
543
|
+
...(session.error ? { detail: session.error } : {}),
|
|
514
544
|
restartedBots: session.restartedBots,
|
|
515
545
|
restartFailures: session.restartFailures,
|
|
516
546
|
};
|
|
@@ -611,6 +641,62 @@ function readCodexLoginStatus() {
|
|
|
611
641
|
detail: firstNonEmptyLine(status.errorMessage, status.stderr, status.stdout),
|
|
612
642
|
};
|
|
613
643
|
}
|
|
644
|
+
async function readCodexModelCatalog() {
|
|
645
|
+
const codexBin = String(config.codexBin ?? "codex").trim() || "codex";
|
|
646
|
+
const client = new CodexAppClient({
|
|
647
|
+
codexBin,
|
|
648
|
+
codexHomeDir: config.codexHomeDir ?? null,
|
|
649
|
+
cwd: config.kernelRootPath,
|
|
650
|
+
sandboxMode: config.codexSandbox,
|
|
651
|
+
approvalPolicy: config.codexApprovalPolicy,
|
|
652
|
+
model: null,
|
|
653
|
+
turnActivityTimeoutMs: Math.min(config.turnActivityTimeoutMs, 60_000),
|
|
654
|
+
});
|
|
655
|
+
try {
|
|
656
|
+
const models = await client.listModels({ limit: 200 });
|
|
657
|
+
return {
|
|
658
|
+
ok: true,
|
|
659
|
+
codexBin,
|
|
660
|
+
models: normalizeModelCatalog(models),
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
return {
|
|
665
|
+
ok: false,
|
|
666
|
+
codexBin,
|
|
667
|
+
error: sanitizeError(error) || "Could not load model catalog from Codex.",
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
finally {
|
|
671
|
+
await client.shutdown().catch(() => {
|
|
672
|
+
// Best effort only.
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function normalizeModelCatalog(rawModels) {
|
|
677
|
+
const seen = new Set();
|
|
678
|
+
const normalized = [];
|
|
679
|
+
for (const entry of Array.isArray(rawModels) ? rawModels : []) {
|
|
680
|
+
const model = String(entry?.model ?? entry?.id ?? "").trim();
|
|
681
|
+
if (!model || seen.has(model)) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
seen.add(model);
|
|
685
|
+
normalized.push({
|
|
686
|
+
id: String(entry?.id ?? model).trim() || model,
|
|
687
|
+
model,
|
|
688
|
+
displayName: String(entry?.displayName ?? model).trim() || model,
|
|
689
|
+
description: String(entry?.description ?? "").trim(),
|
|
690
|
+
isDefault: entry?.isDefault === true,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return normalized.sort((a, b) => {
|
|
694
|
+
if (a.isDefault !== b.isDefault) {
|
|
695
|
+
return a.isDefault ? -1 : 1;
|
|
696
|
+
}
|
|
697
|
+
return a.displayName.localeCompare(b.displayName);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
614
700
|
function looksLikeCodexApiKey(value) {
|
|
615
701
|
const key = String(value ?? "").trim();
|
|
616
702
|
if (key.length < 20 || key.length > 4096) {
|
|
@@ -641,7 +727,7 @@ function runCodexCommand(args, { inputText = "" } = {}) {
|
|
|
641
727
|
errorMessage: formatCodexSpawnError(codexBin, result.error),
|
|
642
728
|
};
|
|
643
729
|
}
|
|
644
|
-
const status =
|
|
730
|
+
const status = typeof result.status === "number" ? result.status : 1;
|
|
645
731
|
return {
|
|
646
732
|
ok: status === 0,
|
|
647
733
|
status,
|
|
@@ -651,7 +737,7 @@ function runCodexCommand(args, { inputText = "" } = {}) {
|
|
|
651
737
|
};
|
|
652
738
|
}
|
|
653
739
|
function formatCodexSpawnError(codexBin, error) {
|
|
654
|
-
const code = String(error
|
|
740
|
+
const code = String(isErrnoException(error) ? (error.code ?? "") : "")
|
|
655
741
|
.trim()
|
|
656
742
|
.toUpperCase();
|
|
657
743
|
if (code === "ENOENT") {
|
|
@@ -683,18 +769,37 @@ function redactSecret(text, secret) {
|
|
|
683
769
|
}
|
|
684
770
|
return input.split(token).join("[redacted]");
|
|
685
771
|
}
|
|
772
|
+
function requireBotManager() {
|
|
773
|
+
if (!botManager) {
|
|
774
|
+
throw new Error("Bot manager is not initialized.");
|
|
775
|
+
}
|
|
776
|
+
return botManager;
|
|
777
|
+
}
|
|
778
|
+
function isRecord(value) {
|
|
779
|
+
return value !== null && typeof value === "object";
|
|
780
|
+
}
|
|
781
|
+
function isErrnoException(error) {
|
|
782
|
+
return isRecord(error);
|
|
783
|
+
}
|
|
686
784
|
async function restartRunningBots() {
|
|
687
|
-
const
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
785
|
+
const manager = requireBotManager();
|
|
786
|
+
const statuses = await manager.listBotsLive();
|
|
787
|
+
const runningBotIds = [];
|
|
788
|
+
for (const entry of statuses) {
|
|
789
|
+
if (!isRecord(entry) || entry.running !== true) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const botId = String(entry.id ?? "").trim();
|
|
793
|
+
if (botId) {
|
|
794
|
+
runningBotIds.push(botId);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
692
797
|
const restartedBotIds = [];
|
|
693
798
|
const failures = [];
|
|
694
799
|
for (const botId of runningBotIds) {
|
|
695
800
|
try {
|
|
696
|
-
await
|
|
697
|
-
await
|
|
801
|
+
await manager.stopBot(botId);
|
|
802
|
+
await manager.startBot(botId);
|
|
698
803
|
restartedBotIds.push(botId);
|
|
699
804
|
}
|
|
700
805
|
catch (error) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { BotRuntime } from "@copilot-hub/core/bot-runtime";
|
|
3
2
|
import { createChannelAdapter } from "./channels/channel-factory.js";
|
|
4
3
|
const rawBotConfig = String(process.env.AGENT_BOT_CONFIG_JSON ?? "").trim();
|
|
@@ -50,19 +49,20 @@ sendEvent("workerReady", {
|
|
|
50
49
|
name: runtime.name,
|
|
51
50
|
});
|
|
52
51
|
async function handleInboundMessage(message) {
|
|
53
|
-
const
|
|
52
|
+
const record = asRecord(message);
|
|
53
|
+
const type = String(record.type ?? "request").trim();
|
|
54
54
|
if (type === "kernelResponse") {
|
|
55
|
-
handleKernelResponse(
|
|
55
|
+
handleKernelResponse(record);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
if (type !== "request") {
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
try {
|
|
62
|
-
await handleWorkerRequest(
|
|
62
|
+
await handleWorkerRequest(record);
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
65
|
-
const requestId =
|
|
65
|
+
const requestId = record.requestId ?? null;
|
|
66
66
|
if (requestId !== null && requestId !== undefined) {
|
|
67
67
|
sendResponse({
|
|
68
68
|
requestId,
|
|
@@ -75,9 +75,9 @@ async function handleInboundMessage(message) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
async function handleWorkerRequest(message) {
|
|
78
|
-
const requestId = message
|
|
79
|
-
const action = String(message
|
|
80
|
-
const payload = message
|
|
78
|
+
const requestId = message.requestId;
|
|
79
|
+
const action = String(message.action ?? "").trim();
|
|
80
|
+
const payload = asRecord(message.payload);
|
|
81
81
|
if (!requestId) {
|
|
82
82
|
throw new Error("requestId is required.");
|
|
83
83
|
}
|
|
@@ -103,27 +103,27 @@ async function handleWorkerRequest(message) {
|
|
|
103
103
|
break;
|
|
104
104
|
}
|
|
105
105
|
case "listPendingApprovals": {
|
|
106
|
-
const threadId = payload
|
|
106
|
+
const threadId = payload.threadId ? String(payload.threadId) : undefined;
|
|
107
107
|
result = await runtime.listPendingApprovals(threadId);
|
|
108
108
|
break;
|
|
109
109
|
}
|
|
110
110
|
case "resolvePendingApproval": {
|
|
111
111
|
result = await runtime.resolvePendingApproval({
|
|
112
|
-
threadId: String(payload
|
|
113
|
-
approvalId: String(payload
|
|
114
|
-
decision: String(payload
|
|
112
|
+
threadId: String(payload.threadId ?? ""),
|
|
113
|
+
approvalId: String(payload.approvalId ?? ""),
|
|
114
|
+
decision: String(payload.decision ?? ""),
|
|
115
115
|
});
|
|
116
116
|
break;
|
|
117
117
|
}
|
|
118
118
|
case "reloadCapabilities": {
|
|
119
|
-
const capabilityDefinitions = Array.isArray(payload
|
|
119
|
+
const capabilityDefinitions = Array.isArray(payload.capabilityDefinitions)
|
|
120
120
|
? payload.capabilityDefinitions
|
|
121
121
|
: null;
|
|
122
122
|
result = await runtime.reloadCapabilities(capabilityDefinitions);
|
|
123
123
|
break;
|
|
124
124
|
}
|
|
125
125
|
case "setProjectRoot": {
|
|
126
|
-
const projectRoot = String(payload
|
|
126
|
+
const projectRoot = String(payload.projectRoot ?? "").trim();
|
|
127
127
|
if (!projectRoot) {
|
|
128
128
|
throw new Error("projectRoot is required.");
|
|
129
129
|
}
|
|
@@ -132,7 +132,7 @@ async function handleWorkerRequest(message) {
|
|
|
132
132
|
break;
|
|
133
133
|
}
|
|
134
134
|
case "setWebPublicBaseUrl": {
|
|
135
|
-
const value = String(payload
|
|
135
|
+
const value = String(payload.webPublicBaseUrl ?? "").trim();
|
|
136
136
|
if (!value) {
|
|
137
137
|
throw new Error("webPublicBaseUrl is required.");
|
|
138
138
|
}
|
|
@@ -186,7 +186,7 @@ function sendEvent(event, payload) {
|
|
|
186
186
|
payload,
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
|
-
function requestKernelAction({ action, payload, context }) {
|
|
189
|
+
function requestKernelAction({ action, payload, context, }) {
|
|
190
190
|
if (!process.send) {
|
|
191
191
|
return Promise.reject(new Error("Kernel IPC is unavailable."));
|
|
192
192
|
}
|
|
@@ -195,6 +195,11 @@ function requestKernelAction({ action, payload, context }) {
|
|
|
195
195
|
? kernelRequestTimeoutMs
|
|
196
196
|
: 20000;
|
|
197
197
|
return new Promise((resolve, reject) => {
|
|
198
|
+
const send = process.send;
|
|
199
|
+
if (!send) {
|
|
200
|
+
reject(new Error("Kernel IPC is unavailable."));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
198
203
|
const timer = setTimeout(() => {
|
|
199
204
|
pendingKernelRequests.delete(requestId);
|
|
200
205
|
reject(new Error(`Kernel request '${String(action ?? "")}' timed out after ${timeoutMs}ms.`));
|
|
@@ -205,7 +210,7 @@ function requestKernelAction({ action, payload, context }) {
|
|
|
205
210
|
timer,
|
|
206
211
|
});
|
|
207
212
|
try {
|
|
208
|
-
|
|
213
|
+
send({
|
|
209
214
|
type: "kernelRequest",
|
|
210
215
|
requestId,
|
|
211
216
|
action,
|
|
@@ -221,7 +226,7 @@ function requestKernelAction({ action, payload, context }) {
|
|
|
221
226
|
});
|
|
222
227
|
}
|
|
223
228
|
function handleKernelResponse(message) {
|
|
224
|
-
const requestId = String(message
|
|
229
|
+
const requestId = String(message.requestId ?? "").trim();
|
|
225
230
|
if (!requestId) {
|
|
226
231
|
return;
|
|
227
232
|
}
|
|
@@ -231,13 +236,16 @@ function handleKernelResponse(message) {
|
|
|
231
236
|
}
|
|
232
237
|
pendingKernelRequests.delete(requestId);
|
|
233
238
|
clearTimeout(pending.timer);
|
|
234
|
-
if (message
|
|
239
|
+
if (message.ok) {
|
|
235
240
|
pending.resolve(message.result);
|
|
236
241
|
return;
|
|
237
242
|
}
|
|
238
|
-
pending.reject(new Error(String(message
|
|
243
|
+
pending.reject(new Error(String(message.error ?? "Unknown kernel response error.")));
|
|
239
244
|
}
|
|
240
245
|
function sanitizeError(error) {
|
|
241
246
|
const raw = error instanceof Error ? error.message : String(error);
|
|
242
247
|
return raw.split(/\r?\n/).slice(0, 12).join("\n");
|
|
243
248
|
}
|
|
249
|
+
function asRecord(value) {
|
|
250
|
+
return value && typeof value === "object" ? value : {};
|
|
251
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { TelegramChannel } from "./telegram-channel.js";
|
|
3
2
|
import { WhatsAppChannel } from "./whatsapp-channel.js";
|
|
4
|
-
export function createChannelAdapter({ channelConfig, runtime }) {
|
|
3
|
+
export function createChannelAdapter({ channelConfig, runtime, }) {
|
|
5
4
|
const kind = String(channelConfig?.kind ?? "")
|
|
6
5
|
.trim()
|
|
7
6
|
.toLowerCase();
|