copilot-hub 0.1.13 → 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.
Files changed (66) hide show
  1. package/apps/agent-engine/dist/agent-worker.js +28 -20
  2. package/apps/agent-engine/dist/config.js +1 -2
  3. package/apps/agent-engine/dist/index.js +146 -41
  4. package/apps/control-plane/dist/agent-worker.js +28 -20
  5. package/apps/control-plane/dist/channels/channel-factory.js +1 -2
  6. package/apps/control-plane/dist/channels/hub-model-utils.js +280 -0
  7. package/apps/control-plane/dist/channels/hub-ops-commands.js +859 -143
  8. package/apps/control-plane/dist/channels/telegram-channel.js +197 -53
  9. package/apps/control-plane/dist/channels/whatsapp-channel.js +6 -1
  10. package/apps/control-plane/dist/config.js +1 -2
  11. package/apps/control-plane/dist/copilot-hub.js +2 -3
  12. package/apps/control-plane/dist/index.js +41 -23
  13. package/apps/control-plane/dist/kernel/admin-contract.js +1 -2
  14. package/apps/control-plane/dist/test/hub-model-utils.test.js +170 -0
  15. package/package.json +4 -2
  16. package/packages/core/dist/agent-supervisor.d.ts +109 -28
  17. package/packages/core/dist/agent-supervisor.js +99 -45
  18. package/packages/core/dist/agent-supervisor.js.map +1 -1
  19. package/packages/core/dist/bot-manager.d.ts +86 -45
  20. package/packages/core/dist/bot-manager.js +17 -3
  21. package/packages/core/dist/bot-manager.js.map +1 -1
  22. package/packages/core/dist/bot-runtime.d.ts +225 -125
  23. package/packages/core/dist/bot-runtime.js +240 -52
  24. package/packages/core/dist/bot-runtime.js.map +1 -1
  25. package/packages/core/dist/bridge-service.d.ts +158 -31
  26. package/packages/core/dist/bridge-service.js +33 -22
  27. package/packages/core/dist/bridge-service.js.map +1 -1
  28. package/packages/core/dist/capability-manager.d.ts +60 -12
  29. package/packages/core/dist/capability-manager.js +74 -37
  30. package/packages/core/dist/capability-manager.js.map +1 -1
  31. package/packages/core/dist/channel-factory.d.ts +9 -4
  32. package/packages/core/dist/channel-factory.js +1 -2
  33. package/packages/core/dist/channel-factory.js.map +1 -1
  34. package/packages/core/dist/codex-app-client.d.ts +110 -43
  35. package/packages/core/dist/codex-app-client.js +182 -333
  36. package/packages/core/dist/codex-app-client.js.map +1 -1
  37. package/packages/core/dist/codex-app-events.d.ts +30 -0
  38. package/packages/core/dist/codex-app-events.js +266 -0
  39. package/packages/core/dist/codex-app-events.js.map +1 -0
  40. package/packages/core/dist/codex-app-utils.d.ts +28 -0
  41. package/packages/core/dist/codex-app-utils.js +164 -0
  42. package/packages/core/dist/codex-app-utils.js.map +1 -0
  43. package/packages/core/dist/codex-provider.d.ts +36 -27
  44. package/packages/core/dist/codex-provider.js +12 -11
  45. package/packages/core/dist/codex-provider.js.map +1 -1
  46. package/packages/core/dist/codex-quota-display.d.ts +12 -0
  47. package/packages/core/dist/codex-quota-display.js +56 -0
  48. package/packages/core/dist/codex-quota-display.js.map +1 -0
  49. package/packages/core/dist/extension-contract.d.ts +1 -1
  50. package/packages/core/dist/extension-contract.js +0 -1
  51. package/packages/core/dist/extension-contract.js.map +1 -1
  52. package/packages/core/dist/kernel-control-plane.d.ts +52 -12
  53. package/packages/core/dist/kernel-control-plane.js +95 -32
  54. package/packages/core/dist/kernel-control-plane.js.map +1 -1
  55. package/packages/core/dist/provider-factory.d.ts +20 -6
  56. package/packages/core/dist/provider-factory.js +20 -8
  57. package/packages/core/dist/provider-factory.js.map +1 -1
  58. package/packages/core/dist/telegram-channel.d.ts +103 -16
  59. package/packages/core/dist/telegram-channel.js +195 -59
  60. package/packages/core/dist/telegram-channel.js.map +1 -1
  61. package/packages/core/dist/whatsapp-channel.d.ts +21 -20
  62. package/packages/core/dist/whatsapp-channel.js +6 -1
  63. package/packages/core/dist/whatsapp-channel.js.map +1 -1
  64. package/packages/core/package.json +4 -0
  65. package/scripts/dist/daemon.mjs +41 -2
  66. package/scripts/src/daemon.mts +45 -2
