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
@@ -0,0 +1,170 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ const BOT_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
4
+ let utilsPromise = null;
5
+ async function loadUtils() {
6
+ if (!utilsPromise) {
7
+ const specifier = ["..", "channels", "hub-model-utils.js"].join("/");
8
+ utilsPromise = import(specifier);
9
+ }
10
+ return utilsPromise;
11
+ }
12
+ test("parseSetModelCommand accepts explicit model and auto keyword", async () => {
13
+ const { parseSetModelCommand } = await loadUtils();
14
+ const modelResult = parseSetModelCommand("/set_model agent_a gpt-5.3-codex", BOT_ID_PATTERN);
15
+ assert.equal(modelResult.ok, true);
16
+ assert.equal(modelResult.botId, "agent_a");
17
+ assert.equal(modelResult.model, "gpt-5.3-codex");
18
+ const autoResult = parseSetModelCommand("/set_model agent_a auto", BOT_ID_PATTERN);
19
+ assert.equal(autoResult.ok, true);
20
+ assert.equal(autoResult.botId, "agent_a");
21
+ assert.equal(autoResult.model, null);
22
+ });
23
+ test("parseSetModelAllCommand accepts explicit model and auto keyword", async () => {
24
+ const { parseSetModelAllCommand } = await loadUtils();
25
+ const modelResult = parseSetModelAllCommand("/set_model_all gpt-5.3-codex");
26
+ assert.equal(modelResult.ok, true);
27
+ assert.equal(modelResult.model, "gpt-5.3-codex");
28
+ const autoResult = parseSetModelAllCommand("/set_model_all auto");
29
+ assert.equal(autoResult.ok, true);
30
+ assert.equal(autoResult.model, null);
31
+ });
32
+ test("parseSetModelCommand rejects invalid command input", async () => {
33
+ const { parseSetModelAllCommand, parseSetModelCommand } = await loadUtils();
34
+ const invalidId = parseSetModelCommand("/set_model bad*id gpt-5", BOT_ID_PATTERN);
35
+ assert.equal(invalidId.ok, false);
36
+ const invalidModel = parseSetModelCommand("/set_model agent_a bad/model", BOT_ID_PATTERN);
37
+ assert.equal(invalidModel.ok, false);
38
+ const missingModel = parseSetModelAllCommand("/set_model_all");
39
+ assert.equal(missingModel.ok, false);
40
+ const invalidGlobalModel = parseSetModelAllCommand("/set_model_all bad/model");
41
+ assert.equal(invalidGlobalModel.ok, false);
42
+ });
43
+ test("buildSessionModelOptions de-duplicates and marks selected model", async () => {
44
+ const { buildSessionModelOptions } = await loadUtils();
45
+ const options = buildSessionModelOptions({
46
+ currentModel: "gpt-5",
47
+ catalog: [
48
+ { model: "gpt-4.1", displayName: "GPT-4.1" },
49
+ { model: "gpt-5", displayName: "GPT-5" },
50
+ { model: "gpt-5", displayName: "GPT-5 duplicate" },
51
+ ],
52
+ inlineMax: 10,
53
+ });
54
+ assert.equal(options.length, 2);
55
+ assert.equal(options[0].model, "gpt-5");
56
+ assert.equal(options[0].selected, true);
57
+ assert.equal(options[1].model, "gpt-4.1");
58
+ assert.equal(options[1].selected, false);
59
+ });
60
+ test("resolveModelSelectionFromAction resolves auto and keyed entries", async () => {
61
+ const { resolveModelSelectionFromAction } = await loadUtils();
62
+ const session = {
63
+ modelOptions: [
64
+ { key: "k0", model: "gpt-5", label: "GPT-5" },
65
+ { key: "k1", model: "gpt-4.1", label: "GPT-4.1" },
66
+ ],
67
+ };
68
+ const autoSelection = resolveModelSelectionFromAction({ session, profileId: "auto" });
69
+ assert.equal(autoSelection.ok, true);
70
+ assert.equal(autoSelection.model, null);
71
+ const explicitSelection = resolveModelSelectionFromAction({ session, profileId: "k1" });
72
+ assert.equal(explicitSelection.ok, true);
73
+ assert.equal(explicitSelection.model, "gpt-4.1");
74
+ assert.equal(explicitSelection.label, "GPT-4.1");
75
+ });
76
+ test("fetchCodexModelOptions normalizes model catalog from API", async () => {
77
+ const { fetchCodexModelOptions } = await loadUtils();
78
+ const result = await fetchCodexModelOptions(async () => ({
79
+ models: [
80
+ { id: "model-a", model: "gpt-5", displayName: "GPT-5", isDefault: true },
81
+ { id: "model-b", model: "gpt-5", displayName: "Duplicate" },
82
+ { id: "model-c", model: "gpt-4.1", displayName: "GPT-4.1" },
83
+ ],
84
+ }));
85
+ assert.equal(result.available, true);
86
+ assert.equal(result.models.length, 2);
87
+ assert.deepEqual(result.models[0], {
88
+ model: "gpt-5",
89
+ displayName: "GPT-5",
90
+ isDefault: true,
91
+ });
92
+ });
93
+ test("policy resolvers keep safe defaults", async () => {
94
+ const { applyBotModelPolicy, applyModelPolicyToBots, getBotPolicyState, resolveApprovalPolicy, resolveSandboxMode, } = await loadUtils();
95
+ assert.equal(resolveSandboxMode("read-only"), "read-only");
96
+ assert.equal(resolveSandboxMode("invalid"), "danger-full-access");
97
+ assert.equal(resolveApprovalPolicy("on-request"), "on-request");
98
+ assert.equal(resolveApprovalPolicy("invalid"), "never");
99
+ assert.deepEqual(getBotPolicyState({
100
+ provider: {
101
+ options: {
102
+ sandboxMode: "workspace-write",
103
+ approvalPolicy: "on-failure",
104
+ },
105
+ },
106
+ }), {
107
+ sandboxMode: "workspace-write",
108
+ approvalPolicy: "on-failure",
109
+ });
110
+ let capturedPath = "";
111
+ let capturedPayload = null;
112
+ await applyBotModelPolicy({
113
+ apiPost: async (path, payload) => {
114
+ capturedPath = path;
115
+ capturedPayload = payload;
116
+ return { ok: true };
117
+ },
118
+ botId: "worker-a",
119
+ botState: {
120
+ provider: {
121
+ options: {
122
+ sandboxMode: "read-only",
123
+ approvalPolicy: "on-request",
124
+ },
125
+ },
126
+ },
127
+ model: "gpt-5",
128
+ });
129
+ assert.equal(capturedPath, "/api/bots/worker-a/policy");
130
+ assert.deepEqual(capturedPayload, {
131
+ sandboxMode: "read-only",
132
+ approvalPolicy: "on-request",
133
+ model: "gpt-5",
134
+ });
135
+ const posted = [];
136
+ const batchResult = await applyModelPolicyToBots({
137
+ apiPost: async (path, payload) => {
138
+ posted.push({ path, payload });
139
+ if (path.endsWith("/worker-b/policy")) {
140
+ throw new Error("restart failed");
141
+ }
142
+ return { ok: true };
143
+ },
144
+ bots: [
145
+ {
146
+ id: "worker-a",
147
+ provider: {
148
+ options: {
149
+ sandboxMode: "read-only",
150
+ approvalPolicy: "on-request",
151
+ },
152
+ },
153
+ },
154
+ {
155
+ id: "worker-b",
156
+ provider: {
157
+ options: {
158
+ sandboxMode: "workspace-write",
159
+ approvalPolicy: "on-failure",
160
+ },
161
+ },
162
+ },
163
+ ],
164
+ model: null,
165
+ });
166
+ assert.deepEqual(batchResult.updatedBotIds, ["worker-a"]);
167
+ assert.equal(batchResult.failures.length, 1);
168
+ assert.equal(batchResult.failures[0].botId, "worker-b");
169
+ assert.equal(posted.length, 2);
170
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-hub",
3
- "version": "0.1.13",
3
+ "version": "0.1.16",
4
4
  "description": "Copilot Hub CLI and runtime bundle",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,7 +48,8 @@
48
48
  "packages/*"
49
49
  ],
50
50
  "scripts": {
51
- "build": "npm run build:scripts && npm run build --workspaces --if-present",
51
+ "build": "npm run build:scripts && npm run build:workspaces",
52
+ "build:workspaces": "npm run build --workspace @copilot-hub/contracts --if-present && npm run build --workspace @copilot-hub/core --if-present && npm run build --workspace @copilot-hub/agent-engine --if-present && npm run build --workspace @copilot-hub/control-plane --if-present",
52
53
  "build:scripts": "tsc -p scripts/tsconfig.json",
53
54
  "prepare:shared": "npm run build:scripts --silent && node scripts/dist/ensure-shared-build.mjs",
54
55
  "start": "npm run build:scripts --silent && node scripts/dist/cli.mjs start",
@@ -79,6 +80,7 @@
79
80
  },
80
81
  "devDependencies": {
81
82
  "@eslint/js": "^9.22.0",
83
+ "@types/express": "^5.0.6",
82
84
  "@types/node": "^22.13.10",
83
85
  "eslint": "^9.22.0",
84
86
  "globals": "^16.0.0",
@@ -1,39 +1,120 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ type BotConfig = {
3
+ id: string;
4
+ name: string;
5
+ enabled?: boolean;
6
+ autoStart?: boolean;
7
+ threadMode?: string;
8
+ sharedThreadId?: string;
9
+ workspaceRoot: string;
10
+ dataDir: string;
11
+ provider?: {
12
+ kind?: string;
13
+ options?: Record<string, unknown>;
14
+ };
15
+ kernelAccess?: {
16
+ enabled?: boolean;
17
+ allowedActions?: unknown;
18
+ allowedChatIds?: unknown;
19
+ };
20
+ capabilities?: unknown[];
21
+ } & Record<string, unknown>;
22
+ type ProviderDefaults = Record<string, unknown>;
23
+ type KernelActionHandler = (payload: {
24
+ actorBotId: string;
25
+ action: unknown;
26
+ payload: Record<string, unknown>;
27
+ context: Record<string, unknown>;
28
+ }) => Promise<unknown> | unknown;
29
+ type WorkerStatusBase = {
30
+ id: string;
31
+ name: string;
32
+ enabled: boolean;
33
+ autoStart: boolean;
34
+ threadMode: string;
35
+ sharedThreadId: string;
36
+ providerKind: string;
37
+ kernelVersion: string | null;
38
+ webThreadId: string | null;
39
+ running: boolean;
40
+ telegramRunning: boolean;
41
+ telegramError: string | null;
42
+ workspaceRoot: string;
43
+ dataDir: string;
44
+ kernelAccess: {
45
+ enabled: boolean;
46
+ allowedActions: string[];
47
+ allowedChatIds: string[];
48
+ };
49
+ capabilities: unknown[];
50
+ channels: unknown[];
51
+ };
52
+ type SupervisorStatus = WorkerStatusBase & {
53
+ lastHeartbeatAt: string | null;
54
+ lastHeartbeatError: string | null;
55
+ };
56
+ type PendingRequest = {
57
+ resolve: (value: unknown) => void;
58
+ reject: (error: Error) => void;
59
+ timer: NodeJS.Timeout;
60
+ };
1
61
  export declare class AgentSupervisor {
2
62
  #private;
63
+ botConfig: BotConfig;
64
+ providerDefaults: ProviderDefaults;
65
+ turnActivityTimeoutMs: number;
66
+ maxMessages: number;
67
+ webPublicBaseUrl: string;
68
+ workerScriptPath: string;
69
+ onKernelAction: KernelActionHandler | null;
70
+ child: ChildProcess | null;
71
+ startingPromise: Promise<void> | null;
72
+ shutdownRequested: boolean;
73
+ restartTimer: NodeJS.Timeout | null;
74
+ restartAttempt: number;
75
+ nextRequestId: number;
76
+ pendingRequests: Map<string, PendingRequest>;
77
+ desiredChannelsRunning: boolean;
78
+ heartbeatInFlight: Promise<SupervisorStatus> | null;
79
+ recoveryPromise: Promise<void> | null;
80
+ lastHeartbeatAt: string | null;
81
+ lastHeartbeatError: string | null;
82
+ statusCache: WorkerStatusBase;
3
83
  constructor({ botConfig, providerDefaults, turnActivityTimeoutMs, maxMessages, webPublicBaseUrl, workerScriptPath, onKernelAction, }: {
4
- botConfig: any;
5
- providerDefaults: any;
6
- turnActivityTimeoutMs: any;
7
- maxMessages: any;
8
- webPublicBaseUrl: any;
9
- workerScriptPath: any;
10
- onKernelAction?: null | undefined;
84
+ botConfig: BotConfig;
85
+ providerDefaults: ProviderDefaults;
86
+ turnActivityTimeoutMs: number;
87
+ maxMessages: number;
88
+ webPublicBaseUrl: string;
89
+ workerScriptPath: string;
90
+ onKernelAction?: KernelActionHandler | null;
11
91
  });
12
92
  get id(): string;
13
- get config(): any;
14
- getStatus(): any;
15
- setWebPublicBaseUrl(value: any): void;
93
+ get config(): BotConfig;
94
+ getStatus(): SupervisorStatus;
95
+ setWebPublicBaseUrl(value: string): void;
16
96
  boot(): Promise<void>;
17
97
  ensureWorker(): Promise<void>;
18
- startChannels(): Promise<any>;
19
- stopChannels(): Promise<any>;
98
+ startChannels(): Promise<SupervisorStatus>;
99
+ stopChannels(): Promise<SupervisorStatus>;
20
100
  resetWebThread(): Promise<unknown>;
21
- listPendingApprovals(threadId: any): Promise<unknown>;
22
- resolvePendingApproval({ threadId, approvalId, decision }: {
23
- threadId: any;
24
- approvalId: any;
25
- decision: any;
101
+ listPendingApprovals(threadId?: string): Promise<unknown>;
102
+ resolvePendingApproval({ threadId, approvalId, decision, }: {
103
+ threadId: string;
104
+ approvalId: string;
105
+ decision: string;
26
106
  }): Promise<unknown>;
27
- listCapabilities(): Promise<any>;
28
- setCapabilities(nextCapabilities: any): void;
29
- setProviderOptions(nextOptions: any): Promise<any>;
30
- reloadCapabilities(nextCapabilities?: null): Promise<any>;
31
- setProjectRoot(projectRoot: any): Promise<any>;
32
- refreshStatus(timeoutMs?: number): Promise<any>;
33
- heartbeat({ timeoutMs }?: {
34
- timeoutMs?: number | undefined;
35
- }): Promise<any>;
36
- forceRestart(reason?: string): Promise<any>;
107
+ listCapabilities(): Promise<unknown[]>;
108
+ setCapabilities(nextCapabilities: unknown): void;
109
+ setProviderOptions(nextOptions: Record<string, unknown>): Promise<SupervisorStatus>;
110
+ reloadCapabilities(nextCapabilities?: unknown): Promise<SupervisorStatus>;
111
+ setProjectRoot(projectRoot: string): Promise<SupervisorStatus>;
112
+ refreshStatus(timeoutMs?: number): Promise<SupervisorStatus>;
113
+ heartbeat({ timeoutMs, }?: {
114
+ timeoutMs?: number;
115
+ }): Promise<SupervisorStatus>;
116
+ forceRestart(reason?: string): Promise<SupervisorStatus>;
37
117
  shutdown(): Promise<void>;
38
- request(action: any, payload?: null, timeoutMs?: number): Promise<unknown>;
118
+ request(action: string, payload?: unknown, timeoutMs?: number): Promise<unknown>;
39
119
  }
120
+ export {};
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { fork } from "node:child_process";
3
2
  const REQUEST_TIMEOUT_MS = 15000;
4
3
  const HEARTBEAT_TIMEOUT_MS = 4000;
@@ -8,6 +7,26 @@ const WORKER_READY_POLL_INTERVAL_MS = 150;
8
7
  const WORKER_RECOVERY_TIMEOUT_MS = 20000;
9
8
  const WORKER_RECOVERY_POLL_INTERVAL_MS = 250;
10
9
  export class AgentSupervisor {
10
+ botConfig;
11
+ providerDefaults;
12
+ turnActivityTimeoutMs;
13
+ maxMessages;
14
+ webPublicBaseUrl;
15
+ workerScriptPath;
16
+ onKernelAction;
17
+ child;
18
+ startingPromise;
19
+ shutdownRequested;
20
+ restartTimer;
21
+ restartAttempt;
22
+ nextRequestId;
23
+ pendingRequests;
24
+ desiredChannelsRunning;
25
+ heartbeatInFlight;
26
+ recoveryPromise;
27
+ lastHeartbeatAt;
28
+ lastHeartbeatError;
29
+ statusCache;
11
30
  constructor({ botConfig, providerDefaults, turnActivityTimeoutMs, maxMessages, webPublicBaseUrl, workerScriptPath, onKernelAction = null, }) {
12
31
  this.botConfig = botConfig;
13
32
  this.providerDefaults = providerDefaults;
@@ -102,7 +121,7 @@ export class AgentSupervisor {
102
121
  await this.ensureWorker();
103
122
  return this.request("listPendingApprovals", { threadId });
104
123
  }
105
- async resolvePendingApproval({ threadId, approvalId, decision }) {
124
+ async resolvePendingApproval({ threadId, approvalId, decision, }) {
106
125
  await this.ensureWorker();
107
126
  return this.request("resolvePendingApproval", {
108
127
  threadId,
@@ -136,6 +155,15 @@ export class AgentSupervisor {
136
155
  if (typeof nextOptions?.approvalPolicy === "string" && nextOptions.approvalPolicy.trim()) {
137
156
  mergedOptions.approvalPolicy = nextOptions.approvalPolicy.trim();
138
157
  }
158
+ if (Object.prototype.hasOwnProperty.call(nextOptions ?? {}, "model")) {
159
+ const normalizedModel = String(nextOptions?.model ?? "").trim();
160
+ if (normalizedModel) {
161
+ mergedOptions.model = normalizedModel;
162
+ }
163
+ else {
164
+ delete mergedOptions.model;
165
+ }
166
+ }
139
167
  this.botConfig = {
140
168
  ...previousConfig,
141
169
  provider: {
@@ -145,7 +173,7 @@ export class AgentSupervisor {
145
173
  },
146
174
  };
147
175
  try {
148
- const status = await this.forceRestart("provider policy updated");
176
+ const status = await this.forceRestart("provider options updated");
149
177
  this.#updateStatus(status);
150
178
  return this.getStatus();
151
179
  }
@@ -159,7 +187,7 @@ export class AgentSupervisor {
159
187
  }
160
188
  this.botConfig = previousConfig;
161
189
  try {
162
- await this.forceRestart("provider policy rollback");
190
+ await this.forceRestart("provider options rollback");
163
191
  }
164
192
  catch {
165
193
  // Best effort rollback only.
@@ -172,9 +200,11 @@ export class AgentSupervisor {
172
200
  this.setCapabilities(nextCapabilities);
173
201
  }
174
202
  await this.ensureWorker();
175
- const status = await this.request("reloadCapabilities", {
176
- capabilityDefinitions: Array.isArray(nextCapabilities) ? nextCapabilities : undefined,
177
- });
203
+ const payload = {};
204
+ if (Array.isArray(nextCapabilities)) {
205
+ payload.capabilityDefinitions = nextCapabilities;
206
+ }
207
+ const status = await this.request("reloadCapabilities", payload);
178
208
  this.#updateStatus(status);
179
209
  return this.getStatus();
180
210
  }
@@ -190,7 +220,7 @@ export class AgentSupervisor {
190
220
  this.#updateStatus(status);
191
221
  return this.getStatus();
192
222
  }
193
- async heartbeat({ timeoutMs = HEARTBEAT_TIMEOUT_MS } = {}) {
223
+ async heartbeat({ timeoutMs = HEARTBEAT_TIMEOUT_MS, } = {}) {
194
224
  if (this.shutdownRequested) {
195
225
  return this.getStatus();
196
226
  }
@@ -274,7 +304,7 @@ export class AgentSupervisor {
274
304
  const child = this.child;
275
305
  this.child = null;
276
306
  try {
277
- child.kill("SIGTERM");
307
+ child?.kill("SIGTERM");
278
308
  }
279
309
  catch {
280
310
  // Ignore.
@@ -282,7 +312,8 @@ export class AgentSupervisor {
282
312
  this.#setOfflineStatus();
283
313
  }
284
314
  request(action, payload = null, timeoutMs = REQUEST_TIMEOUT_MS) {
285
- if (!this.child || !this.child.connected) {
315
+ const child = this.child;
316
+ if (!child || !child.connected) {
286
317
  return Promise.reject(new Error(`Worker '${this.id}' is not running.`));
287
318
  }
288
319
  const requestId = `${this.id}_${Date.now()}_${this.nextRequestId++}`;
@@ -297,7 +328,7 @@ export class AgentSupervisor {
297
328
  timer,
298
329
  });
299
330
  try {
300
- this.child.send({
331
+ child.send({
301
332
  type: "request",
302
333
  requestId,
303
334
  action,
@@ -307,7 +338,7 @@ export class AgentSupervisor {
307
338
  catch (error) {
308
339
  clearTimeout(timer);
309
340
  this.pendingRequests.delete(requestId);
310
- reject(error);
341
+ reject(toError(error));
311
342
  }
312
343
  });
313
344
  }
@@ -317,6 +348,7 @@ export class AgentSupervisor {
317
348
  clearTimeout(this.restartTimer);
318
349
  this.restartTimer = null;
319
350
  }
351
+ const windowsForkOptions = process.platform === "win32" ? { windowsHide: true } : {};
320
352
  const child = fork(this.workerScriptPath, [], {
321
353
  cwd: process.cwd(),
322
354
  env: {
@@ -328,8 +360,8 @@ export class AgentSupervisor {
328
360
  AGENT_WEB_PUBLIC_BASE_URL: this.webPublicBaseUrl,
329
361
  AGENT_KERNEL_REQUEST_TIMEOUT_MS: String(REQUEST_TIMEOUT_MS),
330
362
  },
363
+ ...windowsForkOptions,
331
364
  stdio: ["ignore", "pipe", "pipe", "ipc"],
332
- windowsHide: true,
333
365
  });
334
366
  this.child = child;
335
367
  child.stdout?.on("data", (chunk) => {
@@ -356,32 +388,35 @@ export class AgentSupervisor {
356
388
  await this.refreshStatus();
357
389
  }
358
390
  #handleWorkerMessage(message) {
359
- if (!message || typeof message !== "object") {
391
+ const record = asRecord(message);
392
+ if (!record.type) {
360
393
  return;
361
394
  }
362
- if (message.type === "response") {
363
- const requestId = String(message.requestId ?? "");
395
+ if (record.type === "response") {
396
+ const response = record;
397
+ const requestId = String(response.requestId ?? "");
364
398
  const pending = this.pendingRequests.get(requestId);
365
399
  if (!pending) {
366
400
  return;
367
401
  }
368
402
  this.pendingRequests.delete(requestId);
369
403
  clearTimeout(pending.timer);
370
- if (message.ok) {
371
- pending.resolve(message.result);
404
+ if (response.ok) {
405
+ pending.resolve(response.result);
372
406
  }
373
407
  else {
374
- pending.reject(new Error(String(message.error ?? "Unknown worker error.")));
408
+ pending.reject(new Error(String(response.error ?? "Unknown worker error.")));
375
409
  }
376
410
  return;
377
411
  }
378
- if (message.type === "event") {
379
- if (message.event === "workerReady") {
412
+ if (record.type === "event") {
413
+ const event = record;
414
+ if (event.event === "workerReady") {
380
415
  return;
381
416
  }
382
417
  }
383
- if (message.type === "kernelRequest") {
384
- void this.#handleKernelRequest(message);
418
+ if (record.type === "kernelRequest") {
419
+ void this.#handleKernelRequest(record);
385
420
  }
386
421
  }
387
422
  async #handleKernelRequest(message) {
@@ -401,8 +436,8 @@ export class AgentSupervisor {
401
436
  const result = await this.onKernelAction({
402
437
  actorBotId: this.id,
403
438
  action: message?.action,
404
- payload: message?.payload ?? {},
405
- context: message?.context ?? {},
439
+ payload: asRecord(message?.payload),
440
+ context: asRecord(message?.context),
406
441
  });
407
442
  this.#sendKernelResponse({
408
443
  requestId,
@@ -418,7 +453,7 @@ export class AgentSupervisor {
418
453
  });
419
454
  }
420
455
  }
421
- #sendKernelResponse({ requestId, ok, result = null, error = null }) {
456
+ #sendKernelResponse({ requestId, ok, result = null, error = null, }) {
422
457
  if (!this.child || !this.child.connected) {
423
458
  return;
424
459
  }
@@ -435,7 +470,7 @@ export class AgentSupervisor {
435
470
  // Ignore response send errors.
436
471
  }
437
472
  }
438
- #handleWorkerExit({ code, signal }) {
473
+ #handleWorkerExit({ code, signal, }) {
439
474
  if (this.child) {
440
475
  this.child = null;
441
476
  }
@@ -454,20 +489,23 @@ export class AgentSupervisor {
454
489
  }, delayMs);
455
490
  }
456
491
  #rejectAllPending(error) {
492
+ const normalized = toError(error);
457
493
  for (const pending of this.pendingRequests.values()) {
458
494
  clearTimeout(pending.timer);
459
- pending.reject(error);
495
+ pending.reject(normalized);
460
496
  }
461
497
  this.pendingRequests.clear();
462
498
  }
463
499
  #updateStatus(status) {
464
- if (!status || typeof status !== "object") {
500
+ const statusRecord = asRecord(status);
501
+ if (Object.keys(statusRecord).length === 0) {
465
502
  return;
466
503
  }
504
+ const running = typeof statusRecord.running === "boolean" ? statusRecord.running : this.statusCache.running;
467
505
  this.statusCache = {
468
506
  ...this.statusCache,
469
- ...status,
470
- running: status.running ?? this.statusCache.running,
507
+ ...statusRecord,
508
+ running,
471
509
  };
472
510
  if (this.statusCache.running) {
473
511
  this.restartAttempt = 0;
@@ -499,29 +537,27 @@ async function waitForWorkerReady(supervisor, child) {
499
537
  throw new Error(`Worker '${supervisor.id}' did not become ready within ${timeoutMs}ms.`);
500
538
  }
501
539
  function createInitialStatus(botConfig) {
540
+ const provider = asRecord(botConfig.provider);
541
+ const kernelAccess = asRecord(botConfig.kernelAccess);
502
542
  return {
503
- id: botConfig.id,
504
- name: botConfig.name,
543
+ id: String(botConfig.id),
544
+ name: String(botConfig.name),
505
545
  enabled: botConfig.enabled !== false,
506
546
  autoStart: Boolean(botConfig.autoStart),
507
- threadMode: botConfig.threadMode,
508
- sharedThreadId: botConfig.sharedThreadId,
509
- providerKind: botConfig.provider?.kind ?? "codex",
547
+ threadMode: String(botConfig.threadMode ?? "single"),
548
+ sharedThreadId: String(botConfig.sharedThreadId ?? ""),
549
+ providerKind: String(provider.kind ?? "codex"),
510
550
  kernelVersion: null,
511
551
  webThreadId: null,
512
552
  running: false,
513
553
  telegramRunning: false,
514
554
  telegramError: null,
515
- workspaceRoot: botConfig.workspaceRoot,
516
- dataDir: botConfig.dataDir,
555
+ workspaceRoot: String(botConfig.workspaceRoot),
556
+ dataDir: String(botConfig.dataDir),
517
557
  kernelAccess: {
518
- enabled: botConfig.kernelAccess?.enabled === true,
519
- allowedActions: Array.isArray(botConfig.kernelAccess?.allowedActions)
520
- ? [...botConfig.kernelAccess.allowedActions]
521
- : [],
522
- allowedChatIds: Array.isArray(botConfig.kernelAccess?.allowedChatIds)
523
- ? [...botConfig.kernelAccess.allowedChatIds]
524
- : [],
558
+ enabled: kernelAccess.enabled === true,
559
+ allowedActions: normalizeStringList(kernelAccess.allowedActions),
560
+ allowedChatIds: normalizeStringList(kernelAccess.allowedChatIds),
525
561
  },
526
562
  capabilities: [],
527
563
  channels: [],
@@ -584,4 +620,22 @@ function sanitizeError(error) {
584
620
  const raw = error instanceof Error ? error.message : String(error);
585
621
  return raw.split(/\r?\n/).slice(0, 12).join("\n");
586
622
  }
623
+ function asRecord(value) {
624
+ return value && typeof value === "object" ? value : {};
625
+ }
626
+ function toError(error) {
627
+ return error instanceof Error ? error : new Error(String(error));
628
+ }
629
+ function normalizeStringList(value) {
630
+ if (Array.isArray(value)) {
631
+ return value.map((entry) => String(entry ?? "").trim()).filter(Boolean);
632
+ }
633
+ if (typeof value === "string") {
634
+ return value
635
+ .split(",")
636
+ .map((entry) => entry.trim())
637
+ .filter(Boolean);
638
+ }
639
+ return [];
640
+ }
587
641
  //# sourceMappingURL=agent-supervisor.js.map