@@ -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 type = String(message?.type ?? "request").trim();
50
+ const record = asRecord(message);
51
+ const type = String(record.type ?? "request").trim();
52
52
  if (type === "kernelResponse") {
53
- handleKernelResponse(message);
53
+ handleKernelResponse(record);
54
54
  return;
55
55
  }
56
56
  if (type !== "request") {
57
57
  return;
58
58
  }
59
59
  try {
60
- await handleWorkerRequest(message);
60
+ await handleWorkerRequest(record);
61
61
  }
62
62
  catch (error) {
63
- const requestId = message?.requestId ?? null;
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?.requestId;
77
- const action = String(message?.action ?? "").trim();
78
- const payload = message?.payload ?? {};
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?.threadId ? String(payload.threadId) : undefined;
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?.threadId ?? ""),
111
- approvalId: String(payload?.approvalId ?? ""),
112
- decision: String(payload?.decision ?? ""),
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?.capabilityDefinitions)
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?.projectRoot ?? "").trim();
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?.webPublicBaseUrl ?? "").trim();
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
- process.send({
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?.requestId ?? "").trim();
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?.ok) {
237
+ if (message.ok) {
233
238
  pending.resolve(message.result);
234
239
  return;
235
240
  }
236
- pending.reject(new Error(String(message?.error ?? "Unknown kernel response error.")));
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
- instanceLock = new InstanceLock(config.instanceLockFilePath);
30
- await instanceLock.acquire();
29
+ const lock = new InstanceLock(config.instanceLockFilePath);
30
+ await lock.acquire();
31
+ instanceLock = lock;
31
32
  }
32
- secretStore = new KernelSecretStore(config.secretStoreFilePath);
33
- await secretStore.init();
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) => secretStore.getSecret(name),
46
+ resolveSecret: (name) => kernelSecretStore.getSecret(name),
45
47
  });
46
48
  const runtimeBots = registry.bots.filter((bot) => bot.enabled !== false);
47
- botManager = new BotManager({
48
- botDefinitions: runtimeBots,
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
- controlPlane = new KernelControlPlane({
61
- botManager,
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
- botManager.setKernelActionHandler((request) => controlPlane.handleAgentAction(request));
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
- botManager.setWebPublicBaseUrl(runtimeWebPublicBaseUrl);
98
- await botManager.startAutoBots();
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 result = await controlPlane.runSystemAction(CONTROL_ACTIONS.BOTS_SET_POLICY, {
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 && typeof error === "object" && error.code === "EADDRINUSE";
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
- if (server) {
431
+ const activeServer = server;
432
+ if (activeServer) {
405
433
  await new Promise((resolve) => {
406
- server.close(() => resolve());
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 value = String(body?.deleteMode ?? "")
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 || undefined,
512
- code: session.code || undefined,
513
- detail: session.error || undefined,
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 = Number.isInteger(result.status) ? result.status : 1;
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?.code ?? "")
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 statuses = await botManager.listBotsLive();
688
- const runningBotIds = statuses
689
- .filter((entry) => entry?.running === true)
690
- .map((entry) => String(entry?.id ?? "").trim())
691
- .filter(Boolean);
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 botManager.stopBot(botId);
697
- await botManager.startBot(botId);
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 type = String(message?.type ?? "request").trim();
52
+ const record = asRecord(message);
53
+ const type = String(record.type ?? "request").trim();
54
54
  if (type === "kernelResponse") {
55
- handleKernelResponse(message);
55
+ handleKernelResponse(record);
56
56
  return;
57
57
  }
58
58
  if (type !== "request") {
59
59
  return;
60
60
  }
61
61
  try {
62
- await handleWorkerRequest(message);
62
+ await handleWorkerRequest(record);
63
63
  }
64
64
  catch (error) {
65
- const requestId = message?.requestId ?? null;
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?.requestId;
79
- const action = String(message?.action ?? "").trim();
80
- const payload = message?.payload ?? {};
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?.threadId ? String(payload.threadId) : undefined;
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?.threadId ?? ""),
113
- approvalId: String(payload?.approvalId ?? ""),
114
- decision: String(payload?.decision ?? ""),
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?.capabilityDefinitions)
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?.projectRoot ?? "").trim();
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?.webPublicBaseUrl ?? "").trim();
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
- process.send({
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?.requestId ?? "").trim();
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?.ok) {
239
+ if (message.ok) {
235
240
  pending.resolve(message.result);
236
241
  return;
237
242
  }
238
- pending.reject(new Error(String(message?.error ?? "Unknown kernel response error.")));
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();